Browse Source

exported userMenu to new component

master
nicolas-arnaud 2 years ago
parent
commit
f6a13c7ba9
  1. 14
      back/src/chat/chat.controller.ts
  2. 17
      front/src/App.svelte
  3. 5
      front/src/components/Channels.svelte
  4. 260
      front/src/components/Chat.svelte
  5. 77
      front/src/components/Friends.svelte
  6. 174
      front/src/components/UsersMenu.svelte

14
back/src/chat/chat.controller.ts

@ -18,7 +18,7 @@ import { CreateChannelDto } from './dto/create-channel.dto'
import { IdDto, PasswordDto, MuteDto } from './dto/updateUser.dto'
import type User from 'src/users/entity/user.entity'
import type Channel from './entity/channel.entity'
import Channel from './entity/channel.entity'
import { Profile42 } from 'src/auth/42.decorator'
import { Profile } from 'passport-42'
import { IsNumberString, IsPositive } from 'class-validator'
@ -221,6 +221,18 @@ export class ChatController {
this.channelService.update(channel)
}
@Get(':id')
async getChannel (@Param('id', ParseIntPipe) id: number): Promise<Channel> {
const chan = await this.channelService.getFullChannel(id)
if (chan == null) {
throw new NotFoundException(`Channel #${id} not found`)
}
chan.users.forEach((u) => (u.socketKey = ''))
chan.admins.forEach((u) => (u.socketKey = ''))
chan.owner.socketKey = ''
return chan
}
@Get()
async getChannelsForUser (@Profile42() profile: Profile): Promise<Channel[]> {
const chan = await this.channelService.getChannelsForUser(+profile.id)

17
front/src/App.svelte

@ -44,7 +44,7 @@
function setAppState(newState: APPSTATE | string) {
if (newState === appState) return;
appState = newState;
history.pushState({ appState }, "", appState);
history.pushState({ appState: appState, prevState: window.location.pathname }, "", appState);
}
onMount(() => {
@ -130,7 +130,6 @@
on:keydown={() => setAppState(APPSTATE.CHANNELS)}
>
<Chat
{appState}
{setAppState}
bind:channel={selectedChannel}
on:view-profile={openIdProfile}
@ -155,9 +154,13 @@
<Leaderboard />
</div>
{/if}
{#if appState === APPSTATE.FRIENDS}
{#if appState == APPSTATE.FRIENDS}
<div on:click={resetAppState} on:keydown={resetAppState}>
<Friends />
<Friends
{setAppState}
on:view-profile={openIdProfile}
on:invite-to-game={pong.inviteToGame}
/>
</div>
{/if}
{#if appState === APPSTATE.HISTORY}
@ -180,10 +183,8 @@
{/if}
{#if appState === APPSTATE.PROFILE_ID}
<div
on:click={() =>
setAppState(APPSTATE.CHANNELS + "#" + selectedChannel.name)}
on:keydown={() =>
setAppState(APPSTATE.CHANNELS + "#" + selectedChannel.name)}
on:click={() => setAppState(history.state.prevState)}
on:keydown={() => setAppState(history.state.prevState)}
>
<Profile
{gamePlaying}

5
front/src/components/Channels.svelte

@ -16,8 +16,9 @@
isPrivate: boolean;
password: string;
owner: User;
banned: Array<User>;
muted: Array<User>;
admins: Array<User>;
banned: Array<Array<number>>;
muted: Array<Array<number>>;
isDM: boolean;
}
export interface ChatMessageServer {

260
front/src/components/Chat.svelte

@ -4,19 +4,20 @@
import { io, Socket } from "socket.io-client";
import { show_popup, content } from "./Alert/content";
import { APPSTATE } from "../App.svelte";
import { formatChannelNames, type ChannelsType, type ChatMessage, type ChatMessageServer } from "./Channels.svelte";
import type User from "./Profile.svelte";
import type { CreateChannelDto } from "./dtos/create-channel.dto";
import type { ChannelsType, ChatMessage, ChatMessageServer } from "./Channels.svelte";
import type { IdDto, MuteDto } from "./dtos/updateUser.dto";
import type { ConnectionDto } from "./dtos/connection.dto";
import type { CreateMessageDto } from "./dtos/create-message.dto";
import type { kickUserDto } from "./dtos/kickUser.dto";
import UsersMenu from "./UsersMenu.svelte";
</script>
<script lang="ts">
export let channel: ChannelsType;
export let messages: Array<ChatMessage> = [];
export let appState: string;
export let setAppState: (newState: APPSTATE | string) => void;
let socket: Socket;
@ -25,30 +26,10 @@
let blockedUsers: Array<User> = [];
let chatMembers: Array<User> = [];
async function getCurrentChannel() {
const res = await fetch(API_URL + "/channels", {
credentials: "include",
mode: "cors",
});
if (res.ok) {
const newChannels: Array<ChannelsType> = await res.json();
await formatChannelNames(newChannels);
newChannels.forEach((newChannel) => {
const urlSplit = appState.split("#", 2)
if (urlSplit.length > 1) {
const currentChannelName = appState.split("#", 2)[1];
if (newChannel.name === currentChannelName) {
channel = newChannel;
}
}
});
}
}
onMount(async () => {
socket = io(API_URL);
socket.connect();
await getCurrentChannel();
await getFullChannel();
if (!channel) setAppState(APPSTATE.CHANNELS);
if (!channel.password) {
const data: ConnectionDto = {
@ -107,6 +88,18 @@
console.log("Try to join channel: ", $store.ftId, channel.id, $content);
});
const dispatch = createEventDispatcher();
async function getFullChannel() {
const response = await fetch(API_URL + "/channels/" + channel.id, {
credentials: "include",
mode: "cors",
});
if (response.ok)
channel = await response.json();
else
console.log("Error while refreshing channel");
}
async function getMembers() {
if (!channel) return;
let res = await fetch(API_URL + "/users/blocked/", {
@ -145,18 +138,18 @@
};
//--------------------------------------------------------------------------------/
const dispatch = createEventDispatcher();
let showProfileMenu = false;
let showUserMenu = false;
let selectedUser: string | null = null;
function openProfile(username: string) {
showProfileMenu = true;
function openUserMenu(username: string) {
showUserMenu = true;
selectedUser = username;
}
function closeProfileMenu() {
showProfileMenu = false;
function closeUserMenu() {
showUserMenu = false;
selectedUser = "";
}
onMount(closeProfileMenu);
onMount(closeUserMenu);
//--------------------------------------------------------------------------------/
@ -166,121 +159,6 @@
}
//--------------------------------------------------------------------------------/
async function getDMs(username: string): Promise<Response | null> {
const res = await fetch(API_URL + "/channels/dms/" + username, {
credentials: "include",
mode: "cors",
})
if (res.ok)
return res;
else
return null;
}
async function openDirectChat() {
const DMUsername = selectedUser;
let DMChannel: Array<ChannelsType> = [];
const res = await getDMs(DMUsername)
if (res && res.ok) {
DMChannel = await res.json();
if (DMChannel.length != 0)
await formatChannelNames(DMChannel)
setAppState(APPSTATE.CHANNELS + "#" + DMChannel[0].name)
} else {
console.log("Creating DMChannel: " + $store.username + "&" + DMUsername)
const body: CreateChannelDto = {
name: "none",
owner: $store.ftId,
password: "",
isPrivate: true,
isDM: true,
otherDMedUsername: DMUsername
}
fetch(API_URL + "/channels", {
credentials: "include",
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}).then(async () => {
const response = await getDMs(DMUsername)
if (response && response.ok) {
DMChannel = await response.json();
if (DMChannel.length != 0) {
await formatChannelNames(DMChannel)
setAppState(APPSTATE.CHANNELS + "#" + DMChannel[0].name)
} else {
show_popup("Error: Couldn't create DM.", false)
}
} else {
show_popup("Error: Couldn't create DM.", false)
}
}).catch(() => {
show_popup("Error: Couldn't create DM.", false)
})
}
}
const blockUser = async (username: string) => {
let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include",
mode: "cors",
});
if (response.ok) {
const target = await response.json();
response = await fetch(API_URL + "/users/block/" + target.ftId, {
method: "GET",
credentials: "include",
mode: "cors"
});
messages = messages.map((message) => {
if (message.author.username === username) {
message.hidden = true;
}
return message;
});
closeProfileMenu();
}
if (response.ok) await show_popup("User blocked", false);
else {
const error = await response.json();
await show_popup(error.message, false);
}
};
//--------------------------------------------------------------------------------/
const unblockUser = async (username: string) => {
let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include",
mode: "cors",
});
if (response.ok) {
const target = await response.json();
response = await fetch(API_URL + "/users/block/" + target.ftId, {
credentials: "include",
method: "DELETE",
mode: "cors"
});
messages = messages.map((message) => {
if (message.author.username === username) {
message.hidden = false;
}
return message;
});
}
if (response.ok) await show_popup("User unblocked", false);
else {
const error = await response.json();
await show_popup(error.message, false);
}
};
//--------------------------------------------------------------------------------/
const banUser = async (username: string) => {
@ -294,8 +172,7 @@
"Enter a time for which the user will be banned from this channel"
);
const duration = $content;
if (duration == "")
return;
if (duration == "") return;
const body: MuteDto = {
data: [target.ftId, duration]
}
@ -404,8 +281,6 @@
}
};
//--------------------------------------------------------------------------------/
const removeAdminUser = async (username: string) => {
let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include",
@ -440,6 +315,16 @@
dispatch("return-home")
}
};
const updateHiddens = (event: CustomEvent<string>) => {
const username = event.detail;
messages = messages.map((message) => {
if (message.author.username === username) {
message.hidden = !message.hidden;
}
return message;
});
}
</script>
<div class="overlay">
@ -450,8 +335,8 @@
{#if !message.hidden}
<span
class="message-name"
on:click={() => openProfile(message.author.username)}
on:keydown={() => openProfile(message.author.username)}
on:click={() => openUserMenu(message.author.username)}
on:keydown={() => openUserMenu(message.author.username)}
style="cursor: pointer;"
>
{message.author.username}
@ -460,40 +345,17 @@
</p>
{/each}
</div>
{#if showProfileMenu}
<div
class="profile-menu"
on:click|stopPropagation
on:keydown|stopPropagation
>
<ul>
<li>
<button on:click={openDirectChat}> Send Message </button>
</li>
<li>
<button on:click={() => dispatch("view-profile", selectedUser)}>
View Profile
</button>
</li>
<li>
<button on:click={() => dispatch("add-friend", selectedUser)}>
Add Friend
</button>
</li>
<li>
<button on:click={() => dispatch("invite-to-game", selectedUser)}>
Invite to Game
</button>
</li>
<li>
<button on:click={() => blockUser(selectedUser)}>
Block User
</button>
</li>
<li><button on:click={closeProfileMenu}> Close </button></li>
</ul>
</div>
{/if}
{#if showUserMenu}
<UsersMenu
{setAppState}
bind:username={selectedUser}
on:updateHiddens={updateHiddens}
on:close={closeUserMenu}
on:view-profile={() => dispatch("view-profile", selectedUser)}
on:add-friend={() => dispatch("add-friend", selectedUser)}
on:invite-to-game={() => dispatch("invite-to-game", selectedUser)}
/>
{/if}
<form on:submit|preventDefault={sendMessage}>
<input type="text" placeholder="Type a message..." bind:value={newText} />
<button style="background:#dedede; margin:auto">
@ -517,18 +379,15 @@
<li>
<p>
{member.username}
<button on:click={() => dispatch("view-profile", member.username)}> profile </button>
<button on:click={() => banUser(member.username)}> ban </button>
<button on:click={() => kickUser(member.username)}> kick </button>
<button on:click={() => muteUser(member.username)}> mute </button>
<button on:click={() => adminUser(member.username)}>
promote
</button>
<button on:click={() => removeAdminUser(member.username)}>
demote
</button>
<button on:click={() => unblockUser(member.username)}>
unblock
</button>
{#if channel.admins.some((usr) => usr.username == member.username)}
<button on:click={() => removeAdminUser(member.username)}> demote </button>
{:else}
<button on:click={() => adminUser(member.username)}> promote </button>
{/if}
</p>
</li>
{/each}
@ -616,17 +475,6 @@
height: 16px;
}
.profile-menu {
position: absolute;
background-color: #ffffff;
border: 1px solid #dedede;
border-radius: 5px;
padding: 1rem;
max-height: 70%;
overflow-y: auto;
z-index: 1;
}
ul {
list-style: none;
padding: 0;

77
front/src/components/Friends.svelte

@ -1,12 +1,19 @@
<script lang="ts" context="module">
import { onMount, onDestroy } from "svelte";
import { API_URL, store } from "../Auth";
import { show_popup } from "./Alert/content";
import UsersMenu from "./UsersMenu.svelte";
import type { APPSTATE } from "../App.svelte";
import { createEventDispatcher } from "svelte";
export interface Friend {
username: string;
status: "online" | "offline" | "in a game";
ftId: number;
}
export async function addFriend(event: any) {
console.log(typeof event);
event.preventDefault();
const username = event.target
? event.target.querySelector('input[type="text"]').value
@ -26,13 +33,25 @@
</script>
<script lang="ts">
import { onMount, onDestroy } from "svelte";
import { API_URL, store } from "../Auth";
import { show_popup } from "./Alert/content";
export let setAppState: (newState: APPSTATE | string) => void;
let friends: Friend[] = [];
let invits: Friend[] = [];
let friendsInterval: ReturnType<typeof setInterval>;
const dispatch = createEventDispatcher();
onMount(() => {
getFriends();
getInvits();
friendsInterval = setInterval(async () => {
getFriends();
getInvits();
}, 5000);
});
onDestroy(() => {
clearInterval(friendsInterval);
});
async function getFriends(): Promise<void> {
let response = await fetch(API_URL + "/users/friends", {
@ -49,29 +68,49 @@
invits = await response.json();
}
onMount(() => {
getFriends();
getInvits();
friendsInterval = setInterval(async () => {
getFriends();
getInvits();
}, 5000);
});
let showUserMenu = false;
let selectedUser: string | null = null;
function openUserMenu(username: string) {
showUserMenu = true;
selectedUser = username;
}
function closeUserMenu() {
showUserMenu = false;
selectedUser = "";
}
onDestroy(() => {
clearInterval(friendsInterval);
});
</script>
<div class="overlay">
<div class="friends" on:click|stopPropagation on:keydown|stopPropagation>
<div>
{#if showUserMenu}
<UsersMenu
{setAppState}
bind:username={selectedUser}
on:close={closeUserMenu}
on:view-profile={() => dispatch("view-profile", selectedUser)}
on:add-friend={addFriend}
on:invite-to-game={() => dispatch("invite-to-game", selectedUser)}
/>
{/if}
<li>
<span class="message-name"
on:click={() => openUserMenu($store.username)}
on:keydown={() => openUserMenu($store.username)}
style="cursor: pointer;"
>{$store.username} is {$store.status}</span>
</li>
<h2>{$store.username} friends:</h2>
{#if friends.length > 0}
<div class="friends-list">
{#each friends as friend}
<li>
<span>{friend.username} is {friend.status}</span>
<span class="message-name"
on:click={() => openUserMenu(friend.username)}
on:keydown={() => openUserMenu(friend.username)}
style="cursor: pointer;"
>{friend.username} is {friend.status}</span>
</li>
{/each}
</div>
@ -83,7 +122,11 @@
<div class="invits-list">
{#each invits as invit}
<li>
<span>{invit.username} invited you to be friend.</span>
<span class="message-name"
on:click={() => openUserMenu(invit.username)}
on:keydown={() => openUserMenu(invit.username)}
style="cursor: pointer;"
>{invit.username} invited you to be friend.</span>
</li>
{/each}
</div>

174
front/src/components/UsersMenu.svelte

@ -0,0 +1,174 @@
<script lang="ts" context="module">
import { API_URL, store } from "../Auth";
import { APPSTATE} from "../App.svelte";
import { createEventDispatcher } from "svelte";
import { formatChannelNames, type ChannelsType } from "./Channels.svelte";
import { show_popup } from "./Alert/content";
import type { CreateChannelDto } from "./dtos/create-channel.dto";
</script>
<script lang="ts">
export let username: string = "";
export let setAppState: (newState: APPSTATE | string) => void;
const dispatch = createEventDispatcher();
async function getDMs(username: string): Promise<Response | null> {
const res = await fetch(API_URL + "/channels/dms/" + username, {
credentials: "include",
mode: "cors",
})
if (res.ok)
return res;
else
return null;
}
async function openDirectChat() {
const DMUsername = username;
let DMChannel: Array<ChannelsType> = [];
const res = await getDMs(DMUsername)
if (res && res.ok) {
DMChannel = await res.json();
if (DMChannel.length != 0)
await formatChannelNames(DMChannel)
setAppState(APPSTATE.CHANNELS + "#" + DMChannel[0].name)
} else {
console.log("Creating DMChannel: " + $store.username + "&" + DMUsername)
const body: CreateChannelDto = {
name: "none",
owner: $store.ftId,
password: "",
isPrivate: true,
isDM: true,
otherDMedUsername: DMUsername
}
fetch(API_URL + "/channels", {
credentials: "include",
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}).then(async () => {
const response = await getDMs(DMUsername)
if (response && response.ok) {
DMChannel = await response.json();
if (DMChannel.length != 0) {
await formatChannelNames(DMChannel)
setAppState(APPSTATE.CHANNELS + "#" + DMChannel[0].name)
} else {
show_popup("Error: Couldn't create DM.", false)
}
} else {
show_popup("Error: Couldn't create DM.", false)
}
}).catch(() => {
show_popup("Error: Couldn't create DM.", false)
})
}
}
const blockUser = async (username: string) => {
let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include",
mode: "cors",
});
if (response.ok) {
const target = await response.json();
response = await fetch(API_URL + "/users/block/" + target.ftId, {
method: "GET",
credentials: "include",
mode: "cors"
});
dispatch("updateHiddens", username)
}
if (response.ok) await show_popup("User blocked", false);
else {
const error = await response.json();
await show_popup(error.message, false);
}
dispatch("close")
};
//--------------------------------------------------------------------------------/
const unblockUser = async (username: string) => {
let response = await fetch(API_URL + "/users/" + username + "/byname", {
credentials: "include",
mode: "cors",
});
if (response.ok) {
const target = await response.json();
response = await fetch(API_URL + "/users/block/" + target.ftId, {
credentials: "include",
method: "DELETE",
mode: "cors"
});
dispatch("updateHiddens", username)
}
if (response.ok) await show_popup("User unblocked", false);
else {
const error = await response.json();
await show_popup(error.message, false);
}
dispatch("close")
};
</script>
<div
class="user-menu"
on:click|stopPropagation
on:keydown|stopPropagation
>
<ul>
<li>
<button on:click={openDirectChat}> Send Message </button>
</li>
<li>
<button on:click={() => dispatch("view-profile", username)}>
View Profile
</button>
</li>
<li>
<button on:click={() => dispatch("add-friend", username)}>
Add Friend
</button>
</li>
<li>
<button on:click={() => dispatch("invite-to-game", username)}>
Invite to Game
</button>
</li>
<li>
<button on:click={() => blockUser(username)}>
Block User
</button>
</li>
<li><button on:click={() => dispatch("close")}> Close </button></li>
</ul>
</div>
<style>
.user-menu {
position: absolute;
background-color: #ffffff;
border: 1px solid #dedede;
border-radius: 5px;
padding: 1rem;
max-height: 70%;
overflow-y: auto;
z-index: 1;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
li {
margin-bottom: 0.5rem;
}
</style>
Loading…
Cancel
Save