mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-12 09:04:53 -07:00
Add left nav (#4705)
* Automated translation update ( bf08a04cda )
* Add Layout component wip
* finish layout implementation
* convert header to left nav
* better nav item spacing
* return source files to original glory
* lint fix
* Remove height limit on login screen
* fix top spacing on 3-panel layout
---------
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Brent Clark <brent@backboneiq.com>
This commit is contained in:
parent
cab5f29b57
commit
cef99cba71
18 changed files with 280 additions and 201 deletions
31
webclient/src/containers/Layout/Layout.css
Normal file
31
webclient/src/containers/Layout/Layout.css
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
.layout {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout--no-height-limit {
|
||||
height: initial;
|
||||
max-height: initial;
|
||||
}
|
||||
|
||||
.bottom-bar__container {
|
||||
background: #555;
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page__body {
|
||||
flex: 1;
|
||||
max-height: calc(100% - 50px);
|
||||
}
|
||||
|
||||
.page {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
width: 100%;
|
||||
}
|
||||
39
webclient/src/containers/Layout/Layout.tsx
Normal file
39
webclient/src/containers/Layout/Layout.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import LeftNav from './LeftNav';
|
||||
|
||||
import './Layout.css'
|
||||
|
||||
function Layout(props:LayoutProps) {
|
||||
const { children, className, showNav = true, noHeightLimit = false } = props;
|
||||
const containerClasses = ['layout']
|
||||
if (noHeightLimit === true) {
|
||||
containerClasses.push('layout--no-height-limit')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={containerClasses.join(" ")}>
|
||||
{showNav && <LeftNav />}
|
||||
<section className="page">
|
||||
<div className={`page__body ${className}`}>
|
||||
{children}
|
||||
</div>
|
||||
{showNav && <BottomBar />}
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BottomBar(props) {
|
||||
return (
|
||||
<div className="bottom-bar__container">
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface LayoutProps {
|
||||
showNav?: boolean;
|
||||
children: any;
|
||||
className?: string;
|
||||
noHeightLimit?: boolean
|
||||
}
|
||||
|
||||
export default Layout;
|
||||
128
webclient/src/containers/Layout/LeftNav.css
Normal file
128
webclient/src/containers/Layout/LeftNav.css
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
.LeftNav__container {
|
||||
background: #7033DB;
|
||||
width: 100px;
|
||||
min-width: 100px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.LeftNav__logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.LeftNav__logo a {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.LeftNav__logo img {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.LeftNav-content {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.LeftNav-serverDetails {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.LeftNav-server__indicator {
|
||||
display: inline-block;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
background: red;
|
||||
border: 1px solid;
|
||||
border-radius: 50%;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.LeftNav-nav {
|
||||
}
|
||||
|
||||
.LeftNav-nav__links {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.LeftNav-nav__link {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.LeftNav-nav__link:hover {
|
||||
background: rgba(0, 0, 0, .125);
|
||||
}
|
||||
|
||||
.LeftNav-nav__link:hover .LeftNav-nav__link-menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.LeftNav-nav__link-btn {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 5px 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.LeftNav-nav__link-btn__icon {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.LeftNav-nav__link-menu {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
transform: translateY(100%);
|
||||
min-width: 150px;
|
||||
background: #3f51b5;
|
||||
box-shadow: 1px 1px 2px 0px black;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.LeftNav-nav__link-menu__item {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.LeftNav-nav__link-menu__btn {
|
||||
padding: 6px 16px;
|
||||
width: 100%;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.LeftNav-nav__actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.LeftNav-nav__action {
|
||||
|
||||
}
|
||||
|
||||
.LeftNav-nav__action button {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.temp-subnav__rooms {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.temp-chip {
|
||||
margin-left: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.temp-chip > div {
|
||||
cursor: inherit;
|
||||
}
|
||||
195
webclient/src/containers/Layout/LeftNav.tsx
Normal file
195
webclient/src/containers/Layout/LeftNav.tsx
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { NavLink, useNavigate, generatePath } from 'react-router-dom';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import MailOutlineRoundedIcon from '@mui/icons-material/MailOutline';
|
||||
import MenuRoundedIcon from '@mui/icons-material/MenuRounded';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { AuthenticationService, RoomsService } from 'api';
|
||||
import { CardImportDialog } from 'dialogs';
|
||||
import { Images } from 'images';
|
||||
import { RoomsSelectors, ServerSelectors } from 'store';
|
||||
import { Room, RouteEnum, User } from 'types';
|
||||
|
||||
import './LeftNav.css';
|
||||
|
||||
const LeftNav = ({ joinedRooms, serverState, user }: LeftNavProps) => {
|
||||
const navigate = useNavigate();
|
||||
const [state, setState] = useState<LeftNavState>({
|
||||
anchorEl: null,
|
||||
showCardImportDialog: false,
|
||||
options: [],
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let options: string[] = [
|
||||
'Account',
|
||||
'Replays',
|
||||
];
|
||||
|
||||
if (user && AuthenticationService.isModerator(user)) {
|
||||
options = [
|
||||
...options,
|
||||
'Administration',
|
||||
'Logs'
|
||||
];
|
||||
}
|
||||
|
||||
setState(s => ({ ...s, options }));
|
||||
}, [user]);
|
||||
|
||||
const handleMenuOpen = (event) => {
|
||||
setState(s => ({ ...s, anchorEl: event.target }));
|
||||
}
|
||||
|
||||
const handleMenuItemClick = (option: string) => {
|
||||
const route = RouteEnum[option.toUpperCase()];
|
||||
navigate(generatePath(route));
|
||||
}
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setState(s => ({ ...s, anchorEl: null }));
|
||||
}
|
||||
|
||||
const leaveRoom = (event, roomId) => {
|
||||
event.preventDefault();
|
||||
RoomsService.leaveRoom(roomId);
|
||||
};
|
||||
|
||||
const openImportCardWizard = () => {
|
||||
setState(s => ({ ...s, showCardImportDialog: true }));
|
||||
handleMenuClose();
|
||||
}
|
||||
|
||||
const closeImportCardWizard = () => {
|
||||
setState(s => ({ ...s, showCardImportDialog: false }));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="LeftNav__container">
|
||||
<div>
|
||||
<div className="LeftNav__logo">
|
||||
<NavLink to={RouteEnum.SERVER}>
|
||||
<img src={Images.Logo} alt="logo" />
|
||||
</NavLink>
|
||||
{ AuthenticationService.isConnected(serverState) && (
|
||||
<span className="LeftNav-server__indicator"></span>
|
||||
) }
|
||||
</div>
|
||||
{ AuthenticationService.isConnected(serverState) && (
|
||||
<div className="LeftNav-content">
|
||||
<nav className="LeftNav-nav">
|
||||
<nav className="LeftNav-nav__links">
|
||||
<div className="LeftNav-nav__link">
|
||||
<NavLink
|
||||
className="LeftNav-nav__link-btn"
|
||||
to={
|
||||
joinedRooms.length
|
||||
? generatePath(RouteEnum.ROOM, { roomId: joinedRooms[0].roomId.toString() })
|
||||
: RouteEnum.SERVER
|
||||
}
|
||||
>
|
||||
Rooms
|
||||
<ArrowDropDownIcon className="LeftNav-nav__link-btn__icon" fontSize="small" />
|
||||
</NavLink>
|
||||
<div className="LeftNav-nav__link-menu">
|
||||
{joinedRooms.map(({ name, roomId }) => (
|
||||
<div className="LeftNav-nav__link-menu__item" key={roomId}>
|
||||
<NavLink className="LeftNav-nav__link-menu__btn" to={ generatePath(RouteEnum.ROOM, { roomId: roomId.toString() }) }>
|
||||
{name}
|
||||
|
||||
<IconButton size="small" edge="end" onClick={event => leaveRoom(event, roomId)}>
|
||||
<CloseIcon style={{ fontSize: 10, color: 'white' }} />
|
||||
</IconButton>
|
||||
</NavLink>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="LeftNav-nav__link">
|
||||
<NavLink className="LeftNav-nav__link-btn" to={ RouteEnum.GAME }>
|
||||
Games
|
||||
<ArrowDropDownIcon className="LeftNav-nav__link-btn__icon" fontSize="small" />
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="LeftNav-nav__link">
|
||||
<NavLink className="LeftNav-nav__link-btn" to={ RouteEnum.DECKS }>
|
||||
Decks
|
||||
<ArrowDropDownIcon className="LeftNav-nav__link-btn__icon" fontSize="small" />
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
<div className="LeftNav-nav__actions">
|
||||
<div className="LeftNav-nav__action">
|
||||
<IconButton size="large">
|
||||
<MailOutlineRoundedIcon style={{ color: 'inherit' }} />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className="LeftNav-nav__action">
|
||||
<IconButton onClick={handleMenuOpen} size="large">
|
||||
<MenuRoundedIcon style={{ color: 'inherit' }} />
|
||||
</IconButton>
|
||||
<Menu
|
||||
anchorEl={state.anchorEl}
|
||||
keepMounted
|
||||
open={!!state.anchorEl}
|
||||
onClose={() => handleMenuClose()}
|
||||
PaperProps={{
|
||||
style: {
|
||||
marginTop: '32px',
|
||||
width: '20ch',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{state.options.map((option) => (
|
||||
<MenuItem key={option} onClick={(event) => handleMenuItemClick(option)}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
|
||||
<MenuItem key='Import Cards' onClick={(event) => openImportCardWizard()}>
|
||||
Import Cards
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
|
||||
<CardImportDialog
|
||||
isOpen={state.showCardImportDialog}
|
||||
handleClose={closeImportCardWizard}
|
||||
></CardImportDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface LeftNavProps {
|
||||
serverState: number;
|
||||
server: string;
|
||||
user: User;
|
||||
joinedRooms: Room[];
|
||||
showNav?: boolean;
|
||||
}
|
||||
|
||||
interface LeftNavState {
|
||||
anchorEl: Element;
|
||||
showCardImportDialog: boolean;
|
||||
options: string[];
|
||||
}
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
serverState: ServerSelectors.getState(state),
|
||||
server: ServerSelectors.getName(state),
|
||||
user: ServerSelectors.getUser(state),
|
||||
joinedRooms: RoomsSelectors.getJoinedRooms(state),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(LeftNav);
|
||||
BIN
webclient/src/containers/Layout/logo.png
Normal file
BIN
webclient/src/containers/Layout/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Loading…
Add table
Add a link
Reference in a new issue