Working on upload images and tilemap

This commit is contained in:
Benjamin Sherriff
2023-10-23 16:17:07 -04:00
parent 8b4d4e1b1f
commit 3eb888b57d
22 changed files with 987 additions and 656 deletions

View File

@@ -25,7 +25,7 @@ export async function logout() {
}
export async function refresh(refresh_token_rotation?: boolean): Promise<ResponseAuth | undefined> {
const response = await get('auth/refresh', { params: { refresh_token_rotation } });
const response = await get('auth/refresh', { refresh_token_rotation });
if (response?.status === 200) {
return response.json();
} else {

View File

@@ -8,7 +8,7 @@ export async function get(endpoint: string, params: Record<string, any> = {}): P
// Remove undefined params
Object.keys(params).forEach((key) => params[key] === undefined && delete params[key]);
const urlParams = new URLSearchParams(params);
const url = urlParams ? `${baseURL}/${endpoint}?${urlParams}` : `${baseURL}/${endpoint}`;
const url = urlParams && urlParams.size > 0 ? `${baseURL}/${endpoint}?${urlParams}` : `${baseURL}/${endpoint}`;
const response = await fetch(url, {
method: 'GET',
credentials: 'include'
@@ -16,11 +16,15 @@ export async function get(endpoint: string, params: Record<string, any> = {}): P
return response;
}
export async function post(endpoint: string, body = {}): Promise<Response> {
interface PostOptions {
headers?: Record<string, any>;
}
export async function post(endpoint: string, body = {}, options?: PostOptions): Promise<Response> {
const url = `${baseURL}/${endpoint}`;
const response = await fetch(url, {
method: 'POST',
headers: {
headers: options?.headers || {
'Content-Type': 'application/json'
},
credentials: 'include',

24
ui/src/api/users.ts Normal file
View File

@@ -0,0 +1,24 @@
import { get, post } from '.';
export async function getPicture(): Promise<Blob | undefined> {
const response = await get('users/picture');
if (response?.status === 200) {
return response.blob();
} else {
return undefined;
}
}
export async function setPicture(payload: File): Promise<boolean> {
const data = new FormData();
data.append('file', payload);
// TODO: Figure out why the form data object is empty
const response = await post('users/picture', data, {
headers: { 'Content-Type': 'multipart/form-data' }
});
if (response?.status === 200) {
return true;
} else {
return false;
}
}

View File

@@ -22,15 +22,13 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<head>
<title>Siren</title>
</head>
<body className={`${inter.className} wrapper h-full`}>
<body className={`${inter.className} wrapper`}>
<RecoilRootWrapper>
<MantineProvider>
<Notifications />
<ModalsProvider>
<Header />
<Box p='xl' pt='sm' className='h-full'>
{children}
</Box>
<Box>{children}</Box>
</ModalsProvider>
</MantineProvider>
</RecoilRootWrapper>

View File

@@ -1,6 +1,11 @@
import TileGrid from '@/components/TileGrid';
import React from 'react';
// Home page for siren
export default function Page() {
return <div></div>;
return (
<div>
<TileGrid />
</div>
);
}

View File

@@ -3,6 +3,7 @@
justify-content: space-between;
color: black;
border-bottom: 1px solid #e6e6e6;
max-height: 70px;
}
.navbar .left {

View File

@@ -3,15 +3,16 @@
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import './header.css';
import { Avatar, Button, Card, Grid, Group, Menu, Text, UnstyledButton } from '@mantine/core';
import { Avatar, Button, Card, FileButton, Grid, Group, Menu, Text, UnstyledButton } from '@mantine/core';
import Cookies from 'js-cookie';
import { useEffect, useState } from 'react';
import { logout, me, refreshLoggedIn } from '@/api/auth';
import { logout, refresh, refreshLoggedIn } from '@/api/auth';
import { useToggle } from '@mantine/hooks';
import { HeaderModal } from './HeaderModal';
import { HeaderItem, headerItems } from './headerItems';
import { userState } from '@/state/auth';
import { useRecoilState } from 'recoil';
import { getPicture, setPicture } from '@/api/users';
export default function Header() {
const pathName = usePathname();
@@ -19,13 +20,19 @@ export default function Header() {
const [headers, setHeaders] = useState<HeaderItem[]>([]);
const [user, setUser] = useRecoilState(userState);
const [refreshId, setRefreshId] = useState<NodeJS.Timeout | undefined>(undefined);
const [profilePicture, setProfilePicture] = useState<File | null>(null);
useEffect(() => {
if (!user && Cookies.get('logged_in')) {
me().then((response) => {
if (!user || !Cookies.get('logged_in')) {
refresh().then((response) => {
if (response) {
setRefreshId(refreshLoggedIn());
setUser(response.user);
getPicture().then((response) => {
if (response) {
setProfilePicture(response as File);
}
});
}
});
}
@@ -62,27 +69,51 @@ export default function Header() {
<Menu.Target>
<UnstyledButton className='user user-button'>
<Group>
<Avatar />
<Avatar src={profilePicture ? URL.createObjectURL(profilePicture) : undefined} />
<div style={{ flex: 1 }}>
<Text size='sm' fw={500}>
{user.first_name} {user.last_name}
</Text>
<Text c='dimmed' size='xs'>
<Text c='dimmed' size='xs' style={{ textTransform: 'uppercase' }}>
{user.role}
</Text>
</div>
</Group>
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>
<Menu.Dropdown p={0}>
<Card>
<Card.Section h={140} style={{}} />
<Avatar size={80} radius={80} mx={'auto'} mt={-30} />
<Card.Section h={140} style={{ backgroundColor: '#4481e3' }} />
<FileButton
onChange={(payload) => {
if (payload) {
setPicture(payload).then((response) => {
if (response) {
setProfilePicture(payload);
}
});
}
}}
accept='image/png,image/jpeg,image/jpg'
multiple={false}
>
{(props) => (
<Avatar
{...props}
component='button'
size={80}
radius={80}
mx={'auto'}
mt={-30}
style={{ cursor: 'pointer' }}
src={profilePicture ? URL.createObjectURL(profilePicture) : undefined}
/>
)}
</FileButton>
<Text ta='center' fz='lg' fw={500} mt='sm'>
{user.first_name} {user.last_name}
</Text>
<Text ta='center' fz='sm' c='dimmed'>
<Text ta='center' fz='sm' c='dimmed' style={{ textTransform: 'uppercase' }}>
{user.role}
</Text>
<Grid mt='xl'>

View File

View File

@@ -0,0 +1,38 @@
'use client';
import { Graphics, Stage } from '@pixi/react';
import { Graphics as PixiGraphics } from '@pixi/graphics';
import { useCallback } from 'react';
// export default function TileGrid({ width, height }: TileGridProps) {
export default function TIleGrid() {
// Offset height of navbar from window height
const height = window.innerHeight - 75;
// Offset width of layout padding from window width
const width = window.innerWidth;
const draw = useCallback((g: PixiGraphics) => {
g.clear();
// Draw dot in the corner of each tile
for (let x = 0; x < width; x += 32) {
for (let y = 0; y < height; y += 32) {
g.beginFill(0xffffff, 0.5);
g.drawCircle(x, y, 1);
g.endFill();
}
}
}, []);
return (
<Stage
width={width}
height={height}
options={{
backgroundColor: 0x333333,
antialias: false
}}
>
<Graphics draw={draw} />
</Stage>
);
}

View File

@@ -0,0 +1,9 @@
.tile {
padding: 0;
margin: 0;
width: 100vw;
max-width: 100%;
height: 100vh;
max-height: 100%;
user-select: none;
}