Merge remote-tracking branch 'origin/main' into profile-modal
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
ci/woodpecker/pr/build-and-publish Pipeline was successful

This commit is contained in:
Twig 2025-07-19 16:54:13 +02:00
commit cf84ba9d0a
No known key found for this signature in database
8 changed files with 224 additions and 69 deletions

View file

@ -1,10 +1,10 @@
<template>
<div v-if="isCurrentChannel" class="channel-list-link-container rounded-corners current-channel" tabindex="0">
<div v-if="isCurrentChannel" class="channel-list-link-container rounded-corners current-channel" tabindex="0" :title="props.name">
<NuxtLink class="channel-list-link" :href="props.href" tabindex="-1">
# {{ props.name }}
</NuxtLink>
</div>
<div v-else class="channel-list-link-container rounded-corners" tabindex="0">
<div v-else class="channel-list-link-container rounded-corners" tabindex="0" :title="props.name">
<NuxtLink class="channel-list-link" :href="props.href" tabindex="-1">
# {{ props.name }}
</NuxtLink>
@ -25,6 +25,8 @@ const isCurrentChannel = props.uuid == props.currentUuid;
color: inherit;
padding-left: .25em;
padding-right: .25em;
overflow: hidden;
text-overflow: ellipsis;
}
.channel-list-link-container {

View file

@ -1,27 +1,30 @@
<template>
<div id="middle-left-column">
<div id="friend-sidebar">
<div>
<h3>Direct Messages</h3>
</div>
<VerticalSpacer />
<NuxtLink class="user-item" :href="`/me`" tabindex="0">
<Icon class="user-avatar" name="lucide:user" />
<span class="user-display-name">Friends</span>
</NuxtLink>
<VerticalSpacer />
<div id="direct-message-list">
<UserEntry v-for="user of friends" :user="user"
:href="`/me/${user.uuid}`"/>
<ResizableSidebar width="14rem" min-width="8rem" max-width="30rem" border-sides="right" local-storage-name="middleLeftColumn">
<div id="middle-left-column">
<div id="friend-sidebar">
<div>
<h3>Direct Messages</h3>
</div>
<VerticalSpacer />
<NuxtLink class="user-item" :href="`/me`" tabindex="0">
<Icon class="user-avatar" name="lucide:user" />
<span class="user-display-name">Friends</span>
</NuxtLink>
<VerticalSpacer />
<div id="direct-message-list">
<UserEntry v-for="user of friends" :user="user"
:href="`/me/${user.uuid}`"/>
</div>
</div>
</div>
</div>
</ResizableSidebar>
</template>
<script lang="ts" setup>
import VerticalSpacer from '~/components/UserInterface/VerticalSpacer.vue';
import ResizableSidebar from '../UserInterface/ResizableSidebar.vue';
const { fetchFriends } = useApi();

View file

@ -34,7 +34,15 @@ const props = defineProps<{
}
.user-avatar {
width: 2.3em;
height: 2.3em;
min-width: 2.3em;
max-width: 2.3em;
min-width: 2.3em;
max-height: 2.3em;
}
.user-display-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View file

@ -7,13 +7,13 @@
<script lang="ts" setup>
import type { ContextMenuItem } from '~/types/interfaces';
const props = defineProps<{ menuItems: ContextMenuItem[], cursorX: number, cursorY: number }>();
const props = defineProps<{ menuItems: ContextMenuItem[], pointerX: number, pointerY: number }>();
onMounted(() => {
const contextMenu = document.getElementById("context-menu");
if (contextMenu) {
contextMenu.style.left = props.cursorX.toString() + "px";
contextMenu.style.top = props.cursorY.toString() + "px";
contextMenu.style.left = props.pointerX.toString() + "px";
contextMenu.style.top = props.pointerY.toString() + "px";
}
});

View file

@ -0,0 +1,140 @@
<template>
<div ref="resizableSidebar" class="resizable-sidebar"
:style="{
'width': storedWidth ? `${storedWidth}px` : props.width,
'min-width': props.minWidth,
'max-width': props.maxWidth,
'border': props.borderSides == 'all' ? borderStyling : undefined,
'border-top': props.borderSides?.includes('top') ? borderStyling : undefined,
'border-bottom': props.borderSides?.includes('bottom') ? borderStyling : undefined,
}">
<div v-if="props.borderSides != 'right'" class="width-resizer-bar">
<div ref="widthResizer" class="width-resizer"></div>
</div>
<div class="sidebar-content">
<slot />
</div>
<div v-if="props.borderSides == 'right'" class="width-resizer-bar">
<div ref="widthResizer" class="width-resizer"></div>
</div>
</div>
</template>
<script lang="ts" setup>
import type { ContextMenuItem } from '~/types/interfaces';
const props = defineProps<{ width?: string, minWidth: string, maxWidth: string, borderSides: "all" | "top" | "right" | "bottom" | "left" | ("top" | "right" | "bottom" | "left")[], localStorageName?: string }>();
const borderStyling = ".1rem solid var(--padding-color)";
const resizableSidebar = ref<HTMLDivElement>();
const widthResizer = ref<HTMLDivElement>();
const storedWidth = ref<number>();
const menuItems: ContextMenuItem[] = [
{ name: "Reset", callback: () => { resizableSidebar.value!.style.width = props.width ?? props.minWidth } }
]
onMounted(() => {
loadStoredWidth();
if (resizableSidebar.value && widthResizer.value) {
widthResizer.value.addEventListener("pointerdown", (e) => {
e.preventDefault();
if (e.button == 2) {
createContextMenu(e, menuItems);
return
};
document.body.style.cursor = "ew-resize";
function handleMove(pointer: PointerEvent) {
if (resizableSidebar.value) {
console.log("moving");
console.log("pointer:", pointer);
console.log("width:", resizableSidebar.value.style.width);
let delta = 0;
if (props.borderSides == 'right') {
delta = resizableSidebar.value.getBoundingClientRect().left;
console.log("delta:", delta);
resizableSidebar.value.style.width = `${pointer.clientX - delta}px`;
} else {
delta = resizableSidebar.value.getBoundingClientRect().right;
console.log("delta:", delta);
resizableSidebar.value.style.width = `${delta - pointer.clientX}px`;
}
}
}
document.addEventListener("pointermove", handleMove);
document.addEventListener("pointerup", () => {
console.log("pointer up");
document.removeEventListener("pointermove", handleMove);
console.log("removed pointermove event listener");
document.body.style.cursor = "";
if (resizableSidebar.value && props.localStorageName) {
localStorage.setItem(props.localStorageName, resizableSidebar.value.style.width);
}
}, { once: true });
});
}
});
onActivated(() => {
console.log("[res] activated");
loadStoredWidth();
});
function loadStoredWidth() {
if (props.localStorageName) {
const storedWidthValue = localStorage.getItem(props.localStorageName);
if (storedWidthValue) {
storedWidth.value = parseInt(storedWidthValue) || undefined;
console.log("[res] loaded stored width");
}
}
}
</script>
<style>
.resizable-sidebar > * {
box-sizing: border-box;
}
.resizable-sidebar {
display: flex;
background: var(--optional-channel-list-background);
background-color: var(--sidebar-background-color);
height: 100%;
flex: 0 0 auto;
}
.width-resizer {
width: .5rem;
cursor: col-resize;
position: absolute;
height: 100%;
}
.width-resizer-bar {
display: flex;
justify-content: center;
position: relative;
height: 100%;
width: 1px;
background-color: var(--padding-color);
}
.sidebar-content {
width: 100%;
padding-left: .25em;
padding-right: .25em;
}
.sidebar-content > :first-child {
width: 100%;
height: 100%;
overflow-y: scroll;
overflow-x: hidden;
}
</style>

View file

@ -224,6 +224,8 @@ function createDropdown() {
background: var(--optional-sidebar-background);
background-color: var(--sidebar-background-color);
border-right: 1px solid var(--padding-color);
}
.left-column-segment {
@ -243,16 +245,6 @@ function createDropdown() {
gap: var(--sidebar-icon-gap);
}
#middle-left-column {
padding-left: .25em;
padding-right: .25em;
border-right: 1px solid var(--padding-color);
min-width: 13em;
max-width: 13em;
overflow-y: scroll;
overflow-x: hidden;
}
#home-button {
height: var(--sidebar-icon-width);
}

View file

@ -1,25 +1,33 @@
<template>
<NuxtLayout name="client">
<div id="middle-left-column" class="main-grid-row">
<div id="server-name-container">
<span id="server-name">{{ server?.name }}</span>
<button id="server-settings-button" @click="toggleGuildSettings">
<Icon id="server-settings-icon" name="lucide:chevron-down" />
</button>
<GuildOptionsMenu v-if="showGuildSettings" />
<ResizableSidebar
width="14rem" min-width="8rem" max-width="30rem"
border-sides="right" local-storage-name="middleLeftColumn">
<div id="middle-left-column" class="main-grid-row">
<div id="server-name-container">
<span id="server-name" :title="server?.name">{{ server?.name }}</span>
<button id="server-settings-button" @click="toggleGuildSettings">
<Icon id="server-settings-icon" name="lucide:chevron-down" />
</button>
<GuildOptionsMenu v-if="showGuildSettings" />
</div>
<div id="channels-list">
<ChannelEntry v-for="channel of channels" :name="channel.name"
:uuid="channel.uuid" :current-uuid="(route.params.channelId as string)"
:href="`/servers/${route.params.serverId}/channels/${channel.uuid}`" />
</div>
</div>
<div id="channels-list">
<ChannelEntry v-for="channel of channels" :name="channel.name"
:uuid="channel.uuid" :current-uuid="(route.params.channelId as string)"
:href="`/servers/${route.params.serverId}/channels/${channel.uuid}`" />
</div>
</div>
</ResizableSidebar>
<MessageArea :channel-url="channelUrlPath" />
<div id="members-container">
<div id="members-list">
<MemberEntry v-for="member of members" :member="member" tabindex="0"/>
<ResizableSidebar
width="14rem" min-width="5.5rem" max-width="30rem"
border-sides="left" local-storage-name="membersListWidth">
<div id="members-container">
<div id="members-list">
<MemberEntry v-for="member of members" :member="member" tabindex="0"/>
</div>
</div>
</div>
</ResizableSidebar>
</NuxtLayout>
</template>
@ -27,7 +35,8 @@
import ChannelEntry from "~/components/Guild/ChannelEntry.vue";
import GuildOptionsMenu from "~/components/Guild/GuildOptionsMenu.vue";
import MemberEntry from "~/components/Guild/MemberEntry.vue";
import type { ChannelResponse, GuildMemberResponse, GuildResponse, MessageResponse } from "~/types/interfaces";
import ResizableSidebar from "~/components/UserInterface/ResizableSidebar.vue";
import type { ChannelResponse, GuildMemberResponse, GuildResponse } from "~/types/interfaces";
const route = useRoute();
@ -53,14 +62,18 @@ onMounted(async () => {
console.log("mounting");
const guildUrl = `guilds/${route.params.serverId}`;
server.value = await fetchWithApi(guildUrl);
console.log("fetched guild");
await setArrayVariables();
console.log("set array variables");
});
onActivated(async () => {
console.log("activating");
const guildUrl = `guilds/${route.params.serverId}`;
server.value = await fetchWithApi(guildUrl);
console.log("fetched guild");
await setArrayVariables();
console.log("set array variables");
});
async function setArrayVariables() {
@ -87,18 +100,7 @@ function handleMemberClick(member: GuildMemberResponse) {
</script>
<style>
#middle-left-column {
padding-left: .5em;
padding-right: .5em;
border-right: 1px solid var(--padding-color);
background: var(--optional-channel-list-background);
background-color: var(--sidebar-background-color);
}
#members-container {
min-width: 15rem;
max-width: 15rem;
border-left: 1px solid var(--padding-color);
background: var(--optional-member-list-background);
}
@ -128,12 +130,14 @@ function handleMemberClick(member: GuildMemberResponse) {
display: flex;
flex-direction: column;
gap: .5em;
text-overflow: ellipsis;
}
.member-avatar {
height: 2.3em;
width: 2.3em;
border-radius: 50%;
min-width: 2.3em;
max-width: 2.3em;
min-width: 2.3em;
max-height: 2.3em;
}
.member-display-name {
@ -151,6 +155,8 @@ function handleMemberClick(member: GuildMemberResponse) {
#server-name {
font-size: 1.5em;
overflow: hidden;
text-overflow: ellipsis;
}
#server-settings-button {

View file

@ -2,15 +2,19 @@ import { render } from "vue";
import ContextMenu from "~/components/UserInterface/ContextMenu.vue";
import type { ContextMenuItem } from "~/types/interfaces";
export default (e: MouseEvent, menuItems: ContextMenuItem[]) => {
export default (e: PointerEvent | MouseEvent, menuItems: ContextMenuItem[]) => {
console.log("Rendering new context menu");
const menuContainer = document.createElement("div");
console.log("hello");
menuContainer.id = "context-menu";
document.body.appendChild(menuContainer);
console.log("pointer x:", e.clientX);
console.log("pointer y:", e.clientY);
console.log("menu items:", menuItems);
const contextMenu = h(ContextMenu, {
menuItems,
cursorX: e.clientX,
cursorY: e.clientY
pointerX: e.clientX,
pointerY: e.clientY
});
render(contextMenu, menuContainer);
console.log("Rendered");