ENH: Add a friend list column with room type info

This commit is contained in:
bananarama92 2025-03-18 20:38:28 +01:00
parent 0b2bb53c8d
commit 620bbd1394
No known key found for this signature in database
GPG key ID: E83C7D3B5DA36248
9 changed files with 184 additions and 45 deletions

View file

@ -103,6 +103,49 @@
display: block;
}
.ChatRoomType {
display: flex;
justify-content: center;
gap: 0.15em;
user-select: none;
}
.friend-list-icon-container {
position: relative;
height: var(--row-height);
width: var(--row-height);
max-width: 86px;
max-height: 86px;
aspect-ratio: 1 / 1;
}
.friend-list-icon-container > .button-tooltip {
--tooltip-gap: 0.15em;
}
.friend-list-icon {
pointer-events: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
@media (hover: hover) {
@supports selector(:has(*)) {
.friend-list-icon-container:hover:not(:has(.button-tooltip:hover)) > .button-tooltip {
visibility: visible;
}
}
@supports not selector(:has(*)) {
.friend-list-icon-container:hover > .button-tooltip {
visibility: visible;
}
}
}
.friend-list-icon-small {
pointer-events: none;
height: var(--row-height);
@ -132,21 +175,25 @@
width: calc(100% - var(--button-size));
}
#friend-list-table td {
margin: unset;
#friend-list-table th {
font-weight: normal;
}
.friend-list-row {
color: var(--text-color);
min-height: var(--row-height);
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-evenly;
padding: unset;
margin: var(--small-gap) 0;
}
.friend-list-row * {
margin: var(--small-gap) 0;
.friend-list-row > * {
vertical-align: middle;
text-justify: center;
padding: unset;
}
.friend-list-row:hover {
@ -161,10 +208,6 @@
white-space: preserve;
}
#friend-list .friend-list-column {
height: 100%;
}
#friend-list-member-number,
.MemberNumber {
width: 10%;

Binary file not shown.

After

(image error) Size: 3.5 KiB

Binary file not shown.

After

(image error) Size: 2 KiB

Binary file not shown.

After

(image error) Size: 3.6 KiB

Binary file not shown.

After

(image error) Size: 2.1 KiB

View file

@ -1,6 +1,6 @@
type FriendListModes = FriendListMode[];
type FriendListMode = "OnlineFriends" | "Beeps" | "AllFriends";
type FriendListSortingMode = 'None' | 'MemberName' | 'MemberNickname' | 'MemberNumber' | 'ChatRoomName' | 'RelationType';
type FriendListSortingMode = 'None' | 'MemberName' | 'MemberNickname' | 'MemberNumber' | 'ChatRoomName' | 'RelationType' | 'ChatRoomType';
type FriendListSortingDirection = 'Asc' | 'Desc';
type FriendListReturn<T extends ModuleType> = { Screen: ModuleScreens[T], Module: T, IsInChatRoom?: boolean, hasScrolledChat?: boolean };
@ -19,6 +19,7 @@ type FriendRawRoom = {
name?: string;
caption: string;
canSearchRoom: boolean;
types: FriendListIcon[];
};
type FriendRawBeep = {
@ -27,3 +28,12 @@ type FriendRawBeep = {
hasMessage?: boolean;
canBeep?: boolean;
};
interface FriendListIcon {
/** The {@link HTMLImageElement.src} of the icon */
src: string;
/** The `Character/FriendList` {@link TextGet} key of the icon's tooltip */
tooltipKey: string;
/** A string to-be used for sorting the icon-containing column cells */
sortKey: string;
}

View file

@ -232,6 +232,15 @@ function FriendListLoad() {
attributes: { role: "columnheader" },
}},
),
ElementButton.Create(
"friend-list-chat-room-type",
() => FriendListChangeSortingMode("ChatRoomType"),
{ noStyling: true },
{ button: {
classList: ['friend-list-column', 'friend-list-link', 'mode-specific-content', 'fl-online-friends-content', 'fl-beeps-content'],
attributes: { role: "columnheader" },
}},
),
ElementButton.Create(
"friend-list-chat-room-name",
() => FriendListChangeSortingMode("ChatRoomName"),
@ -544,6 +553,15 @@ function FriendListChatSearch(room) {
ElementValue("InputSearch", ChatSearchMuffle(room));
}
/** @satisfies {{ [key in (ServerChatRoomSpace | "Private")]: FriendListIcon }} */
const FriendListIconMapping = {
"": { src: "./Icons/FemaleInvert.png", tooltipKey: "TypeFemale", sortKey: "F " },
M: { src: "./Icons/MaleInvert.png", tooltipKey: "TypeMale", sortKey: "M " },
X: { src: "./Icons/GenderInvert.png", tooltipKey: "TypeMixed", sortKey: "FM" },
Asylum: { src: "./Icons/Asylum.png", tooltipKey: "TypeAsylum", sortKey: "A " },
Private: { src: "./Icons/PrivateInvert.png", tooltipKey: "TypePrivate", sortKey: "P" },
};
/**
* Loads the friend list data into the HTML div element.
* @param {ServerFriendInfo[]} data - An array of data, we receive from the server
@ -566,7 +584,6 @@ function FriendListLoadFriendList(data) {
const BeepCaption = InterfaceTextGet("Beep");
const DeleteCaption = InterfaceTextGet("Delete");
const ConfirmDeleteCaption = InterfaceTextGet("ConfirmDelete");
const PrivateRoomCaption = InterfaceTextGet("PrivateRoom");
const SentCaption = InterfaceTextGet("SentBeep");
const ReceivedCaption = InterfaceTextGet("ReceivedBeep");
const MailCaption = InterfaceTextGet("BeepWithMail");
@ -641,25 +658,20 @@ function FriendListLoadFriendList(data) {
const originalChatRoomName = friend.ChatRoomName || '';
const chatRoomSpaceCaption = InterfaceTextGet(`ChatRoomSpace${friend.ChatRoomSpace || "F"}`);
const chatRoomName = ChatSearchMuffle(friend.ChatRoomName?.replaceAll('<', '&lt;').replaceAll('>', '&gt;') || undefined);
let caption = '';
const canSearchRoom = FriendListReturn?.Screen === 'ChatSearch' && ChatRoomSpace === (friend.ChatRoomSpace || '');
const canBeep = true;
const rawCaption = [];
if (chatRoomSpaceCaption && chatRoomName) rawCaption.push(`<i>${chatRoomSpaceCaption}</i>`);
if (friend.Private) rawCaption.push(PrivateRoomCaption);
if (chatRoomName) rawCaption.push(chatRoomName);
if (rawCaption.length === 0) rawCaption.push('-');
caption = rawCaption.join(' - ');
friendRawData.push({
memberName: friend.MemberName,
memberNumber: friend.MemberNumber,
chatRoom: {
name: originalChatRoomName,
caption: caption,
caption: chatRoomName || "-",
canSearchRoom: canSearchRoom,
types: [
chatRoomSpaceCaption && chatRoomName ? FriendListIconMapping[friend.ChatRoomSpace ?? ""] : null,
friend.Private ? FriendListIconMapping.Private : null,
].filter(Boolean),
},
beep: {
canBeep: canBeep,
@ -673,18 +685,9 @@ function FriendListLoadFriendList(data) {
const beepData = FriendListBeepLog[i];
const chatRoomSpaceCaption = InterfaceTextGet(`ChatRoomSpace${beepData.ChatRoomSpace || "F"}`);
const chatRoomName = ChatSearchMuffle(beepData.ChatRoomName?.replaceAll('<', '&lt;').replaceAll('>', '&gt;') || undefined);
let chatRoomCaption = '';
let beepCaption = '';
const canSearchRoom = FriendListReturn?.Screen === 'ChatSearch' && ChatRoomSpace === (beepData.ChatRoomSpace || '');
const rawRoomCaption = [];
if (chatRoomSpaceCaption && chatRoomName) rawRoomCaption.push(`<i>${chatRoomSpaceCaption}</i>`);
if (beepData.Private) rawRoomCaption.push(PrivateRoomCaption);
if (chatRoomName) rawRoomCaption.push(chatRoomName);
if (rawRoomCaption.length === 0) rawRoomCaption.push('-');
chatRoomCaption = rawRoomCaption.join(' - ');
const rawBeepCaption = [];
if (beepData.Sent) {
rawBeepCaption.push(SentCaption);
@ -703,8 +706,12 @@ function FriendListLoadFriendList(data) {
memberNumber: beepData.MemberNumber,
chatRoom: {
name: beepData.ChatRoomName,
caption: chatRoomCaption,
caption: chatRoomName || "-",
canSearchRoom: canSearchRoom,
types: [
chatRoomSpaceCaption && chatRoomName ? FriendListIconMapping[beepData.ChatRoomSpace ?? ""] : null,
beepData.Private ? FriendListIconMapping.Private : null,
].filter(Boolean),
},
beep: {
beepIndex: i,
@ -760,21 +767,94 @@ function FriendListLoadFriendList(data) {
if (friend.chatRoom) {
if (!friend.chatRoom.name || !friend.chatRoom.canSearchRoom) {
row.appendChild(ElementCreate({
// Sorting is performed via each cell's `textContent`,
// so explicitly prepend an invisible node with some sorting key
let totalSortKey = "";
const imgContainer = ElementCreate({
tag: "td",
classList: ['friend-list-column', 'ChatRoomName'],
innerHTML: friend.chatRoom.caption,
}));
classList: ['friend-list-column', 'ChatRoomType'],
children: [
{ tag: "span", style: { display: "none" }, classList: ["friend-list-sorting-node"] },
...friend.chatRoom.types.map(({ src, tooltipKey, sortKey }) => {
totalSortKey += sortKey;
return {
tag: /** @type {const} */("div"),
classList: ["friend-list-icon-container"],
children: [
{
tag: /** @type {const} */("img"),
attributes: { src, decoding: "async", loading: "lazy", alt: TextGet(tooltipKey) },
classList: ["friend-list-icon"],
},
{
tag: /** @type {const} */("div"),
attributes: { role: "tooltip", "aria-hidden": "true" },
children: [TextGet(tooltipKey)],
classList: ["button-tooltip", "button-tooltip-right"],
},
],
};
}),
],
});
imgContainer.children[0].textContent = totalSortKey + " ";
if (imgContainer.children.length === 1) {
imgContainer.append("-");
}
row.append(
imgContainer,
ElementCreate({
tag: "td",
classList: ['friend-list-column', 'ChatRoomName'],
children: [friend.chatRoom.caption],
}),
);
} else if (friend.chatRoom.canSearchRoom) {
row.appendChild(ElementCreate({
tag: "button",
classList: ['friend-list-column', 'friend-list-link', 'blank-button', 'ChatRoomName'],
innerHTML: friend.chatRoom.caption,
eventListeners: {
click: () => FriendListChatSearch(friend.chatRoom.name),
},
attributes: { role: "cell" },
}));
// Sorting is performed via each cell's `textContent`,
// so explicitly prepend an invisible node with some sorting key
let totalSortKey = "";
const imgContainer = ElementCreate({
tag: "td",
classList: ['friend-list-column', 'ChatRoomType'],
children: [
{ tag: "span", style: { display: "none" }, classList: ["friend-list-sorting-node"] },
...friend.chatRoom.types.map(({ src, tooltipKey, sortKey }) => {
totalSortKey += sortKey;
return {
tag: /** @type {const} */("div"),
classList: ["friend-list-icon-container"],
children: [
{
tag: /** @type {const} */("img"),
attributes: { src, decoding: "async", loading: "lazy", alt: TextGet(tooltipKey) },
classList: ["friend-list-icon"],
},
{
tag: /** @type {const} */("div"),
attributes: { role: "tooltip", "aria-hidden": "true" },
children: [TextGet(tooltipKey)],
classList: ["button-tooltip", "button-tooltip-right"],
},
],
};
}),
],
});
imgContainer.children[0].textContent = totalSortKey + " ";
if (imgContainer.children.length === 1) {
imgContainer.append("-");
}
row.append(
imgContainer,
ElementCreate({
tag: "td",
classList: ['friend-list-column', 'friend-list-link', 'blank-button', 'ChatRoomName'],
innerHTML: friend.chatRoom.caption,
eventListeners: {
click: () => FriendListChatSearch(friend.chatRoom.name),
},
}),
);
}
}

View file

@ -5,7 +5,8 @@ MemberName,Name
MemberNickname,Nickname
MemberNumber,Member number
ChatRoomName,Chat room
FriendType,Relation type
ChatRoomType,Room type
RelationType,Relation type
ActionFriends,Send a Beep
ActionRead,Read a Beep
ActionDelete,Delete a Friend
@ -21,3 +22,8 @@ TypeOwner,Owner
TypeLover,Lover
TypeSubmissive,Submissive
TypeFriend,Friend
TypeFemale,Femaly-only room
TypeMale,Male-only room
TypeMixed,Mixed male/female room
TypeAsylum,Asylum room
TypePrivate,Private room

1 OnlineFriends Online friends
5 MemberNickname Nickname
6 MemberNumber Member number
7 ChatRoomName Chat room
8 FriendType ChatRoomType Relation type Room type
9 RelationType Relation type
10 ActionFriends Send a Beep
11 ActionRead Read a Beep
12 ActionDelete Delete a Friend
22 TypeLover Lover
23 TypeSubmissive Submissive
24 TypeFriend Friend
25 TypeFemale Femaly-only room
26 TypeMale Male-only room
27 TypeMixed Mixed male/female room
28 TypeAsylum Asylum room
29 TypePrivate Private room

View file

@ -881,7 +881,7 @@ interface IFriendListBeepLogMessage {
MemberName: string;
ChatRoomName?: string;
Private: boolean;
ChatRoomSpace?: string;
ChatRoomSpace?: ServerChatRoomSpace;
Sent: boolean;
Time: Date;
Message?: string;