Refactor the client to implement a channel navbar #76
4 changed files with 227 additions and 76 deletions
124
components/UserInterface/Navbar.vue
Normal file
124
components/UserInterface/Navbar.vue
Normal file
|
@ -0,0 +1,124 @@
|
|||
<template>
|
||||
<div id="navbar">
|
||||
<div v-for="entry of props.clientItems" id="navbar-left">
|
||||
<button class="navbar-item" :title="entry.title"
|
||||
@click.prevent="entry.callback()">
|
||||
<Icon :name="entry.icon" class="navbar-item-icon" />
|
||||
</button>
|
||||
</div>
|
||||
<div id="navbar-middle">
|
||||
<NuxtImg v-if="props.contextIcon"
|
||||
class="context-icon"
|
||||
:alt="props.contextName"
|
||||
:src="props.contextIcon" />
|
||||
<DefaultIcon v-else-if="props.contextName && props.guildUuid"
|
||||
class="context-icon"
|
||||
:alt="props.contextName"
|
||||
:name="props.contextName" :seed="props.guildUuid"/>
|
||||
|
||||
<div class="context-title">
|
||||
{{ props.contextName }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="entry of props.channelItems" id="navbar-right">
|
||||
<button class="navbar-item" :title="entry.title"
|
||||
@click.prevent="entry.callback()">
|
||||
<Icon :name="entry.icon" class="navbar-item-icon" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { NavbarInterface, NavbarItem } from '~/types/interfaces';
|
||||
|
||||
const props = defineProps<NavbarInterface>();
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#navbar {
|
||||
--navbar-height: 5dvh;
|
||||
--navbar-icon-size: 3dvh;
|
||||
--navbar-gap: calc(3dvh * .2);
|
||||
--side-margins: calc(.6dvw + .35dvh); /* try to make it reasonable at any aspect ratio */
|
||||
twig marked this conversation as resolved
Outdated
|
||||
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
min-height: var(--navbar-height);
|
||||
max-height: var(--navbar-height);
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
background: var(--optional-topbar-background);
|
||||
background-color: var(--topbar-background-color);
|
||||
border-bottom: 1px solid var(--padding-color);
|
||||
}
|
||||
|
||||
#navbar-left,
|
||||
#navbar-middle,
|
||||
#navbar-right {
|
||||
top: 0;
|
||||
height: var(--navbar-height);
|
||||
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--navbar-gap);
|
||||
}
|
||||
|
||||
#navbar-left {
|
||||
left: var(--side-margins);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
#navbar-middle {
|
||||
max-width: 50dvw;
|
||||
}
|
||||
|
||||
#navbar-right {
|
||||
right: var(--side-margins);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.context-icon {
|
||||
height: calc(var(--navbar-height) * 0.7);
|
||||
width: calc(var(--navbar-height) * 0.7);
|
||||
border-radius: var(--guild-icon-radius);
|
||||
}
|
||||
|
||||
.context-title {
|
||||
min-height: var(--navbar-height);
|
||||
max-height: var(--navbar-height);
|
||||
|
||||
font-weight: 500;
|
||||
font-size: calc(var(--navbar-height) * .5);
|
||||
line-height: calc(var(--navbar-height) * .9);
|
||||
}
|
||||
|
||||
.navbar-item {
|
||||
color: var(--reply-text-color);
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
|
||||
transition: color 300ms;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: var(--navbar-icon-size);
|
||||
}
|
||||
|
||||
.navbar-item:hover {
|
||||
color: var(--primary-highlighted-color);
|
||||
}
|
||||
|
||||
.navbar-item-icon {
|
||||
width: var(--navbar-icon-size);
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -1,13 +1,7 @@
|
|||
<template>
|
||||
<Loading v-show="loading" />
|
||||
<div :class="{ hidden: loading, visible: !loading }" id="client-root">
|
||||
<div id="homebar">
|
||||
<div class="homebar-item">
|
||||
<marquee>
|
||||
gorb!!!!!
|
||||
</marquee>
|
||||
</div>
|
||||
</div>
|
||||
<Navbar id="navbar" v-bind="navbar" />
|
||||
<div id="page-content">
|
||||
<div id="left-column">
|
||||
<div class="left-column-segment">
|
||||
|
@ -52,14 +46,16 @@ import DefaultIcon from '~/components/DefaultIcon.vue';
|
|||
import GuildDropdown from '~/components/Guild/GuildDropdown.vue';
|
||||
import Loading from '~/components/Popups/Loading.vue';
|
||||
import Button from '~/components/UserInterface/Button.vue';
|
||||
import Navbar from '~/components/UserInterface/Navbar.vue';
|
||||
import VerticalSpacer from '~/components/UserInterface/VerticalSpacer.vue';
|
||||
import type { GuildResponse } from '~/types/interfaces';
|
||||
import type { GuildResponse, NavbarInterface, NavbarItem } from '~/types/interfaces';
|
||||
|
||||
definePageMeta({
|
||||
keepalive: true
|
||||
});
|
||||
|
||||
const loading = useState("loading", () => false);
|
||||
const navbar = useState<NavbarInterface>("navbar")
|
||||
|
||||
const createButtonContainer = ref<HTMLButtonElement>();
|
||||
twig marked this conversation as resolved
Outdated
sauceyred
commented
This should be an anchor element, not a button This should be an anchor element, not a button
twig
commented
disagree, i'd like to put an inbox here, that would be a popup, not a link disagree, i'd like to put an inbox here, that would be a popup, not a link
sauceyred
commented
Right but I think all clickable elements that lead you to an external site should be anchor elements. Here I'm specifically talking about the source code element. Right but I think all clickable elements that lead you to an external site should be anchor elements. Here I'm specifically talking about the source code element.
twig
commented
sure, i'm reworking it anyways sure, i'm reworking it anyways
|
||||
|
||||
|
@ -154,6 +150,23 @@ const options = [
|
|||
|
||||
const guilds = await api.fetchMyGuilds();
|
||||
|
||||
onMounted(() => {
|
||||
if (!navbar.value) {
|
||||
const helpItem = {
|
||||
title: "Source",
|
||||
icon: "lucide:code-xml",
|
||||
callback: () => { open("https://git.gorb.app/gorb/frontend") }
|
||||
} as NavbarItem
|
||||
|
||||
navbar.value = {
|
||||
clientItems: [
|
||||
helpItem
|
||||
],
|
||||
channelItems: [] // set by the channel
|
||||
} as NavbarInterface
|
||||
}
|
||||
})
|
||||
|
||||
function createDropdown() {
|
||||
const dropdown = h(GuildDropdown, { options });
|
||||
const div = document.createElement("div");
|
||||
|
@ -192,22 +205,6 @@ function createDropdown() {
|
|||
transition: opacity 500ms;
|
||||
}
|
||||
|
||||
#homebar {
|
||||
min-height: 4dvh;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
background: var(--optional-topbar-background);
|
||||
background-color: var(--topbar-background-color);
|
||||
border-bottom: 1px solid var(--padding-color);
|
||||
padding-left: 5dvw;
|
||||
padding-right: 5dvw;
|
||||
}
|
||||
|
||||
.homebar-item {
|
||||
width: 100dvw;
|
||||
}
|
||||
|
||||
#page-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -36,11 +36,12 @@ import ChannelEntry from "~/components/Guild/ChannelEntry.vue";
|
|||
import GuildOptionsMenu from "~/components/Guild/GuildOptionsMenu.vue";
|
||||
import MemberEntry from "~/components/Guild/MemberEntry.vue";
|
||||
import ResizableSidebar from "~/components/UserInterface/ResizableSidebar.vue";
|
||||
import type { ChannelResponse, GuildMemberResponse, GuildResponse } from "~/types/interfaces";
|
||||
import type { ChannelResponse, GuildMemberResponse, GuildResponse, NavbarInterface } from "~/types/interfaces";
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const loading = useState("loading");
|
||||
const navbar = useState<NavbarInterface>("navbar");
|
||||
|
||||
const channelUrlPath = `channels/${route.params.channelId}`;
|
||||
|
||||
|
@ -65,13 +66,18 @@ onMounted(async () => {
|
|||
console.log("fetched guild");
|
||||
await setArrayVariables();
|
||||
console.log("set array variables");
|
||||
|
||||
updateNavbar()
|
||||
});
|
||||
|
||||
onActivated(async () => {
|
||||
console.log("activating");
|
||||
updateNavbar()
|
||||
|
||||
const guildUrl = `guilds/${route.params.serverId}`;
|
||||
server.value = await fetchWithApi(guildUrl);
|
||||
console.log("fetched guild");
|
||||
|
||||
await setArrayVariables();
|
||||
console.log("set array variables");
|
||||
});
|
||||
|
@ -98,6 +104,14 @@ function toggleInvitePopup(e: Event) {
|
|||
|
||||
function handleMemberClick(member: GuildMemberResponse) {
|
||||
}
|
||||
|
||||
function updateNavbar() {
|
||||
if (server.value) {
|
||||
navbar.value.contextName = server.value.name
|
||||
navbar.value.contextIcon = server.value.icon ?? undefined
|
||||
navbar.value.guildUuid = server.value.uuid
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -121,3 +121,19 @@ export interface ContextMenuInterface {
|
|||
pointerY: number,
|
||||
items: ContextMenuItem[]
|
||||
}
|
||||
|
||||
export interface NavbarItem {
|
||||
title: string,
|
||||
icon: string,
|
||||
hasPing?: boolean, // whether to draw a "ping" icon or not
|
||||
callback: (...args: any[]) => any;
|
||||
}
|
||||
|
||||
export interface NavbarInterface {
|
||||
clientItems: NavbarItem[]
|
||||
channelItems: NavbarItem[] // search bar will require some changes
|
||||
contextName?: string
|
||||
contextIcon?: string
|
||||
guildUuid?: string
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue
Shouldn't these variable definitions be defined in theme files?
i suppose? they're really just there to make development easier, but they could be included in the layout theme files, if so, do we include all 4?
Yeah, don't see a reason not to