guild-settings #35
12 changed files with 573 additions and 76 deletions
7
app.vue
7
app.vue
|
@ -25,6 +25,13 @@ onMounted(() => {
|
||||||
if (e.target instanceof HTMLElement && e.target.classList.contains("message-text") && e.target.contentEditable) {
|
if (e.target instanceof HTMLElement && e.target.classList.contains("message-text") && e.target.contentEditable) {
|
||||||
e.target.contentEditable = "false";
|
e.target.contentEditable = "false";
|
||||||
}
|
}
|
||||||
|
const destroyOnClick = document.getElementsByClassName("destroy-on-click");
|
||||||
|
for (const element of destroyOnClick) {
|
||||||
|
const closest = (e.target as HTMLElement).closest(".destroy-on-click");
|
||||||
|
if (element != closest) {
|
||||||
|
unrender(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
document.addEventListener("keyup", (e) => {
|
document.addEventListener("keyup", (e) => {
|
||||||
const messageReply = document.getElementById("message-reply") as HTMLDivElement;
|
const messageReply = document.getElementById("message-reply") as HTMLDivElement;
|
||||||
|
|
13
components/Banner.vue
Normal file
13
components/Banner.vue
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
46
components/Dropdown.vue
Normal file
46
components/Dropdown.vue
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<template>
|
||||||
|
<div class="dropdown-body">
|
||||||
|
<div v-for="option of props.options" class="dropdown-option">
|
||||||
|
<button class="dropdown-button" :data-value="option.value" @click.prevent="option.callback" tabindex="0">{{ option.name }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DropdownOption } from '~/types/interfaces';
|
||||||
|
|
||||||
|
const props = defineProps<{ options: DropdownOption[] }>();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.dropdown-body {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
left: 4dvw;
|
||||||
|
bottom: 4dvh;
|
||||||
|
background-color: var(--chat-background-color);
|
||||||
|
width: 8rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
border: .09rem solid rgb(70, 70, 70);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-button {
|
||||||
|
padding-top: .5dvh;
|
||||||
|
padding-bottom: .5dvh;
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: transparent;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-button:hover {
|
||||||
|
background-color: var(--padding-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
59
components/GuildOptionsMenu.vue
Normal file
59
components/GuildOptionsMenu.vue
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<div id="guild-options-menu" class="destroy-on-click">
|
||||||
|
<div v-for="setting of settings" class="guild-option" tabindex="0">
|
||||||
|
<button class="guild-option-button" @click="setting.action" tabindex="0">{{ setting.name }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { render } from 'vue';
|
||||||
|
import InviteModal from './InviteModal.vue';
|
||||||
|
|
||||||
|
const settings = [
|
||||||
|
{ name: "Invite", icon: "lucide:letter", action: openInviteModal }
|
||||||
|
]
|
||||||
|
|
||||||
|
function openInviteModal() {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
const guildId = useRoute().params.serverId as string;
|
||||||
|
console.log("guild id:", guildId);
|
||||||
|
const inviteModal = h(InviteModal, { guildId });
|
||||||
|
document.body.appendChild(div);
|
||||||
|
render(inviteModal, div);
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#guild-options-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--chat-background-color);
|
||||||
|
top: 8dvh;
|
||||||
|
z-index: 10;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guild-option {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 2em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.guild-option:hover {
|
||||||
|
background-color: var(--padding-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.guild-option-button {
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--main-text-color);
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
70
components/InviteModal.vue
Normal file
70
components/InviteModal.vue
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<template>
|
||||||
|
<Modal v-bind="props" :title="props.title || 'Create an invite'">
|
||||||
|
<div v-if="invite" id="invite-body">
|
||||||
|
<div id="invite-label">{{ invite }}</div>
|
||||||
|
<div id="invite-buttons">
|
||||||
|
<Button text="Copy as link" variant="neutral" :callback="() => copyInvite('link')" />
|
||||||
|
<Button text="Copy as code" variant="neutral" :callback="() => copyInvite('code')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<Button text="Generate Invite" variant="normal" :callback="generateInvite">Generate Invite</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { InviteResponse, ModalProps } from '~/types/interfaces';
|
||||||
|
import Button from './UserInterface/Button.vue';
|
||||||
|
|
||||||
|
const props = defineProps<ModalProps & { guildId: string }>();
|
||||||
|
|
||||||
|
const invite = ref<string>();
|
||||||
|
|
||||||
|
async function generateInvite(): Promise<void> {
|
||||||
|
const chars = "ABCDEFGHIJKLMNOQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890"
|
||||||
|
let randCode = "";
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
randCode += chars[Math.floor(Math.random() * chars.length)];
|
||||||
|
}
|
||||||
|
const createdInvite: InviteResponse | undefined = await fetchWithApi(
|
||||||
|
`/guilds/${props.guildId}/invites`,
|
||||||
|
{ method: "POST", body: { custom_id: randCode } }
|
||||||
|
);
|
||||||
|
|
||||||
|
invite.value = createdInvite?.id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyInvite(type: "link" | "code") {
|
||||||
|
if (!invite.value) return;
|
||||||
|
|
||||||
|
if (type == "link") {
|
||||||
|
const inviteUrl = URL.parse(`invite/${invite.value}`, `${window.location.protocol}//${window.location.host}`);
|
||||||
|
if (inviteUrl) {
|
||||||
|
navigator.clipboard.writeText(inviteUrl.href);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigator.clipboard.writeText(invite.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
#invite-body, #invite-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#invite-body {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#invite-label {
|
||||||
|
text-align: center;
|
||||||
|
color: aquamarine;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
84
components/Modal.vue
Normal file
84
components/Modal.vue
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
<template>
|
||||||
|
<dialog ref="dialog" class="modal" :class="props.obscure ? 'modal-obscure' : 'modal-regular'">
|
||||||
|
<span class="modal-exit-button-container" style="position: absolute; right: 2em; top: .2em; width: .5em; height: .5em;">
|
||||||
|
<Button text="X" variant="neutral" :callback="() => dialog?.remove()" />
|
||||||
|
</span>
|
||||||
|
<div class="modal-content">
|
||||||
|
<h1 class="modal-title">{{ title }}</h1>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ModalProps } from '~/types/interfaces';
|
||||||
|
import Button from './UserInterface/Button.vue';
|
||||||
|
|
||||||
|
const props = defineProps<ModalProps>();
|
||||||
|
const dialog = ref<HTMLDialogElement>();
|
||||||
|
|
||||||
|
console.log("props:", props);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (dialog.value) {
|
||||||
|
dialog.value.showModal();
|
||||||
|
if (props.onClose) {
|
||||||
|
dialog.value.addEventListener("close", props.onClose);
|
||||||
|
}
|
||||||
|
if (props.onCancel) {
|
||||||
|
dialog.value.addEventListener("cancel", props.onCancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.modal {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1em;
|
||||||
|
opacity: 100%;
|
||||||
|
padding: 1%;
|
||||||
|
background-color: var(--sidebar-highlighted-background-color);
|
||||||
|
color: var(--text-color);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-regular::backdrop {
|
||||||
|
background-color: var(--chat-background-color);
|
||||||
|
opacity: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-obscure::backdrop {
|
||||||
|
background-color: var(--chat-background-color);
|
||||||
|
opacity: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-top-container {
|
||||||
|
position: fixed;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1em;
|
||||||
|
margin: 1em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,40 +0,0 @@
|
||||||
<template>
|
|
||||||
<div id="invite-popup">
|
|
||||||
<div v-if="invite">
|
|
||||||
<p>{{ invite }}</p>
|
|
||||||
<button @click="copyInvite">Copy Link</button>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<button @click="generateInvite">Generate Invite</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { InviteResponse } from '~/types/interfaces';
|
|
||||||
|
|
||||||
|
|
||||||
const invite = ref<string>();
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
async function generateInvite(): Promise<void> {
|
|
||||||
const createdInvite: InviteResponse | undefined = await fetchWithApi(
|
|
||||||
`/guilds/${route.params.serverId}/invites`,
|
|
||||||
{ method: "POST", body: { custom_id: "oijewfoiewf" } }
|
|
||||||
);
|
|
||||||
|
|
||||||
invite.value = createdInvite?.id;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyInvite() {
|
|
||||||
const inviteUrl = URL.parse(`invite/${invite.value}`, `${window.location.protocol}//${window.location.host}`);
|
|
||||||
navigator.clipboard.writeText(inviteUrl!.href);
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -53,6 +53,18 @@ export const useApi = () => {
|
||||||
return await fetchWithApi(`/channels/${channelId}/messages/${messageId}`);
|
return await fetchWithApi(`/channels/${channelId}/messages/${messageId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createGuild(name: string): Promise<GuildResponse | undefined> {
|
||||||
|
return await fetchWithApi(`/guilds`, { method: "POST", body: { name } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function joinGuild(invite: string): Promise<GuildResponse> {
|
||||||
|
return await fetchWithApi(`/invites/${invite}`, { method: "POST" }) as GuildResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createChannel(guildId: string, name: string, description?: string): Promise<void> {
|
||||||
|
return await fetchWithApi(`/guilds/${guildId}/channels`, { method: "POST", body: { name, description } });
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchInstanceStats(apiBase: string): Promise<StatsResponse> {
|
async function fetchInstanceStats(apiBase: string): Promise<StatsResponse> {
|
||||||
return await $fetch(`${apiBase}/stats`, { method: "GET" });
|
return await $fetch(`${apiBase}/stats`, { method: "GET" });
|
||||||
}
|
}
|
||||||
|
@ -76,6 +88,9 @@ export const useApi = () => {
|
||||||
removeFriend,
|
removeFriend,
|
||||||
fetchMessages,
|
fetchMessages,
|
||||||
fetchMessage,
|
fetchMessage,
|
||||||
|
createGuild,
|
||||||
|
joinGuild,
|
||||||
|
createChannel,
|
||||||
fetchInstanceStats,
|
fetchInstanceStats,
|
||||||
sendVerificationEmail
|
sendVerificationEmail
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,18 +10,27 @@
|
||||||
</div>
|
</div>
|
||||||
<div id = "page-content">
|
<div id = "page-content">
|
||||||
<div id="left-column">
|
<div id="left-column">
|
||||||
<NuxtLink id="home-button" href="/me">
|
<div id="left-column-top">
|
||||||
<img class="sidebar-icon" src="/public/icon.svg"/>
|
<NuxtLink id="home-button" href="/me">
|
||||||
</NuxtLink>
|
<img class="sidebar-icon" src="/public/icon.svg"/>
|
||||||
<div id="servers-list">
|
</NuxtLink>
|
||||||
<NuxtLink v-for="guild of guilds" :href="`/servers/${guild.uuid}`">
|
<div id="servers-list">
|
||||||
<img v-if="guild.icon" class="sidebar-icon" :src="guild.icon" :alt="guild.name"/>
|
<NuxtLink v-for="guild of guilds" :href="`/servers/${guild.uuid}`">
|
||||||
<Icon v-else name="lucide:server" class="sidebar-icon white" :alt="guild.name" />
|
<img v-if="guild.icon" class="sidebar-icon" :src="guild.icon" :alt="guild.name"/>
|
||||||
|
<Icon v-else name="lucide:server" class="sidebar-icon white" :alt="guild.name" />
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="left-column-bottom">
|
||||||
|
<div ref="createButtonContainer">
|
||||||
|
<button id="create-button" @click.prevent="createDropdown">
|
||||||
|
<Icon id="create-icon" name="lucide:square-plus" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<NuxtLink id="settings-menu" href="/settings">
|
||||||
|
<Icon name="lucide:settings" class="sidebar-icon" alt="Settings menu" />
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<NuxtLink id="settings-menu" href="/settings">
|
|
||||||
<Icon name="lucide:settings" class="sidebar-icon" alt="Settings menu" />
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
</div>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@ -29,11 +38,165 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { render } from 'vue';
|
||||||
|
import Dropdown from '~/components/Dropdown.vue';
|
||||||
|
import Modal from '~/components/Modal.vue';
|
||||||
|
import Button from '~/components/UserInterface/Button.vue';
|
||||||
import type { GuildResponse } from '~/types/interfaces';
|
import type { GuildResponse } from '~/types/interfaces';
|
||||||
|
|
||||||
const loading = useState("loading", () => false);
|
const loading = useState("loading", () => false);
|
||||||
|
|
||||||
|
const createButtonContainer = ref<HTMLButtonElement>();
|
||||||
|
|
||||||
|
const api = useApi();
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{ name: "Join", value: "join", callback: async () => {
|
||||||
|
console.log("join guild!");
|
||||||
|
const div = document.createElement("div");
|
||||||
|
const guildJoinModal = h(Modal, {
|
||||||
|
title: "Join Guild",
|
||||||
|
id: "guild-join-modal",
|
||||||
|
onClose: () => {
|
||||||
|
unrender(div);
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
unrender(div);
|
||||||
|
},
|
||||||
|
style: "height: 20dvh; width: 15dvw"
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h("input", {
|
||||||
|
id: "guild-invite-input",
|
||||||
|
type: "text",
|
||||||
|
placeholder: "oyqICZ",
|
||||||
|
}),
|
||||||
|
h(Button, {
|
||||||
|
text: "Join",
|
||||||
|
variant: "normal",
|
||||||
|
callback: async () => {
|
||||||
|
const input = document.getElementById("guild-invite-input") as HTMLInputElement;
|
||||||
|
const invite = input.value;
|
||||||
|
if (invite.length == 6) {
|
||||||
|
try {
|
||||||
|
const joinedGuild = await api.joinGuild(invite);
|
||||||
|
guilds?.push(joinedGuild);
|
||||||
|
return await navigateTo(`/servers/${joinedGuild.uuid}`);
|
||||||
|
} catch (error) {
|
||||||
|
alert(`Couldn't use invite: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
document.body.appendChild(div);
|
||||||
|
render(guildJoinModal, div);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ name: "Create", value: "create", callback: async () => {
|
||||||
|
console.log("create guild");
|
||||||
|
const user = await useAuth().getUser();
|
||||||
|
const div = document.createElement("div");
|
||||||
|
const guildCreateModal = h(Modal, {
|
||||||
|
title: "Create a Guild",
|
||||||
|
id: "guild-join-modal",
|
||||||
|
onClose: () => {
|
||||||
|
unrender(div);
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
unrender(div);
|
||||||
|
},
|
||||||
|
style: "height: 20dvh; width: 15dvw;"
|
||||||
|
},
|
||||||
|
[
|
||||||
|
h("input", {
|
||||||
|
id: "guild-name-input",
|
||||||
|
type: "text",
|
||||||
|
placeholder: `${user?.display_name || user?.username}'s Awesome Bouncy Castle'`,
|
||||||
|
style: "width: 100%"
|
||||||
|
}),
|
||||||
|
h(Button, {
|
||||||
|
text: "Create!",
|
||||||
|
variant: "normal",
|
||||||
|
callback: async () => {
|
||||||
|
const input = document.getElementById("guild-name-input") as HTMLInputElement;
|
||||||
|
const name = input.value;
|
||||||
|
try {
|
||||||
|
const guild = (await api.createGuild(name)) as GuildResponse;
|
||||||
|
await api.createChannel(guild.uuid, "general");
|
||||||
|
} catch (error) {
|
||||||
|
alert(`Couldn't create guild: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
document.body.appendChild(div);
|
||||||
|
render(guildCreateModal, div);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const guilds: GuildResponse[] | undefined = await fetchWithApi("/me/guilds");
|
const guilds: GuildResponse[] | undefined = await fetchWithApi("/me/guilds");
|
||||||
|
|
||||||
|
//const servers = await fetchWithApi("/servers") as { uuid: string, name: string, description: string }[];
|
||||||
|
//console.log("servers:", servers);
|
||||||
|
const members = [
|
||||||
|
{
|
||||||
|
id: "3287484395",
|
||||||
|
displayName: "SauceyRed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3287484395",
|
||||||
|
displayName: "SauceyRed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3287484395",
|
||||||
|
displayName: "SauceyRed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3287484395",
|
||||||
|
displayName: "SauceyRed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3287484395",
|
||||||
|
displayName: "SauceyRed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3287484395",
|
||||||
|
displayName: "SauceyRed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3287484395",
|
||||||
|
displayName: "SauceyRed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3287484395",
|
||||||
|
displayName: "SauceyRed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "3287484395",
|
||||||
|
displayName: "SauceyRed"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function createDropdown() {
|
||||||
|
const dropdown = h(Dropdown, { options });
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.classList.add("dropdown", "destroy-on-click");
|
||||||
|
if (createButtonContainer.value) {
|
||||||
|
createButtonContainer.value.appendChild(div);
|
||||||
|
} else {
|
||||||
|
document.body.appendChild(div);
|
||||||
|
}
|
||||||
|
render(dropdown, div);
|
||||||
|
div.addEventListener("keyup", (e) => {
|
||||||
|
if (e.key == "Escape") {
|
||||||
|
unrender(div);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
div.focus();
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -80,6 +243,8 @@ const guilds: GuildResponse[] | undefined = await fetchWithApi("/me/guilds");
|
||||||
#left-column {
|
#left-column {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
gap: .75em;
|
gap: .75em;
|
||||||
padding-left: .25em;
|
padding-left: .25em;
|
||||||
padding-right: .25em;
|
padding-right: .25em;
|
||||||
|
@ -89,6 +254,31 @@ const guilds: GuildResponse[] | undefined = await fetchWithApi("/me/guilds");
|
||||||
background-color: var(--sidebar-background-color);
|
background-color: var(--sidebar-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#left-column-top, #left-column-bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5dvh;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
#left-column-bottom {
|
||||||
|
padding-top: 1dvh;
|
||||||
|
border-top: 1px solid var(--padding-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#middle-left-column {
|
||||||
|
padding-left: 1dvw;
|
||||||
|
padding-right: 1dvw;
|
||||||
|
border-right: 1px solid var(--padding-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#home-button {
|
||||||
|
border-bottom: 1px solid var(--padding-color);
|
||||||
|
padding-bottom: 1dvh;
|
||||||
|
}
|
||||||
|
|
||||||
#servers-list {
|
#servers-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -97,6 +287,20 @@ const guilds: GuildResponse[] | undefined = await fetchWithApi("/me/guilds");
|
||||||
padding-top: .5em;
|
padding-top: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#create-button {
|
||||||
|
color: var(--primary-color);
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 2rem;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#create-icon {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
#middle-left-column {
|
#middle-left-column {
|
||||||
padding-left: .25em;
|
padding-left: .25em;
|
||||||
padding-right: .25em;
|
padding-right: .25em;
|
||||||
|
@ -120,9 +324,6 @@ const guilds: GuildResponse[] | undefined = await fetchWithApi("/me/guilds");
|
||||||
}
|
}
|
||||||
|
|
||||||
#settings-menu {
|
#settings-menu {
|
||||||
position: absolute;
|
|
||||||
bottom: .25em;
|
|
||||||
|
|
||||||
color: var(--primary-color)
|
color: var(--primary-color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<NuxtLayout name="client">
|
<NuxtLayout name="client">
|
||||||
<div id="middle-left-column" class="main-grid-row">
|
<div id="middle-left-column" class="main-grid-row">
|
||||||
<div id="server-title">
|
<div id="server-name-container">
|
||||||
<h3>
|
<span id="server-name">{{ server?.name }}</span>
|
||||||
{{ server?.name }}
|
<button id="server-settings-button" @click="toggleGuildSettings">
|
||||||
<span>
|
<Icon id="server-settings-icon" name="lucide:chevron-down" />
|
||||||
<button @click="showGuildSettings">
|
</button>
|
||||||
<Icon name="lucide:settings" />
|
<GuildOptionsMenu v-if="showGuildSettings" />
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<button @click="toggleInvitePopup">
|
|
||||||
<Icon name="lucide:share-2" />
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
<InvitePopup v-if="showInvitePopup" />
|
|
||||||
</h3>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="channels-list">
|
<div id="channels-list">
|
||||||
<ChannelEntry v-for="channel of channels" :name="channel.name"
|
<ChannelEntry v-for="channel of channels" :name="channel.name"
|
||||||
|
@ -45,7 +36,10 @@ const server = ref<GuildResponse | undefined>();
|
||||||
const channels = ref<ChannelResponse[] | undefined>();
|
const channels = ref<ChannelResponse[] | undefined>();
|
||||||
const channel = ref<ChannelResponse | undefined>();
|
const channel = ref<ChannelResponse | undefined>();
|
||||||
|
|
||||||
|
const members = ref<GuildMemberResponse[]>();
|
||||||
|
|
||||||
const showInvitePopup = ref(false);
|
const showInvitePopup = ref(false);
|
||||||
|
const showGuildSettings = ref(false);
|
||||||
|
|
||||||
import type { ChannelResponse, GuildMemberResponse, GuildResponse, MessageResponse } from "~/types/interfaces";
|
import type { ChannelResponse, GuildMemberResponse, GuildResponse, MessageResponse } from "~/types/interfaces";
|
||||||
|
|
||||||
|
@ -53,23 +47,34 @@ import type { ChannelResponse, GuildMemberResponse, GuildResponse, MessageRespon
|
||||||
//console.log("channelid: servers:", servers);
|
//console.log("channelid: servers:", servers);
|
||||||
|
|
||||||
const { fetchMembers } = useApi();
|
const { fetchMembers } = useApi();
|
||||||
const members = await fetchMembers(route.params.serverId as string);
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
console.log("channelid: set loading to true");
|
console.log("mounting");
|
||||||
const guildUrl = `guilds/${route.params.serverId}`;
|
const guildUrl = `guilds/${route.params.serverId}`;
|
||||||
server.value = await fetchWithApi(guildUrl);
|
server.value = await fetchWithApi(guildUrl);
|
||||||
|
await setArrayVariables();
|
||||||
|
});
|
||||||
|
|
||||||
|
onActivated(async () => {
|
||||||
|
console.log("activating");
|
||||||
|
const guildUrl = `guilds/${route.params.serverId}`;
|
||||||
|
server.value = await fetchWithApi(guildUrl);
|
||||||
|
await setArrayVariables();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function setArrayVariables() {
|
||||||
|
members.value = await fetchMembers(route.params.serverId as string);
|
||||||
|
const guildUrl = `guilds/${route.params.serverId}`;
|
||||||
channels.value = await fetchWithApi(`${guildUrl}/channels`);
|
channels.value = await fetchWithApi(`${guildUrl}/channels`);
|
||||||
console.log("channels:", channels.value);
|
console.log("channels:", channels.value);
|
||||||
channel.value = await fetchWithApi(`/channels/${route.params.channelId}`);
|
channel.value = await fetchWithApi(`/channels/${route.params.channelId}`);
|
||||||
console.log("channel:", channel.value);
|
console.log("channel:", channel.value);
|
||||||
|
}
|
||||||
|
|
||||||
console.log("channelid: channel:", channel);
|
function toggleGuildSettings(e: Event) {
|
||||||
console.log("channelid: set loading to false");
|
e.preventDefault();
|
||||||
});
|
showGuildSettings.value = !showGuildSettings.value;
|
||||||
|
}
|
||||||
function showGuildSettings() { }
|
|
||||||
|
|
||||||
function toggleInvitePopup(e: Event) {
|
function toggleInvitePopup(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -81,7 +86,6 @@ function handleMemberClick(member: GuildMemberResponse) {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
#middle-left-column {
|
#middle-left-column {
|
||||||
padding-left: .5em;
|
padding-left: .5em;
|
||||||
padding-right: .5em;
|
padding-right: .5em;
|
||||||
|
@ -136,4 +140,23 @@ function handleMemberClick(member: GuildMemberResponse) {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#server-name-container {
|
||||||
|
padding-top: 3dvh;
|
||||||
|
padding-bottom: 3dvh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#server-name {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#server-settings-button {
|
||||||
|
background-color: transparent;
|
||||||
|
font-size: 1em;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -86,6 +86,19 @@ export interface ScrollPosition {
|
||||||
offsetLeft: number
|
offsetLeft: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DropdownOption {
|
||||||
|
name: string,
|
||||||
|
value: string | number,
|
||||||
|
callback: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ModalProps {
|
||||||
|
title?: string,
|
||||||
|
obscure?: boolean,
|
||||||
|
onClose?: () => void,
|
||||||
|
onCancel?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
export interface ContextMenuItem {
|
export interface ContextMenuItem {
|
||||||
name: string,
|
name: string,
|
||||||
callback: (...args: any[]) => any;
|
callback: (...args: any[]) => any;
|
||||||
|
|
6
utils/unrender.ts
Normal file
6
utils/unrender.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { render } from "vue";
|
||||||
|
|
||||||
|
export default (div: Element) => {
|
||||||
|
render(null, div);
|
||||||
|
div.remove();
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue