mirror of
https://gitgud.io/BondageProjects/Bondage-College.git
synced 2025-04-25 17:59:34 +00:00
ENH: Add a friend list column with room type info
This commit is contained in:
parent
0b2bb53c8d
commit
620bbd1394
9 changed files with 184 additions and 45 deletions
BondageClub
CSS
Icons
Screens/Character/FriendList
Scripts
|
@ -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%;
|
||||
|
|
BIN
BondageClub/Icons/FemaleInverted.png
Normal file
BIN
BondageClub/Icons/FemaleInverted.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 3.5 KiB |
BIN
BondageClub/Icons/GenderInvert.png
Normal file
BIN
BondageClub/Icons/GenderInvert.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2 KiB |
BIN
BondageClub/Icons/MaleInverted.png
Normal file
BIN
BondageClub/Icons/MaleInverted.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 3.6 KiB |
BIN
BondageClub/Icons/PrivateInvert.png
Normal file
BIN
BondageClub/Icons/PrivateInvert.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.1 KiB |
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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('<', '<').replaceAll('>', '>') || 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('<', '<').replaceAll('>', '>') || 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),
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
2
BondageClub/Scripts/Typedef.d.ts
vendored
2
BondageClub/Scripts/Typedef.d.ts
vendored
|
@ -881,7 +881,7 @@ interface IFriendListBeepLogMessage {
|
|||
MemberName: string;
|
||||
ChatRoomName?: string;
|
||||
Private: boolean;
|
||||
ChatRoomSpace?: string;
|
||||
ChatRoomSpace?: ServerChatRoomSpace;
|
||||
Sent: boolean;
|
||||
Time: Date;
|
||||
Message?: string;
|
||||
|
|
Loading…
Add table
Reference in a new issue