Working on layout, tilegrid
This commit is contained in:
5
ui/src/app/campaigns/page.tsx
Normal file
5
ui/src/app/campaigns/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
5
ui/src/app/characters/[id]/page.tsx
Normal file
5
ui/src/app/characters/[id]/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page({ params }: { params: { id: string } }) {
|
||||||
|
return <>{params.id}</>;
|
||||||
|
}
|
||||||
5
ui/src/app/characters/create/page.tsx
Normal file
5
ui/src/app/characters/create/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <h1>Create new Character</h1>;
|
||||||
|
}
|
||||||
5
ui/src/app/characters/page.tsx
Normal file
5
ui/src/app/characters/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
@@ -4,6 +4,16 @@ import React from 'react';
|
|||||||
// Home page for siren
|
// Home page for siren
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
|
// <div>
|
||||||
|
// <p>Siren is a Dungeon Master's best friend.</p>
|
||||||
|
// <h2>Features:</h2>
|
||||||
|
// <ul>
|
||||||
|
// <li>Manage your campaign and players</li>
|
||||||
|
// <li>Create battlemaps on the fly and track initiative</li>
|
||||||
|
// <li>Connect the Discord Bot to play online with friends</li>
|
||||||
|
// <li>Reference Races, Classes, Items, Spells, and more</li>
|
||||||
|
// </ul>
|
||||||
|
// </div>
|
||||||
<div style={{ overflow: 'hidden' }}>
|
<div style={{ overflow: 'hidden' }}>
|
||||||
<TileGrid />
|
<TileGrid />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,36 +1,49 @@
|
|||||||
export interface HeaderItem {
|
export interface HeaderItem {
|
||||||
name: string;
|
label: string;
|
||||||
link: string;
|
link?: string;
|
||||||
role?: string;
|
links?: HeaderItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const headerItems: HeaderItem[] = [
|
export const headerItems: HeaderItem[] = [
|
||||||
{
|
{
|
||||||
name: 'Races',
|
label: 'Campaigns',
|
||||||
link: '/races'
|
link: '/campaigns'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Classes',
|
label: 'Characters',
|
||||||
link: '/classes'
|
link: '/characters'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Feats',
|
label: 'Resources',
|
||||||
link: '/feats'
|
links: [
|
||||||
},
|
{
|
||||||
{
|
label: 'Races',
|
||||||
name: 'Options & Features',
|
link: '/races'
|
||||||
link: '/options'
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Classes',
|
||||||
name: 'Backgrounds',
|
link: '/classes'
|
||||||
link: '/backgrounds'
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Feats',
|
||||||
name: 'Items',
|
link: '/feats'
|
||||||
link: '/items'
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Options & Features',
|
||||||
name: 'Spells',
|
link: '/options'
|
||||||
link: '/spells'
|
},
|
||||||
|
{
|
||||||
|
label: 'Backgrounds',
|
||||||
|
link: '/backgrounds'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Items',
|
||||||
|
link: '/items'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Spells',
|
||||||
|
link: '/spells'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import './header.css';
|
import './header.css';
|
||||||
import { Avatar, Button, Card, FileButton, Grid, Group, Menu, Text, UnstyledButton } from '@mantine/core';
|
import { Avatar, Button, Card, Center, FileButton, Grid, Group, Menu, Text, UnstyledButton } from '@mantine/core';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { logout, refresh, refreshLoggedIn } from '@/api/auth';
|
import { logout, refresh, refreshLoggedIn } from '@/api/auth';
|
||||||
@@ -13,14 +13,16 @@ import { HeaderItem, headerItems } from './headerItems';
|
|||||||
import { userState } from '@/state/auth';
|
import { userState } from '@/state/auth';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { getPicture, setPicture } from '@/api/users';
|
import { getPicture, setPicture } from '@/api/users';
|
||||||
|
import { BsChevronDown } from 'react-icons/bs';
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const pathName = usePathname();
|
const pathName = usePathname();
|
||||||
const [modalType, toggle] = useToggle([undefined, 'login', 'register', 'reset']);
|
const [modalType, toggle] = useToggle([undefined, 'login', 'register', 'reset']);
|
||||||
const [headers, setHeaders] = useState<HeaderItem[]>([]);
|
const [headers] = useState<HeaderItem[]>(headerItems);
|
||||||
const [user, setUser] = useRecoilState(userState);
|
const [user, setUser] = useRecoilState(userState);
|
||||||
const [refreshId, setRefreshId] = useState<NodeJS.Timeout | undefined>(undefined);
|
const [refreshId, setRefreshId] = useState<NodeJS.Timeout | undefined>(undefined);
|
||||||
const [profilePicture, setProfilePicture] = useState<File | null>(null);
|
const [profilePicture, setProfilePicture] = useState<File | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user || !Cookies.get('logged_in')) {
|
if (!user || !Cookies.get('logged_in')) {
|
||||||
@@ -40,16 +42,6 @@ export default function Header() {
|
|||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const h: HeaderItem[] = [];
|
|
||||||
headerItems.forEach((item) => {
|
|
||||||
if (item.role == undefined || user?.role == item.role) {
|
|
||||||
h.push(item);
|
|
||||||
}
|
|
||||||
setHeaders(h);
|
|
||||||
});
|
|
||||||
}, [user]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<nav className='navbar'>
|
<nav className='navbar'>
|
||||||
@@ -58,11 +50,41 @@ export default function Header() {
|
|||||||
Siren
|
Siren
|
||||||
</Link>
|
</Link>
|
||||||
<div className='header-items'>
|
<div className='header-items'>
|
||||||
{headers.map((item) => (
|
{headers.map((item) => {
|
||||||
<Link className={`header-item ${pathName == item.link && 'active'}`} href={item.link} key={item.name}>
|
const menuItems = item.links?.map((subItem) => (
|
||||||
{item.name}
|
<Menu.Item
|
||||||
</Link>
|
color={pathName == subItem.link ? 'blue' : undefined}
|
||||||
))}
|
onClick={() => router.push(subItem.link ?? '#')}
|
||||||
|
key={subItem.label}
|
||||||
|
>
|
||||||
|
{subItem.label}
|
||||||
|
</Menu.Item>
|
||||||
|
));
|
||||||
|
if (menuItems) {
|
||||||
|
return (
|
||||||
|
<Menu trigger='hover' transitionProps={{ exitDuration: 0 }} withinPortal key={item.label}>
|
||||||
|
<Menu.Target>
|
||||||
|
<Link className={`header-item ${pathName == item.link && 'active'}`} href={item.link ?? '#'}>
|
||||||
|
<Center>
|
||||||
|
{item.label}
|
||||||
|
<BsChevronDown />
|
||||||
|
</Center>
|
||||||
|
</Link>
|
||||||
|
</Menu.Target>
|
||||||
|
<Menu.Dropdown>{menuItems}</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
className={`header-item ${pathName == item.link && 'active'}`}
|
||||||
|
href={item.link ?? '#'}
|
||||||
|
key={item.label}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='user-section'>
|
<div className='user-section'>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Graphics, Stage } from '@pixi/react';
|
import { Graphics, Stage, Text } from '@pixi/react';
|
||||||
import { Graphics as PixiGraphics } from '@pixi/graphics';
|
import { Graphics as PixiGraphics } from '@pixi/graphics';
|
||||||
import { MouseEvent, WheelEvent, useCallback, useEffect, useState } from 'react';
|
import { MouseEvent, WheelEvent, useCallback, useEffect, useState } from 'react';
|
||||||
import TileControls, { EditTool, Tool, defaultColors } from './TileControls';
|
import TileControls, { EditTool, Tool, defaultColors } from './TileControls';
|
||||||
import { Box } from '@mantine/core';
|
import { Box } from '@mantine/core';
|
||||||
|
import { TextStyle } from 'pixi.js';
|
||||||
|
|
||||||
interface SquareEdit {
|
interface SquareEdit {
|
||||||
x: number;
|
x: number;
|
||||||
@@ -31,21 +32,21 @@ export default function TileGrid() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window) {
|
if (window) {
|
||||||
window.addEventListener(
|
// window.addEventListener(
|
||||||
'wheel',
|
// 'wheel',
|
||||||
(event) => {
|
// (event) => {
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
},
|
// },
|
||||||
{ passive: false }
|
// { passive: false }
|
||||||
);
|
// );
|
||||||
// Disable right click context menu
|
// // Disable right click context menu
|
||||||
window.addEventListener(
|
// window.addEventListener(
|
||||||
'contextmenu',
|
// 'contextmenu',
|
||||||
(event) => {
|
// (event) => {
|
||||||
event.preventDefault();
|
// event.preventDefault();
|
||||||
},
|
// },
|
||||||
{ passive: false }
|
// { passive: false }
|
||||||
);
|
// );
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -73,20 +74,39 @@ export default function TileGrid() {
|
|||||||
g.endFill();
|
g.endFill();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[edits]
|
[edits, zoom]
|
||||||
);
|
);
|
||||||
|
|
||||||
function clickEvent(e: MouseEvent) {
|
function clickEvent(e: MouseEvent) {
|
||||||
setMouseDown(true);
|
setMouseDown(true);
|
||||||
setLastPosition({ x: e.clientX, y: e.clientY });
|
setLastPosition({ x: e.clientX, y: e.clientY });
|
||||||
if (tool == Tool.ZOOM) {
|
if (tool == Tool.ZOOM) {
|
||||||
handleZoom(e.button === 0 ? -100 : 100);
|
handleZoom(e.button === 0 ? -100 : 100, e.clientX, e.clientY);
|
||||||
|
} else if (tool == Tool.EDIT) {
|
||||||
|
if (editTool === EditTool.SQUARE) {
|
||||||
|
drawSquare(e.button, e.clientX, e.clientY);
|
||||||
|
} else if (editTool === EditTool.CIRCLE) {
|
||||||
|
// handle circle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSquare(button: number, clientX: number, clientY: number) {
|
||||||
|
// Calculate tile coordinates
|
||||||
|
const x = Math.floor((clientX - position.x) / (32 * zoom));
|
||||||
|
const y = Math.floor((clientY - position.y) / (32 * zoom));
|
||||||
|
if (button === 1) {
|
||||||
|
// Add new edit if left mouse button is pressed
|
||||||
|
setEdits([...edits, { x, y, color: colors[selectedColor] }]);
|
||||||
|
} else if (button == 2) {
|
||||||
|
// Remove edit if right mouse button is pressed
|
||||||
|
setEdits(edits.filter((edit) => edit.x !== x || edit.y !== y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveEvent(e: MouseEvent) {
|
function moveEvent(e: MouseEvent) {
|
||||||
if (mouseDown) {
|
if (mouseDown) {
|
||||||
if (tool == Tool.HAND) {
|
if (tool == Tool.HAND || e.buttons == 4) {
|
||||||
let dx = position.x + e.clientX - lastPosition.x;
|
let dx = position.x + e.clientX - lastPosition.x;
|
||||||
let dy = position.y + e.clientY - lastPosition.y;
|
let dy = position.y + e.clientY - lastPosition.y;
|
||||||
// Prevent coordinates from going out of bounds
|
// Prevent coordinates from going out of bounds
|
||||||
@@ -98,18 +118,7 @@ export default function TileGrid() {
|
|||||||
setLastPosition({ x: e.clientX, y: e.clientY });
|
setLastPosition({ x: e.clientX, y: e.clientY });
|
||||||
} else if (tool === Tool.EDIT) {
|
} else if (tool === Tool.EDIT) {
|
||||||
if (editTool === EditTool.SQUARE) {
|
if (editTool === EditTool.SQUARE) {
|
||||||
// Calculate tile coordinates
|
drawSquare(e.buttons, e.clientX, e.clientY);
|
||||||
const x = Math.floor((e.clientX - position.x) / (32 * zoom));
|
|
||||||
const y = Math.floor((e.clientY - position.y - 64) / (32 * zoom));
|
|
||||||
// Check if tile is already edited, and remove it
|
|
||||||
const index = edits.findIndex((edit) => edit.x === x && edit.y === y);
|
|
||||||
if (index !== -1) {
|
|
||||||
setEdits([...edits.slice(0, index), ...edits.slice(index + 1)]);
|
|
||||||
}
|
|
||||||
// Add new edit if left mouse button is pressed
|
|
||||||
if (e.buttons === 1) {
|
|
||||||
setEdits([...edits, { x, y, color: colors[selectedColor] }]);
|
|
||||||
}
|
|
||||||
} else if (editTool === EditTool.CIRCLE) {
|
} else if (editTool === EditTool.CIRCLE) {
|
||||||
// handle circle
|
// handle circle
|
||||||
}
|
}
|
||||||
@@ -120,10 +129,10 @@ export default function TileGrid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function zoomEvent(e: WheelEvent) {
|
function zoomEvent(e: WheelEvent) {
|
||||||
handleZoom(e.deltaY);
|
handleZoom(e.deltaY, e.clientX, e.clientY);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleZoom(delta: number) {
|
function handleZoom(delta: number, clientX: number, clientY: number) {
|
||||||
let newZoom = zoom;
|
let newZoom = zoom;
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
newZoom = zoom / 1.1;
|
newZoom = zoom / 1.1;
|
||||||
@@ -132,7 +141,12 @@ export default function TileGrid() {
|
|||||||
}
|
}
|
||||||
newZoom = Math.min(newZoom, 3);
|
newZoom = Math.min(newZoom, 3);
|
||||||
newZoom = Math.max(newZoom, 0.6);
|
newZoom = Math.max(newZoom, 0.6);
|
||||||
|
console.log(newZoom);
|
||||||
setZoom(newZoom);
|
setZoom(newZoom);
|
||||||
|
// Adjust position to zoom in on mouse position
|
||||||
|
const dx = (position.x - clientX) * (newZoom / zoom) + clientX;
|
||||||
|
const dy = (position.y - clientY) * (newZoom / zoom) + clientY;
|
||||||
|
setPosition({ x: dx, y: dy });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user