mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Reformatted project using prettier plugin
This commit is contained in:
@@ -1,42 +1,112 @@
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowCircleDown } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export function OlAccordion(props: {
|
||||
title: string,
|
||||
children?: JSX.Element | JSX.Element[],
|
||||
showArrows?: boolean
|
||||
title: string;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
showArrows?: boolean;
|
||||
}) {
|
||||
var [open, setOpen] = useState(false);
|
||||
var [scrolledUp, setScrolledUp] = useState(true);
|
||||
var [scrolledDown, setScrolledDown] = useState(false);
|
||||
var [open, setOpen] = useState(false);
|
||||
var [scrolledUp, setScrolledUp] = useState(true);
|
||||
var [scrolledDown, setScrolledDown] = useState(false);
|
||||
|
||||
var contentRef = useRef(null);
|
||||
var contentRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
contentRef.current && (contentRef.current as HTMLElement).children[0]?.addEventListener('scroll', (e: any) => {
|
||||
if (e.target.clientHeight < e.target.scrollHeight) {
|
||||
setScrolledDown(e.target.scrollTop === (e.target.scrollHeight - e.target.offsetHeight));
|
||||
setScrolledUp(e.target.scrollTop === 0);
|
||||
}
|
||||
})
|
||||
})
|
||||
useEffect(() => {
|
||||
contentRef.current &&
|
||||
(contentRef.current as HTMLElement).children[0]?.addEventListener(
|
||||
"scroll",
|
||||
(e: any) => {
|
||||
if (e.target.clientHeight < e.target.scrollHeight) {
|
||||
setScrolledDown(
|
||||
e.target.scrollTop ===
|
||||
e.target.scrollHeight - e.target.offsetHeight
|
||||
);
|
||||
setScrolledUp(e.target.scrollTop === 0);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return <div className="bg-white dark:bg-transparent text-gray-900 dark:text-white">
|
||||
<h3>
|
||||
<button type="button" onClick={() => setOpen(!open)} className="flex items-center justify-between w-full py-2 rtl:text-right text-gray-700 border-gray-200 dark:border-gray-700 dark:text-white gap-3 hover:dark:underline hover:dark:underline-offset-4 hover:dark:underline-gray-700">
|
||||
<span className="font-normal">{props.title}</span>
|
||||
<svg data-open={open} className="w-3 h-3 dark:text-olympus-50 -rotate-180 data-[open='false']:-rotate-90 shrink-0 transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
||||
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5 5 1 1 5" />
|
||||
</svg>
|
||||
</button>
|
||||
</h3>
|
||||
<div className={open? "": "hidden"}>
|
||||
{props.showArrows && <div className="rotate-180"> {!scrolledUp && <FontAwesomeIcon icon={faArrowCircleDown} className="text-white animate-bounce opacity-20 absolute w-full"/>}</div>}
|
||||
<div ref={contentRef} className="py-2 border-gray-200 dark:border-gray-700">
|
||||
{props.children}
|
||||
</div>
|
||||
{props.showArrows && <div>{!scrolledDown && <FontAwesomeIcon icon={faArrowCircleDown} className="text-white animate-bounce opacity-20 absolute w-full"/>}</div>}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
bg-white text-gray-900
|
||||
dark:bg-transparent dark:text-white
|
||||
`}
|
||||
>
|
||||
<h3>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(!open)}
|
||||
className={`
|
||||
flex w-full items-center justify-between gap-3 border-gray-200 py-2
|
||||
text-gray-700
|
||||
dark:border-gray-700 dark:text-white
|
||||
hover:dark:underline hover:dark:underline-offset-4
|
||||
hover:dark:underline-gray-700
|
||||
rtl:text-right
|
||||
`}
|
||||
>
|
||||
<span className="font-normal">{props.title}</span>
|
||||
<svg
|
||||
data-open={open}
|
||||
className={`
|
||||
h-3 w-3 shrink-0 -rotate-180 transition-transform
|
||||
dark:text-olympus-50
|
||||
data-[open='false']:-rotate-90
|
||||
`}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 10 6"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 5 5 1 1 5"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</h3>
|
||||
<div className={open ? "" : "hidden"}>
|
||||
{props.showArrows && (
|
||||
<div className="rotate-180">
|
||||
{" "}
|
||||
{!scrolledUp && (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowCircleDown}
|
||||
className={`
|
||||
absolute w-full animate-bounce text-white opacity-20
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
ref={contentRef}
|
||||
className={`
|
||||
border-gray-200 py-2
|
||||
dark:border-gray-700
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
{props.showArrows && (
|
||||
<div>
|
||||
{!scrolledDown && (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowCircleDown}
|
||||
className={`
|
||||
absolute w-full animate-bounce text-white opacity-20
|
||||
`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,19 +3,37 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React from "react";
|
||||
|
||||
export function OlButtonGroup(props: {
|
||||
children?: JSX.Element | JSX.Element[]
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
}) {
|
||||
return <div className="inline-flex rounded-md shadow-sm" >
|
||||
{props.children}
|
||||
</div>
|
||||
return (
|
||||
<div className="inline-flex rounded-md shadow-sm">{props.children}</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function OlButtonGroupItem(props: {
|
||||
icon: IconProp
|
||||
active: boolean,
|
||||
onClick: () => void
|
||||
icon: IconProp;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return <button onClick={props.onClick} type="button" data-active={props.active} className="h-11 w-11 first-of-type:rounded-s-md last-of-type:rounded-e-md py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-700 dark:data-[active='true']:bg-blue-500 dark:data-[active='true']:border-none dark:border-gray-600 dark:text-white dark:hover:text-white dark:hover:bg-gray-500 dark:focus:ring-blue-500 dark:focus:text-white">
|
||||
<FontAwesomeIcon icon={props.icon} />
|
||||
return (
|
||||
<button
|
||||
onClick={props.onClick}
|
||||
type="button"
|
||||
data-active={props.active}
|
||||
className={`
|
||||
h-11 w-11 border border-gray-200 bg-white py-2 text-sm font-medium
|
||||
text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white
|
||||
dark:hover:bg-gray-500 dark:hover:text-white dark:focus:text-white
|
||||
dark:focus:ring-blue-500 dark:data-[active='true']:border-none
|
||||
dark:data-[active='true']:bg-blue-500
|
||||
first-of-type:rounded-s-md
|
||||
focus:z-10 focus:text-blue-700 focus:ring-2 focus:ring-blue-700
|
||||
hover:bg-gray-100 hover:text-blue-700
|
||||
last-of-type:rounded-e-md
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon icon={props.icon} />
|
||||
</button>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,54 +1,116 @@
|
||||
import React from "react";
|
||||
import { faSkull, faCamera, faFlag, faLink, faUnlink, faAngleDoubleRight, faExclamationCircle, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import {
|
||||
faSkull,
|
||||
faCamera,
|
||||
faFlag,
|
||||
faLink,
|
||||
faUnlink,
|
||||
faAngleDoubleRight,
|
||||
faExclamationCircle,
|
||||
faInfoCircle,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
// Error message callout, only to be used for error messages
|
||||
export function ErrorCallout(props: {
|
||||
title?: string,
|
||||
description?: string
|
||||
}) {
|
||||
return <div className="flex flex-row gap-2 w-fit bg-gray-300 dark:bg-gray-800 p-4 rounded-md border-[1px] border-red-800 text-red-700 dark:text-red-500 w-full">
|
||||
{props.title && (<FontAwesomeIcon className="mt-1" icon={faExclamationCircle}></FontAwesomeIcon>)}
|
||||
<div className="flex flex-col font-bold text-pretty content-center items-start gap-2">
|
||||
{props.title}
|
||||
<div className="flex text-xs font-medium whitespace-pre-line text-red-500">
|
||||
{props.description}
|
||||
</div>
|
||||
export function ErrorCallout(props: { title?: string; description?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-red-800
|
||||
bg-gray-300 p-4 text-red-700
|
||||
dark:bg-gray-800 dark:text-red-500
|
||||
`}
|
||||
>
|
||||
{props.title && (
|
||||
<FontAwesomeIcon
|
||||
className="mt-1"
|
||||
icon={faExclamationCircle}
|
||||
></FontAwesomeIcon>
|
||||
)}
|
||||
<div
|
||||
className={`
|
||||
flex flex-col content-center items-start gap-2 text-pretty font-bold
|
||||
`}
|
||||
>
|
||||
{props.title}
|
||||
<div
|
||||
className={`
|
||||
flex whitespace-pre-line text-xs font-medium text-red-500
|
||||
`}
|
||||
>
|
||||
{props.description}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// General information callout for something that is just nice to know
|
||||
export function InfoCallout(props: {
|
||||
title?: string,
|
||||
description?: string
|
||||
}) {
|
||||
return <div className="flex flex-row gap-2 w-fit bg-gray-300 dark:bg-gray-800 p-4 rounded-md border-[1px] border-blue-800 text-blue-400 dark:text-blue-400 w-full">
|
||||
{props.title && (<FontAwesomeIcon className="mt-1" icon={faInfoCircle}></FontAwesomeIcon>)}
|
||||
<div className="flex flex-col font-medium text-pretty content-center items-start gap-2">
|
||||
{props.title}
|
||||
{props.description && (<div className="flex text-xs font-medium whitespace-pre-line">
|
||||
{props.description}
|
||||
</div>)}
|
||||
</div>
|
||||
export function InfoCallout(props: { title?: string; description?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-blue-800
|
||||
bg-gray-300 p-4 text-blue-400
|
||||
dark:bg-gray-800 dark:text-blue-400
|
||||
`}
|
||||
>
|
||||
{props.title && (
|
||||
<FontAwesomeIcon className="mt-1" icon={faInfoCircle}></FontAwesomeIcon>
|
||||
)}
|
||||
<div
|
||||
className={`
|
||||
flex flex-col content-center items-start gap-2 text-pretty font-medium
|
||||
`}
|
||||
>
|
||||
{props.title}
|
||||
{props.description && (
|
||||
<div
|
||||
className={`
|
||||
flex whitespace-pre-line text-xs font-medium
|
||||
`}
|
||||
>
|
||||
{props.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Used for the "You are playing as BLUE/RED Commander" callouts, only on the login page. Accepted values for coalition are 'blue' and 'red'.
|
||||
export function CommandCallout(props: {
|
||||
coalition?: string,
|
||||
}) {
|
||||
return <div className="flex flex-row gap-2 w-fit bg-gray-300 dark:bg-gray-800 p-4 rounded-md border-[1px] border-gray-700 text-gray-400 dark:text-gray-400 w-full">
|
||||
<FontAwesomeIcon className="mt-1" icon={faAngleDoubleRight}></FontAwesomeIcon>
|
||||
<div className="font-medium text-pretty content-center items-start gap-2 whitespace-break-spaces">
|
||||
You are playing as
|
||||
{props.coalition=='blue' ? (
|
||||
<div className="inline-block font-bold text-blue-500"> BLUE COMMANDER </div>
|
||||
) : (
|
||||
<div className="inline-block font-bold text-red-500"> RED COMMANDER </div>
|
||||
)}
|
||||
|
||||
|
||||
</div>
|
||||
export function CommandCallout(props: { coalition?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-gray-700
|
||||
bg-gray-300 p-4 text-gray-400
|
||||
dark:bg-gray-800 dark:text-gray-400
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="mt-1"
|
||||
icon={faAngleDoubleRight}
|
||||
></FontAwesomeIcon>
|
||||
<div
|
||||
className={`
|
||||
content-center items-start gap-2 whitespace-break-spaces text-pretty
|
||||
font-medium
|
||||
`}
|
||||
>
|
||||
You are playing as
|
||||
{props.coalition == "blue" ? (
|
||||
<div className="inline-block font-bold text-blue-500">
|
||||
{" "}
|
||||
BLUE COMMANDER{" "}
|
||||
</div>
|
||||
) : (
|
||||
<div className="inline-block font-bold text-red-500">
|
||||
{" "}
|
||||
RED COMMANDER{" "}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import React, { ChangeEvent } from "react";
|
||||
|
||||
export function OlCheckbox(props: {
|
||||
checked: boolean,
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
checked: boolean;
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
}) {
|
||||
return <input onChange={props.onChange}
|
||||
type="checkbox"
|
||||
checked={props.checked}
|
||||
value=""
|
||||
className="w-4 h-4 my-auto text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 cursor-pointer"
|
||||
return (
|
||||
<input
|
||||
onChange={props.onChange}
|
||||
type="checkbox"
|
||||
checked={props.checked}
|
||||
value=""
|
||||
className={`
|
||||
my-auto h-4 w-4 cursor-pointer rounded border-gray-300 bg-gray-100
|
||||
text-blue-600
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800
|
||||
dark:focus:ring-blue-600
|
||||
focus:ring-2 focus:ring-blue-500
|
||||
`}
|
||||
/>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,21 +2,47 @@ import React from "react";
|
||||
import { Coalition } from "../../types/types";
|
||||
|
||||
export function OlCoalitionToggle(props: {
|
||||
coalition: Coalition | undefined,
|
||||
onClick: () => void
|
||||
coalition: Coalition | undefined;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return <div className="inline-flex items-center cursor-pointer" onClick={props.onClick}>
|
||||
<button className="sr-only peer" />
|
||||
<div data-flash={props.coalition === undefined} data-coalition={props.coalition ?? 'blue'} className={"relative w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300 " +
|
||||
"dark:peer-focus:ring-blue-800 rounded-full peer " +
|
||||
" after:content-[''] after:absolute after:top-0.5 after:start-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 " +
|
||||
"after:w-6 after:transition-all dark:border-gray-600 " +
|
||||
"data-[coalition='neutral']:after:translate-x-[50%] rtl:data-[coalition='neutral']:after:-translate-x-[50%] data-[coalition='neutral']:after:border-white " +
|
||||
"data-[coalition='red']:after:translate-x-full rtl:data-[coalition='red']:after:-translate-x-full data-[coalition='red']:after:border-white " +
|
||||
" data-[coalition='blue']:bg-blue-600 data-[coalition='neutral']:bg-gray-400 data-[coalition='red']:bg-red-500"}>
|
||||
</div>
|
||||
<span className="ms-3 text-gray-900 dark:text-white data-[flash='true']:after:animate-pulse">
|
||||
{props.coalition? `Coalition (${props.coalition[0].toLocaleUpperCase() + props.coalition.substring(1)})`: "Different values"}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="inline-flex cursor-pointer items-center"
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<button className="peer sr-only" />
|
||||
<div
|
||||
data-flash={props.coalition === undefined}
|
||||
data-coalition={props.coalition ?? "blue"}
|
||||
className={`
|
||||
peer relative h-7 w-14 rounded-full bg-gray-200
|
||||
after:absolute after:start-[4px] after:top-0.5 after:h-6 after:w-6
|
||||
after:rounded-full after:border after:border-gray-300 after:bg-white
|
||||
after:transition-all after:content-['']
|
||||
dark:border-gray-600 dark:peer-focus:ring-blue-800
|
||||
data-[coalition='blue']:bg-blue-600
|
||||
data-[coalition='neutral']:bg-gray-400
|
||||
data-[coalition='neutral']:after:translate-x-[50%]
|
||||
data-[coalition='neutral']:after:border-white
|
||||
data-[coalition='red']:bg-red-500
|
||||
data-[coalition='red']:after:translate-x-full
|
||||
data-[coalition='red']:after:border-white
|
||||
peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300
|
||||
rtl:data-[coalition='neutral']:after:-translate-x-[50%]
|
||||
rtl:data-[coalition='red']:after:-translate-x-full
|
||||
`}
|
||||
></div>
|
||||
<span
|
||||
className={`
|
||||
ms-3 text-gray-900
|
||||
dark:text-white
|
||||
data-[flash='true']:after:animate-pulse
|
||||
`}
|
||||
>
|
||||
{props.coalition
|
||||
? `Coalition (${props.coalition[0].toLocaleUpperCase() + props.coalition.substring(1)})`
|
||||
: "Different values"}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,100 +1,176 @@
|
||||
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
export function OlDropdown(props: {
|
||||
className: string,
|
||||
leftIcon?: IconProp,
|
||||
rightIcon?: IconProp,
|
||||
label: string,
|
||||
children?: JSX.Element | JSX.Element[]
|
||||
className: string;
|
||||
leftIcon?: IconProp;
|
||||
rightIcon?: IconProp;
|
||||
label: string;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
}) {
|
||||
var [open, setOpen] = useState(false);
|
||||
var contentRef = useRef(null);
|
||||
var buttonRef = useRef(null);
|
||||
var [open, setOpen] = useState(false);
|
||||
var contentRef = useRef(null);
|
||||
var buttonRef = useRef(null);
|
||||
|
||||
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
|
||||
/* Reset the position of the content */
|
||||
content.style.left = "0px";
|
||||
content.style.top = "0px";
|
||||
content.style.height = "";
|
||||
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
|
||||
/* Reset the position of the content */
|
||||
content.style.left = "0px";
|
||||
content.style.top = "0px";
|
||||
content.style.height = "";
|
||||
|
||||
/* Get the position and size of the button and the content elements */
|
||||
var [cxl, cyt, cxr, cyb, cw, ch] = [content.getBoundingClientRect().x, content.getBoundingClientRect().y, content.getBoundingClientRect().x + content.clientWidth, content.getBoundingClientRect().y + content.clientHeight, content.clientWidth, content.clientHeight];
|
||||
var [bxl, byt, bxr, byb, bbw, bh] = [button.getBoundingClientRect().x, button.getBoundingClientRect().y, button.getBoundingClientRect().x + button.clientWidth, button.getBoundingClientRect().y + button.clientHeight, button.clientWidth, button.clientHeight];
|
||||
/* Get the position and size of the button and the content elements */
|
||||
var [cxl, cyt, cxr, cyb, cw, ch] = [
|
||||
content.getBoundingClientRect().x,
|
||||
content.getBoundingClientRect().y,
|
||||
content.getBoundingClientRect().x + content.clientWidth,
|
||||
content.getBoundingClientRect().y + content.clientHeight,
|
||||
content.clientWidth,
|
||||
content.clientHeight,
|
||||
];
|
||||
var [bxl, byt, bxr, byb, bbw, bh] = [
|
||||
button.getBoundingClientRect().x,
|
||||
button.getBoundingClientRect().y,
|
||||
button.getBoundingClientRect().x + button.clientWidth,
|
||||
button.getBoundingClientRect().y + button.clientHeight,
|
||||
button.clientWidth,
|
||||
button.clientHeight,
|
||||
];
|
||||
|
||||
/* Limit the maximum height */
|
||||
if (ch > 400) {
|
||||
ch = 400;
|
||||
content.style.height = `${ch}px`;
|
||||
}
|
||||
|
||||
/* Compute the horizontal position of the center of the button and the content */
|
||||
var cxc = (cxl + cxr) / 2;
|
||||
var bxc = (bxl + bxr) / 2;
|
||||
|
||||
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
|
||||
var offsetX = bxc - cxc;
|
||||
var offsetY = byb - cyt + 8;
|
||||
|
||||
/* Compute the new position of the left and right margins of the content */
|
||||
cxl += offsetX;
|
||||
cxr += offsetX;
|
||||
cyb += offsetY;
|
||||
|
||||
/* Try and move the content so it is inside the screen */
|
||||
if (cxl < 0)
|
||||
offsetX -= cxl;
|
||||
if (cxr > window.innerWidth)
|
||||
offsetX -= (cxr - window.innerWidth)
|
||||
if (cyb > window.innerHeight)
|
||||
offsetY -= bh + ch + 16;
|
||||
|
||||
/* Apply the offset */
|
||||
content.style.left = `${offsetX}px`
|
||||
content.style.top = `${offsetY}px`
|
||||
/* Limit the maximum height */
|
||||
if (ch > 400) {
|
||||
ch = 400;
|
||||
content.style.height = `${ch}px`;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current && buttonRef.current) {
|
||||
const content = contentRef.current as HTMLDivElement;
|
||||
const button = buttonRef.current as HTMLButtonElement;
|
||||
/* Compute the horizontal position of the center of the button and the content */
|
||||
var cxc = (cxl + cxr) / 2;
|
||||
var bxc = (bxl + bxr) / 2;
|
||||
|
||||
setPosition(content, button);
|
||||
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
|
||||
var offsetX = bxc - cxc;
|
||||
var offsetY = byb - cyt + 8;
|
||||
|
||||
/* Register click events to automatically close the dropdown when clicked anywhere outside of it */
|
||||
document.addEventListener('click', function (event) {
|
||||
const target = event.target;
|
||||
if (target && /*!content.contains(target as HTMLElement) &&*/ !button.contains(target as HTMLElement)) {
|
||||
setOpen(false);
|
||||
}
|
||||
});
|
||||
/* Compute the new position of the left and right margins of the content */
|
||||
cxl += offsetX;
|
||||
cxr += offsetX;
|
||||
cyb += offsetY;
|
||||
|
||||
/* Try and move the content so it is inside the screen */
|
||||
if (cxl < 0) offsetX -= cxl;
|
||||
if (cxr > window.innerWidth) offsetX -= cxr - window.innerWidth;
|
||||
if (cyb > window.innerHeight) offsetY -= bh + ch + 16;
|
||||
|
||||
/* Apply the offset */
|
||||
content.style.left = `${offsetX}px`;
|
||||
content.style.top = `${offsetY}px`;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current && buttonRef.current) {
|
||||
const content = contentRef.current as HTMLDivElement;
|
||||
const button = buttonRef.current as HTMLButtonElement;
|
||||
|
||||
setPosition(content, button);
|
||||
|
||||
/* Register click events to automatically close the dropdown when clicked anywhere outside of it */
|
||||
document.addEventListener("click", function (event) {
|
||||
const target = event.target;
|
||||
if (
|
||||
target &&
|
||||
/*!content.contains(target as HTMLElement) &&*/ !button.contains(
|
||||
target as HTMLElement
|
||||
)
|
||||
) {
|
||||
setOpen(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return <div className={(props.className ?? "") + " relative"}>
|
||||
<button ref={buttonRef} onClick={() => { setOpen(!open) }} className={"w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-2 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center justify-between border dark:border-gray-700 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:focus:ring-blue-800"} type="button">
|
||||
{props.leftIcon && <FontAwesomeIcon icon={props.leftIcon} className="mr-3" />}
|
||||
<span className="text-nowrap text-ellipsis overflow-hidden">
|
||||
{props.label}
|
||||
</span>
|
||||
<svg className="flex-none w-2.5 h-2.5 ms-3 data-[open='true']:-scale-y-100 transition-transform ml-auto" data-open={open} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
||||
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m1 1 4 4 4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
return (
|
||||
<div className={(props.className ?? "") + "relative"}>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onClick={() => {
|
||||
setOpen(!open);
|
||||
}}
|
||||
className={`
|
||||
inline-flex w-full items-center justify-between rounded-lg border
|
||||
bg-blue-700 px-5 py-2.5 text-center text-sm font-medium text-white
|
||||
dark:border-gray-700 dark:bg-gray-700 dark:text-gray-100
|
||||
dark:hover:bg-gray-600 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
type="button"
|
||||
>
|
||||
{props.leftIcon && (
|
||||
<FontAwesomeIcon
|
||||
icon={props.leftIcon}
|
||||
className={`mr-3`}
|
||||
/>
|
||||
)}
|
||||
<span className="overflow-hidden text-ellipsis text-nowrap">
|
||||
{props.label}
|
||||
</span>
|
||||
<svg
|
||||
className={`
|
||||
ml-auto ms-3 h-2.5 w-2.5 flex-none transition-transform
|
||||
data-[open='true']:-scale-y-100
|
||||
`}
|
||||
data-open={open}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 10 6"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="m1 1 4 4 4-4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div ref={contentRef} data-open={open} className="absolute z-ui-2 w-full p-2 data-[open='false']:hidden bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 overflow-y-scroll">
|
||||
<div className="text-sm text-gray-700 dark:text-gray-200 w-full h-fit">
|
||||
{props.children}
|
||||
</div>
|
||||
<div
|
||||
ref={contentRef}
|
||||
data-open={open}
|
||||
className={`
|
||||
absolute z-ui-2 w-full divide-y divide-gray-100 overflow-y-scroll
|
||||
rounded-lg bg-white p-2 shadow
|
||||
dark:bg-gray-700
|
||||
data-[open='false']:hidden
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
h-fit w-full text-sm text-gray-700
|
||||
dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* Conveniency Component for dropdown elements */
|
||||
export function OlDropdownItem(props) {
|
||||
return <button onClick={props.onClick ?? (() => { })} className={(props.className ?? "") + " px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white flex flex-row content-center rounded-md select-none cursor-pointer"}>
|
||||
{props.children}
|
||||
return (
|
||||
<button
|
||||
onClick={props.onClick ?? (() => {})}
|
||||
className={`
|
||||
${props.className ?? ""}
|
||||
flex cursor-pointer select-none flex-row content-center rounded-md px-4
|
||||
py-2
|
||||
dark:hover:bg-gray-600 dark:hover:text-white
|
||||
hover:bg-gray-100
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,14 +1,54 @@
|
||||
import React from "react";
|
||||
|
||||
export function OlLabelToggle(props: {
|
||||
toggled: boolean | undefined,
|
||||
leftLabel: string,
|
||||
rightLabel: string,
|
||||
onClick: () => void
|
||||
toggled: boolean | undefined;
|
||||
leftLabel: string;
|
||||
rightLabel: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return <button onClick={props.onClick} className="border dark:border-gray-600 relative text-sm flex flex-row flex-none contents-center justify-between w-[120px] h-10 border dark:border-transparent dark:bg-gray-700 rounded-md py-[5px] px-1 select-none cursor-pointer focus:ring-2 focus:outline-none focus:ring-blue-300 dark:hover:bg-gray-600 dark:focus:ring-blue-800">
|
||||
<span data-flash={props.toggled === undefined} data-toggled={props.toggled ?? false} className="data-[flash='true']:animate-pulse absolute my-auto h-[28px] w-[54px] bg-blue-500 rounded-md data-[toggled='true']:translate-x-14 transition-transform"></span>
|
||||
<span data-active={!(props.toggled ?? false)} className="my-auto dark:data-[active='true']:text-white font-normal dark:data-[active='false']:text-gray-400 dark:data-[active='false']:text-gray-400 pl-3 z-ui-2 transition-colors">{props.leftLabel}</span>
|
||||
<span data-active={props.toggled ?? false} className="my-auto dark:data-[active='true']:text-white font-normal dark:data-[active='false']:text-gray-400 pr-3.5 z-ui-2 transition-colors">{props.rightLabel}</span>
|
||||
return (
|
||||
<button
|
||||
onClick={props.onClick}
|
||||
className={`
|
||||
relative flex h-10 w-[120px] flex-none cursor-pointer select-none
|
||||
flex-row justify-between rounded-md border border px-1 py-[5px] text-sm
|
||||
contents-center
|
||||
dark:border-gray-600 dark:border-transparent dark:bg-gray-700
|
||||
dark:hover:bg-gray-600 dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-2 focus:ring-blue-300
|
||||
`}
|
||||
>
|
||||
<span
|
||||
data-flash={props.toggled === undefined}
|
||||
data-toggled={props.toggled ?? false}
|
||||
className={`
|
||||
absolute my-auto h-[28px] w-[54px] rounded-md bg-blue-500
|
||||
transition-transform
|
||||
data-[flash='true']:animate-pulse
|
||||
data-[toggled='true']:translate-x-14
|
||||
`}
|
||||
></span>
|
||||
<span
|
||||
data-active={!(props.toggled ?? false)}
|
||||
className={`
|
||||
my-auto pl-3 font-normal transition-colors z-ui-2
|
||||
dark:data-[active='false']:text-gray-400
|
||||
dark:data-[active='false']:text-gray-400
|
||||
dark:data-[active='true']:text-white
|
||||
`}
|
||||
>
|
||||
{props.leftLabel}
|
||||
</span>
|
||||
<span
|
||||
data-active={props.toggled ?? false}
|
||||
className={`
|
||||
my-auto pr-3.5 font-normal transition-colors z-ui-2
|
||||
dark:data-[active='false']:text-gray-400
|
||||
dark:data-[active='true']:text-white
|
||||
`}
|
||||
>
|
||||
{props.rightLabel}
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,90 @@
|
||||
import React, {ChangeEvent, useEffect, useId} from "react";
|
||||
import React, { ChangeEvent, useEffect, useId } from "react";
|
||||
|
||||
export function OlNumberInput(props: {
|
||||
value: number,
|
||||
min: number,
|
||||
max: number,
|
||||
onDecrease: () => void,
|
||||
onIncrease: () => void,
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
onDecrease: () => void;
|
||||
onIncrease: () => void;
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
}) {
|
||||
return <div className="w-fit">
|
||||
<div className="relative flex items-center max-w-[8rem]">
|
||||
<button type="button" onClick={props.onDecrease} className="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 hover:bg-gray-200 rounded-s-lg p-3 h-10 focus:ring-gray-100 dark:focus:ring-blue-700 focus:ring-2 focus:outline-none">
|
||||
<svg className="w-3 h-3 text-gray-900 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 2">
|
||||
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M1 1h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
<input type="text" onChange={props.onChange} min={props.min} max={props.max} className="bg-gray-50 h-10 text-center text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-700 block w-full py-2.5 border-[2px] dark:border-gray-700 dark:bg-olympus-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-700 dark:focus:border-blue-700" value={props.value} />
|
||||
<button type="button" onClick={props.onIncrease} className="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 hover:bg-gray-200 rounded-e-lg p-3 h-10 focus:ring-gray-100 dark:focus:ring-blue-500 focus:ring-2 focus:outline-none">
|
||||
<svg className="w-3 h-3 text-gray-900 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
|
||||
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 1v16M1 9h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
return (
|
||||
<div className="w-fit">
|
||||
<div className="relative flex max-w-[8rem] items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onDecrease}
|
||||
className={`
|
||||
h-10 rounded-s-lg bg-gray-100 p-3
|
||||
dark:bg-gray-700 dark:hover:bg-gray-600 dark:focus:ring-blue-700
|
||||
focus:outline-none focus:ring-2 focus:ring-gray-100
|
||||
hover:bg-gray-200
|
||||
`}
|
||||
>
|
||||
<svg
|
||||
className={`
|
||||
h-3 w-3 text-gray-900
|
||||
dark:text-white
|
||||
`}
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 18 2"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M1 1h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
onChange={props.onChange}
|
||||
min={props.min}
|
||||
max={props.max}
|
||||
className={`
|
||||
block h-10 w-full border-[2px] bg-gray-50 py-2.5 text-center text-sm
|
||||
text-gray-900
|
||||
dark:border-gray-700 dark:bg-olympus-600 dark:text-white
|
||||
dark:placeholder-gray-400 dark:focus:border-blue-700
|
||||
dark:focus:ring-blue-700
|
||||
focus:border-blue-700 focus:ring-blue-500
|
||||
`}
|
||||
value={props.value}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onIncrease}
|
||||
className={`
|
||||
h-10 rounded-e-lg bg-gray-100 p-3
|
||||
dark:bg-gray-700 dark:hover:bg-gray-600 dark:focus:ring-blue-500
|
||||
focus:outline-none focus:ring-2 focus:ring-gray-100
|
||||
hover:bg-gray-200
|
||||
`}
|
||||
>
|
||||
<svg
|
||||
className={`
|
||||
h-3 w-3 text-gray-900
|
||||
dark:text-white
|
||||
`}
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 18 18"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 1v16M1 9h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
import React, { ChangeEvent, useEffect, useRef } from "react";
|
||||
|
||||
export function OlRangeSlider(props: {
|
||||
value: number | undefined,
|
||||
min?: number,
|
||||
max?: number,
|
||||
step?: number,
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
value: number | undefined;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
}) {
|
||||
var elementRef = useRef(null);
|
||||
var elementRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (elementRef.current) {
|
||||
const sliderEl = elementRef.current as HTMLInputElement;
|
||||
const tempSliderValue = Number(sliderEl.value);
|
||||
const progress = (tempSliderValue / Number(sliderEl.max)) * 100;
|
||||
sliderEl.style.background = `linear-gradient(to right, #3F83F8 ${progress}%, #4B5563 ${progress}%)`;
|
||||
}
|
||||
})
|
||||
useEffect(() => {
|
||||
if (elementRef.current) {
|
||||
const sliderEl = elementRef.current as HTMLInputElement;
|
||||
const tempSliderValue = Number(sliderEl.value);
|
||||
const progress = (tempSliderValue / Number(sliderEl.max)) * 100;
|
||||
sliderEl.style.background = `linear-gradient(to right, #3F83F8 ${progress}%, #4B5563 ${progress}%)`;
|
||||
}
|
||||
});
|
||||
|
||||
return <input type="range"
|
||||
ref={elementRef}
|
||||
onChange={props.onChange}
|
||||
value={props.value ?? 0}
|
||||
min={props.min ?? 0}
|
||||
max={props.max ?? 100}
|
||||
step={props.step ?? 1}
|
||||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700" />
|
||||
}
|
||||
return (
|
||||
<input
|
||||
type="range"
|
||||
ref={elementRef}
|
||||
onChange={props.onChange}
|
||||
value={props.value ?? 0}
|
||||
min={props.min ?? 0}
|
||||
max={props.max ?? 100}
|
||||
step={props.step ?? 1}
|
||||
className={`
|
||||
h-2 w-full cursor-pointer appearance-none rounded-lg bg-gray-200
|
||||
dark:bg-gray-700
|
||||
`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,62 @@
|
||||
import { faMultiply, faSearch } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import React, {ChangeEvent, useId, useRef} from "react"
|
||||
import { faMultiply, faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React, { ChangeEvent, useId, useRef } from "react";
|
||||
|
||||
export function OlSearchBar(props: {
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
}) {
|
||||
const searchId = useId();
|
||||
const inputRef = useRef(null);
|
||||
const searchId = useId();
|
||||
const inputRef = useRef(null);
|
||||
|
||||
function resetSearch() {
|
||||
inputRef.current && ((inputRef.current as HTMLInputElement).value = '');
|
||||
}
|
||||
function resetSearch() {
|
||||
inputRef.current && ((inputRef.current as HTMLInputElement).value = "");
|
||||
}
|
||||
|
||||
return <div>
|
||||
<label htmlFor={searchId} className="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Search</label>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-y-0 start-0 flex items-center ps-4 pointer-events-none">
|
||||
<FontAwesomeIcon icon={faSearch} className="dark:text-gray-400" />
|
||||
</div>
|
||||
<input type="search" ref={inputRef} id={searchId} onChange= {props.onChange} className="block w-full p-3 mb-2 ps-10 text-sm text-gray-900 border border-gray-300 rounded-full bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Search" required />
|
||||
<FontAwesomeIcon icon={faMultiply} className="absolute cursor-pointer end-4 bottom-4 my-auto dark:text-gray-400 text-sm" onClick={resetSearch}/>
|
||||
return (
|
||||
<div>
|
||||
<label
|
||||
htmlFor={searchId}
|
||||
className={`
|
||||
sr-only mb-2 text-sm font-medium text-gray-900
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Search
|
||||
</label>
|
||||
<div className="relative">
|
||||
<div
|
||||
className={`
|
||||
pointer-events-none absolute inset-y-0 start-0 flex items-center
|
||||
ps-4
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faSearch} className="dark:text-gray-400" />
|
||||
</div>
|
||||
<input
|
||||
type="search"
|
||||
ref={inputRef}
|
||||
id={searchId}
|
||||
onChange={props.onChange}
|
||||
className={`
|
||||
mb-2 block w-full rounded-full border border-gray-300 bg-gray-50 p-3
|
||||
ps-10 text-sm text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700 dark:text-white
|
||||
dark:placeholder-gray-400 dark:focus:border-blue-500
|
||||
dark:focus:ring-blue-500
|
||||
focus:border-blue-500 focus:ring-blue-500
|
||||
`}
|
||||
placeholder="Search"
|
||||
required
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faMultiply}
|
||||
className={`
|
||||
absolute bottom-4 end-4 my-auto cursor-pointer text-sm
|
||||
dark:text-gray-400
|
||||
`}
|
||||
onClick={resetSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,61 +1,150 @@
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faExternalLink, faLock, faLockOpen, faUnlock, faUnlockAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import React, { useRef, useState } from "react"
|
||||
import {
|
||||
faExternalLink,
|
||||
faLock,
|
||||
faLockOpen,
|
||||
faUnlock,
|
||||
faUnlockAlt,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { OlTooltip } from "./oltooltip";
|
||||
|
||||
export function OlStateButton(props: {
|
||||
className?: string,
|
||||
checked: boolean,
|
||||
icon: IconProp,
|
||||
tooltip: string,
|
||||
onClick: () => void
|
||||
className?: string;
|
||||
checked: boolean;
|
||||
icon: IconProp;
|
||||
tooltip: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
var [hover, setHover] = useState(false);
|
||||
var buttonRef = useRef(null);
|
||||
var [hover, setHover] = useState(false);
|
||||
var buttonRef = useRef(null);
|
||||
|
||||
const className = (props.className ?? '') + ` h-[40px] w-[40px] flex-none font-medium rounded-md text-lg dark:bg-olympus-600 dark:hover:bg-olympus-300 dark:data-[checked='true']:bg-blue-500 dark:data-[checked='true']:text-white dark:text-gray-300 dark:border-gray-600 `;
|
||||
const className =
|
||||
(props.className ?? "") +
|
||||
`
|
||||
h-[40px] w-[40px] flex-none rounded-md text-lg font-medium
|
||||
dark:border-gray-600 dark:bg-olympus-600 dark:text-gray-300
|
||||
dark:hover:bg-olympus-300 dark:data-[checked='true']:bg-blue-500
|
||||
dark:data-[checked='true']:text-white
|
||||
`;
|
||||
|
||||
return <><button ref={buttonRef} onClick={() => { props.onClick(); setHover(false); }} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onClick={() => {
|
||||
props.onClick();
|
||||
setHover(false);
|
||||
}}
|
||||
data-checked={props.checked}
|
||||
type="button"
|
||||
className={className}
|
||||
onMouseEnter={() => {
|
||||
setHover(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHover(false);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={props.icon} />
|
||||
</button>
|
||||
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
|
||||
</button>
|
||||
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function OlRoundStateButton(props: {
|
||||
className?: string,
|
||||
checked: boolean,
|
||||
icon: IconProp,
|
||||
tooltip: string,
|
||||
onClick: () => void
|
||||
className?: string;
|
||||
checked: boolean;
|
||||
icon: IconProp;
|
||||
tooltip: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
var [hover, setHover] = useState(false);
|
||||
var buttonRef = useRef(null);
|
||||
var [hover, setHover] = useState(false);
|
||||
var buttonRef = useRef(null);
|
||||
|
||||
const className = (props.className ?? '') + ` h-8 w-8 flex-none m-auto border-2 border-gray-900 font-medium rounded-full text-sm dark:bg-[transparent] dark:data-[checked='true']:bg-white dark:text-gray-400 dark:data-[checked='true']:text-gray-900 dark:data-[checked='true']:border-white dark:border-gray-400 dark:data-[checked='true']:hover:bg-gray-200 dark:data-[checked='true']:hover:border-gray-200 dark:hover:bg-gray-800`;
|
||||
const className =
|
||||
(props.className ?? "") +
|
||||
`
|
||||
m-auto h-8 w-8 flex-none rounded-full border-2 border-gray-900 text-sm
|
||||
font-medium
|
||||
dark:border-gray-400 dark:bg-[transparent] dark:text-gray-400
|
||||
dark:hover:bg-gray-800 dark:data-[checked='true']:border-white
|
||||
dark:data-[checked='true']:bg-white
|
||||
dark:data-[checked='true']:text-gray-900
|
||||
dark:data-[checked='true']:hover:border-gray-200
|
||||
dark:data-[checked='true']:hover:bg-gray-200
|
||||
`;
|
||||
|
||||
return <><button ref={buttonRef} onClick={() => { props.onClick(); setHover(false); }} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onClick={() => {
|
||||
props.onClick();
|
||||
setHover(false);
|
||||
}}
|
||||
data-checked={props.checked}
|
||||
type="button"
|
||||
className={className}
|
||||
onMouseEnter={() => {
|
||||
setHover(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHover(false);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon className="pt-[3px]" icon={props.icon} />
|
||||
</button>
|
||||
{ hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} /> }
|
||||
</button>
|
||||
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function OlLockStateButton(props: {
|
||||
className?: string,
|
||||
checked: boolean,
|
||||
tooltip: string,
|
||||
onClick: () => void
|
||||
className?: string;
|
||||
checked: boolean;
|
||||
tooltip: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
var [hover, setHover] = useState(false);
|
||||
var buttonRef = useRef(null);
|
||||
var [hover, setHover] = useState(false);
|
||||
var buttonRef = useRef(null);
|
||||
|
||||
const className = (props.className ?? '') + ` h-8 w-8 flex-none m-auto border-gray-900 font-medium rounded-full text-sm dark:bg-red-500 dark:data-[checked='true']:bg-green-500 dark:text-olympus-900 dark:data-[checked='true']:text-green-900 dark:data-[checked='true']:hover:bg-green-400 dark:hover:bg-red-400`;
|
||||
const className =
|
||||
(props.className ?? "") +
|
||||
`
|
||||
m-auto h-8 w-8 flex-none rounded-full border-gray-900 text-sm font-medium
|
||||
dark:bg-red-500 dark:text-olympus-900 dark:hover:bg-red-400
|
||||
dark:data-[checked='true']:bg-green-500
|
||||
dark:data-[checked='true']:text-green-900
|
||||
dark:data-[checked='true']:hover:bg-green-400
|
||||
`;
|
||||
|
||||
return <><button ref={buttonRef} onClick={() => { props.onClick(); setHover(false); }} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
|
||||
<FontAwesomeIcon className="pt-[3px]" icon={props.checked == true ? faUnlockAlt : faLock} />
|
||||
</button>
|
||||
{ hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} /> }
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onClick={() => {
|
||||
props.onClick();
|
||||
setHover(false);
|
||||
}}
|
||||
data-checked={props.checked}
|
||||
type="button"
|
||||
className={className}
|
||||
onMouseEnter={() => {
|
||||
setHover(true);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setHover(false);
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="pt-[3px]"
|
||||
icon={props.checked == true ? faUnlockAlt : faLock}
|
||||
/>
|
||||
</button>
|
||||
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
|
||||
</>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
import React from "react";
|
||||
|
||||
export function OlToggle(props: {
|
||||
toggled: boolean | undefined,
|
||||
onClick: () => void
|
||||
toggled: boolean | undefined;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return <div className="inline-flex items-center cursor-pointer" onClick={props.onClick}>
|
||||
<button className="sr-only peer" />
|
||||
<div data-flash={props.toggled === undefined} data-toggled={props.toggled ?? false} className={"relative w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300 " +
|
||||
"dark:peer-focus:ring-blue-800 rounded-full peer " +
|
||||
" after:content-[''] after:absolute after:top-0.5 after:start-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 " +
|
||||
"after:w-6 after:transition-all dark:border-gray-600 " +
|
||||
"data-[toggled='true']:after:translate-x-full rtl:data-[toggled='true']:after:-translate-x-full data-[toggled='true']:after:border-white " +
|
||||
"data-[toggled='false']:bg-gray-500 dark:data-[toggled='true']:bg-blue-500 data-[flash='true']:after:animate-pulse"}></div>
|
||||
</div>
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="inline-flex cursor-pointer items-center"
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<button className="peer sr-only" />
|
||||
<div
|
||||
data-flash={props.toggled === undefined}
|
||||
data-toggled={props.toggled ?? false}
|
||||
className={`
|
||||
peer relative h-7 w-14 rounded-full bg-gray-200
|
||||
after:absolute after:start-[4px] after:top-0.5 after:h-6 after:w-6
|
||||
after:rounded-full after:border after:border-gray-300 after:bg-white
|
||||
after:transition-all after:content-['']
|
||||
dark:border-gray-600 dark:peer-focus:ring-blue-800
|
||||
dark:data-[toggled='true']:bg-blue-500
|
||||
data-[flash='true']:after:animate-pulse
|
||||
data-[toggled='false']:bg-gray-500
|
||||
data-[toggled='true']:after:translate-x-full
|
||||
data-[toggled='true']:after:border-white
|
||||
peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300
|
||||
rtl:data-[toggled='true']:after:-translate-x-full
|
||||
`}
|
||||
></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,63 +1,85 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
export function OlTooltip(props: {
|
||||
content: string,
|
||||
buttonRef: React.MutableRefObject<null>
|
||||
content: string;
|
||||
buttonRef: React.MutableRefObject<null>;
|
||||
}) {
|
||||
var contentRef = useRef(null);
|
||||
var contentRef = useRef(null);
|
||||
|
||||
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
|
||||
/* Reset the position of the content */
|
||||
content.style.left = "0px";
|
||||
content.style.top = "0px";
|
||||
content.style.height = "";
|
||||
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
|
||||
/* Reset the position of the content */
|
||||
content.style.left = "0px";
|
||||
content.style.top = "0px";
|
||||
content.style.height = "";
|
||||
|
||||
/* Get the position and size of the button and the content elements */
|
||||
var [cxl, cyt, cxr, cyb, cw, ch] = [content.getBoundingClientRect().x, content.getBoundingClientRect().y, content.getBoundingClientRect().x + content.clientWidth, content.getBoundingClientRect().y + content.clientHeight, content.clientWidth, content.clientHeight];
|
||||
var [bxl, byt, bxr, byb, bbw, bh] = [button.getBoundingClientRect().x, button.getBoundingClientRect().y, button.getBoundingClientRect().x + button.clientWidth, button.getBoundingClientRect().y + button.clientHeight, button.clientWidth, button.clientHeight];
|
||||
/* Get the position and size of the button and the content elements */
|
||||
var [cxl, cyt, cxr, cyb, cw, ch] = [
|
||||
content.getBoundingClientRect().x,
|
||||
content.getBoundingClientRect().y,
|
||||
content.getBoundingClientRect().x + content.clientWidth,
|
||||
content.getBoundingClientRect().y + content.clientHeight,
|
||||
content.clientWidth,
|
||||
content.clientHeight,
|
||||
];
|
||||
var [bxl, byt, bxr, byb, bbw, bh] = [
|
||||
button.getBoundingClientRect().x,
|
||||
button.getBoundingClientRect().y,
|
||||
button.getBoundingClientRect().x + button.clientWidth,
|
||||
button.getBoundingClientRect().y + button.clientHeight,
|
||||
button.clientWidth,
|
||||
button.clientHeight,
|
||||
];
|
||||
|
||||
/* Limit the maximum height */
|
||||
if (ch > 400) {
|
||||
ch = 400;
|
||||
content.style.height = `${ch}px`;
|
||||
}
|
||||
|
||||
/* Compute the horizontal position of the center of the button and the content */
|
||||
var cxc = (cxl + cxr) / 2;
|
||||
var bxc = (bxl + bxr) / 2;
|
||||
|
||||
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
|
||||
var offsetX = bxc - cxc;
|
||||
var offsetY = byb - cyt + 8;
|
||||
|
||||
/* Compute the new position of the left and right margins of the content */
|
||||
cxl += offsetX;
|
||||
cxr += offsetX;
|
||||
cyb += offsetY;
|
||||
|
||||
/* Try and move the content so it is inside the screen */
|
||||
if (cxl < 0)
|
||||
offsetX -= cxl;
|
||||
if (cxr > window.innerWidth)
|
||||
offsetX -= (cxr - window.innerWidth)
|
||||
if (cyb > window.innerHeight)
|
||||
offsetY -= bh + ch + 16;
|
||||
|
||||
/* Apply the offset */
|
||||
content.style.left = `${offsetX}px`
|
||||
content.style.top = `${offsetY}px`
|
||||
/* Limit the maximum height */
|
||||
if (ch > 400) {
|
||||
ch = 400;
|
||||
content.style.height = `${ch}px`;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current && props.buttonRef.current) {
|
||||
const content = contentRef.current as HTMLDivElement;
|
||||
const button = props.buttonRef.current as HTMLButtonElement;
|
||||
/* Compute the horizontal position of the center of the button and the content */
|
||||
var cxc = (cxl + cxr) / 2;
|
||||
var bxc = (bxl + bxr) / 2;
|
||||
|
||||
setPosition(content, button);
|
||||
}
|
||||
})
|
||||
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
|
||||
var offsetX = bxc - cxc;
|
||||
var offsetY = byb - cyt + 8;
|
||||
|
||||
return props.content !== "" && <div ref={contentRef} className={`absolute whitespace-nowrap z-ui-4 px-3 py-2 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm dark:bg-gray-700`}>
|
||||
{ props.content }
|
||||
</div>
|
||||
}
|
||||
/* Compute the new position of the left and right margins of the content */
|
||||
cxl += offsetX;
|
||||
cxr += offsetX;
|
||||
cyb += offsetY;
|
||||
|
||||
/* Try and move the content so it is inside the screen */
|
||||
if (cxl < 0) offsetX -= cxl;
|
||||
if (cxr > window.innerWidth) offsetX -= cxr - window.innerWidth;
|
||||
if (cyb > window.innerHeight) offsetY -= bh + ch + 16;
|
||||
|
||||
/* Apply the offset */
|
||||
content.style.left = `${offsetX}px`;
|
||||
content.style.top = `${offsetY}px`;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current && props.buttonRef.current) {
|
||||
const content = contentRef.current as HTMLDivElement;
|
||||
const button = props.buttonRef.current as HTMLButtonElement;
|
||||
|
||||
setPosition(content, button);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
props.content !== "" && (
|
||||
<div
|
||||
ref={contentRef}
|
||||
className={`
|
||||
absolute whitespace-nowrap z-ui-4 rounded-lg bg-gray-900 px-3 py-2
|
||||
text-sm font-medium text-white shadow-sm
|
||||
dark:bg-gray-700
|
||||
`}
|
||||
>
|
||||
{props.content}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,53 @@
|
||||
import React from "react";
|
||||
import React from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { faArrowRightLong, faCaretRight, faCircleArrowRight, faLongArrowAltRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
faArrowRightLong,
|
||||
faCaretRight,
|
||||
faCircleArrowRight,
|
||||
faLongArrowAltRight,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faArrowRight } from "@fortawesome/free-solid-svg-icons/faArrowRight";
|
||||
|
||||
export function OlUnitEntryList(props: {
|
||||
icon: IconProp,
|
||||
blueprint: UnitBlueprint,
|
||||
onClick: () => void
|
||||
icon: IconProp;
|
||||
blueprint: UnitBlueprint;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return <div onClick={props.onClick} className="group relative text-sm cursor-pointer select-none flex justify-between items-center dark:text-gray-300 dark:hover:bg-white dark:hover:bg-olympus-500 px-2 py-2 rounded-sm mr-2">
|
||||
<FontAwesomeIcon icon={props.icon} className="text-sm"></FontAwesomeIcon>
|
||||
<div className="font-normal text-left flex-1 px-2">{props.blueprint.label}</div>
|
||||
<div className="font-bold bg-olympus-800 dark:text-olympus-50 rounded-full px-2 py-0.5 text-xs">{props.blueprint.era === "WW2" ? "WW2" : props.blueprint.era.split(" ").map((word) => {
|
||||
return word.charAt(0).toLocaleUpperCase();
|
||||
})}</div>
|
||||
<FontAwesomeIcon icon={faArrowRight} className="px-1 dark:text-olympus-50 group-hover:translate-x-1 transition-transform"></FontAwesomeIcon>
|
||||
return (
|
||||
<div
|
||||
onClick={props.onClick}
|
||||
className={`
|
||||
group relative mr-2 flex cursor-pointer select-none items-center
|
||||
justify-between rounded-sm px-2 py-2 text-sm
|
||||
dark:text-gray-300 dark:hover:bg-olympus-500 dark:hover:bg-white
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon icon={props.icon} className="text-sm"></FontAwesomeIcon>
|
||||
<div className="flex-1 px-2 text-left font-normal">
|
||||
{props.blueprint.label}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
rounded-full bg-olympus-800 px-2 py-0.5 text-xs font-bold
|
||||
dark:text-olympus-50
|
||||
`}
|
||||
>
|
||||
{props.blueprint.era === "WW2"
|
||||
? "WW2"
|
||||
: props.blueprint.era.split(" ").map((word) => {
|
||||
return word.charAt(0).toLocaleUpperCase();
|
||||
})}
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className={`
|
||||
px-1 transition-transform
|
||||
dark:text-olympus-50
|
||||
group-hover:translate-x-1
|
||||
`}
|
||||
></FontAwesomeIcon>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,28 +3,80 @@ import { UnitBlueprint } from "../../interfaces";
|
||||
import { Coalition } from "../../types/types";
|
||||
|
||||
export function OlUnitSummary(props: {
|
||||
blueprint: UnitBlueprint,
|
||||
coalition: Coalition
|
||||
blueprint: UnitBlueprint;
|
||||
coalition: Coalition;
|
||||
}) {
|
||||
return <div data-coalition={props.coalition} className="relative border-l-4 flex flex-col gap-2 p-2 pt-4 pb-4 items-start shadow-lg bg-white dark:bg-olympus-200/30 data-[coalition='blue']:border-blue-600 data-[coalition='neutral']:border-gray-400 data-[coalition='red']:border-red-500">
|
||||
<div className="flex flex-row gap-2 content-center">
|
||||
<img className="absolute object-cover h-full invert opacity-10 right-5 top-0" src={"images/units/"+props.blueprint.filename} alt="" />
|
||||
<div className="ml-2 my-auto w-full font-semibold tracking-tight text-gray-900 dark:text-white">{props.blueprint.label}</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-between px-2 leading-normal h-fit">
|
||||
<p className="text-sm text-gray-700 dark:text-gray-400 mb-1">{props.blueprint.description}</p>
|
||||
</div>
|
||||
<div className="flex flex-row gap-1 px-2">
|
||||
{props.blueprint.abilities?.split(" ").map((tag) => {
|
||||
return <div key={tag} className="font-bold bg-olympus-800 dark:text-olympus-50 rounded-full px-2 py-0.5 text-xs">
|
||||
{tag}
|
||||
</div>
|
||||
})}
|
||||
|
||||
<div className="font-bold bg-olympus-800 dark:text-olympus-50 rounded-full px-2 py-0.5 text-xs">{props.blueprint.era === "WW2" ? "WW2" : props.blueprint.era.split(" ").map((word) => {
|
||||
return word.charAt(0).toLocaleUpperCase();
|
||||
})}</div>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div
|
||||
data-coalition={props.coalition}
|
||||
className={`
|
||||
relative flex flex-col items-start gap-2 border-l-4 bg-white p-2 pb-4
|
||||
pt-4 shadow-lg
|
||||
dark:bg-olympus-200/30
|
||||
data-[coalition='blue']:border-blue-600
|
||||
data-[coalition='neutral']:border-gray-400
|
||||
data-[coalition='red']:border-red-500
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-row content-center gap-2">
|
||||
<img
|
||||
className={`
|
||||
absolute right-5 top-0 h-full object-cover opacity-10 invert
|
||||
`}
|
||||
src={"images/units/" + props.blueprint.filename}
|
||||
alt=""
|
||||
/>
|
||||
<div
|
||||
className={`
|
||||
my-auto ml-2 w-full font-semibold tracking-tight text-gray-900
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
{props.blueprint.label}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-col justify-between px-2 leading-normal
|
||||
`}
|
||||
>
|
||||
<p
|
||||
className={`
|
||||
mb-1 text-sm text-gray-700
|
||||
dark:text-gray-400
|
||||
`}
|
||||
>
|
||||
{props.blueprint.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row gap-1 px-2">
|
||||
{props.blueprint.abilities?.split(" ").map((tag) => {
|
||||
return (
|
||||
<div
|
||||
key={tag}
|
||||
className={`
|
||||
rounded-full bg-olympus-800 px-2 py-0.5 text-xs font-bold
|
||||
dark:text-olympus-50
|
||||
`}
|
||||
>
|
||||
{tag}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<div
|
||||
className={`
|
||||
rounded-full bg-olympus-800 px-2 py-0.5 text-xs font-bold
|
||||
dark:text-olympus-50
|
||||
`}
|
||||
>
|
||||
{props.blueprint.era === "WW2"
|
||||
? "WW2"
|
||||
: props.blueprint.era.split(" ").map((word) => {
|
||||
return word.charAt(0).toLocaleUpperCase();
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,43 @@
|
||||
import React from 'react'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faArrowRight, faCheckCircle, faExternalLink, faLink, faUnlink } from '@fortawesome/free-solid-svg-icons'
|
||||
import React from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowRight,
|
||||
faCheckCircle,
|
||||
faExternalLink,
|
||||
faLink,
|
||||
faUnlink,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export function Card(props: {
|
||||
children?: JSX.Element | JSX.Element[],
|
||||
className?: string
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
className?: string;
|
||||
}) {
|
||||
return <div className={props.className + " group flex flex-col gap-3 border-[1px] border-black/10 max-h-80 w-full max-w-64 max-lg:max-w-[320px] dark:hover:bg-olympus-300 content-start rounded-md p-4 drop-shadow-md dark:bg-olympus-400 text-black dark:text-white text-pretty cursor-pointer"}>
|
||||
{props.children}
|
||||
<div className='flex flex-grow justify-end items-end text-black dark:text-gray-500 pr-2'><FontAwesomeIcon className="group-hover:translate-x-2 transition-transform" icon={faArrowRight} /></div>
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
${props.className}
|
||||
group flex max-h-80 w-full max-w-64 cursor-pointer flex-col
|
||||
content-start gap-3 text-pretty rounded-md border-[1px] border-black/10
|
||||
p-4 text-black drop-shadow-md
|
||||
dark:bg-olympus-400 dark:text-white dark:hover:bg-olympus-300
|
||||
max-lg:max-w-[320px]
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
<div
|
||||
className={`
|
||||
flex flex-grow items-end justify-end pr-2 text-black
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className={`
|
||||
transition-transform
|
||||
group-hover:translate-x-2
|
||||
`}
|
||||
icon={faArrowRight}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import React from 'react'
|
||||
import React from "react";
|
||||
|
||||
export function Modal(props: {
|
||||
grayout?: boolean,
|
||||
children?: JSX.Element | JSX.Element[],
|
||||
className?: string
|
||||
grayout?: boolean;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
className?: string;
|
||||
}) {
|
||||
return <div className={props.className + "fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-ui-4 rounded-xl border-solid border-[1px] border-gray-700 drop-shadow-md "}>
|
||||
{props.children}
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
${props.className}
|
||||
fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-ui-4
|
||||
rounded-xl border-[1px] border-solid border-gray-700 drop-shadow-md
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,120 +1,418 @@
|
||||
import React, { useState, version } from 'react'
|
||||
import { Modal } from './components/modal'
|
||||
import { Card } from './components/card'
|
||||
import { ErrorCallout, InfoCallout, CommandCallout } from '../../ui/components/olcallout'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faArrowRight, faCheckCircle, faDatabase, faExclamation, faExclamationCircle, faExternalLink, faLink, faServer, faSitemap, faUnlink, faWindowMaximize } from '@fortawesome/free-solid-svg-icons'
|
||||
import { VERSION, connectedToServer } from '../../olympusapp'
|
||||
import { faFirefoxBrowser } from '@fortawesome/free-brands-svg-icons'
|
||||
import React, { useState, version } from "react";
|
||||
import { Modal } from "./components/modal";
|
||||
import { Card } from "./components/card";
|
||||
import {
|
||||
ErrorCallout,
|
||||
InfoCallout,
|
||||
CommandCallout,
|
||||
} from "../../ui/components/olcallout";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faArrowRight,
|
||||
faCheckCircle,
|
||||
faDatabase,
|
||||
faExclamation,
|
||||
faExclamationCircle,
|
||||
faExternalLink,
|
||||
faLink,
|
||||
faServer,
|
||||
faSitemap,
|
||||
faUnlink,
|
||||
faWindowMaximize,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { VERSION, connectedToServer } from "../../olympusapp";
|
||||
import { faFirefoxBrowser } from "@fortawesome/free-brands-svg-icons";
|
||||
|
||||
export function LoginModal(props: {
|
||||
checkingPassword: boolean,
|
||||
loginError: boolean,
|
||||
commandMode: string | null,
|
||||
onLogin: (password: string) => void,
|
||||
onContinue: (username: string) => void,
|
||||
onBack: () => void
|
||||
checkingPassword: boolean;
|
||||
loginError: boolean;
|
||||
commandMode: string | null;
|
||||
onLogin: (password: string) => void;
|
||||
onContinue: (username: string) => void;
|
||||
onBack: () => void;
|
||||
}) {
|
||||
const [password, setPassword] = useState("");
|
||||
const [displayName, setDisplayName] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [displayName, setDisplayName] = useState("");
|
||||
|
||||
return <Modal className="inline-flex max-h-[530px] overflow-y-auto h-[75%] scroll-smooth w-[80%] max-w-[1100px] bg-white dark:bg-olympus-800 max-md:w-full max-md:h-full max-md:max-h-full max-md:rounded-none max-md:border-none ">
|
||||
<img src="/resources/theme/images/splash/1.jpg" className='contents-center w-full object-cover opacity-[7%]'></img>
|
||||
<div className='absolute w-full h-full bg-gradient-to-r from-blue-200/25 to-transparent'></div>
|
||||
<div className='absolute w-full h-full bg-gradient-to-t from-olympus-800 to-transparent'></div>
|
||||
<div className='absolute gap-8 flex flex-col p-16 max-lg:p-8 w-full '>
|
||||
|
||||
<div className="flex flex-row max-lg:flex-col w-full gap-6">
|
||||
<div className="flex flex-grow flex-col gap-5 w-[40%] max-lg:w-[100%] content-center justify-start">
|
||||
{
|
||||
!props.checkingPassword ?
|
||||
<>
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="pt-1 text-gray-800 dark:text-gray-400 text-xs">Connect to</div>
|
||||
<div className="flex text-gray-800 dark:text-gray-200 text-md font-bold items-center justify-center gap-2">{window.location.toString()} </div>
|
||||
</div>
|
||||
<div className='flex flex-row gap-2 content-center items-center w-[100%]'>
|
||||
<span className='size-[80px] min-w-14'><img src="..\images\olympus-500x500.png" className='flex w-full'></img></span>
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<h1 className="flex text-4xl text-gray-800 dark:text-white font-bold text-wrap">DCS Olympus</h1>
|
||||
<div className="flex gap-2 select-none rounded-sm content-center text-green-700 dark:text-green-400 text-sm font-semibold"><FontAwesomeIcon icon={faCheckCircle} className="my-auto" />Version {VERSION}</div>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
!props.loginError ?
|
||||
<>
|
||||
{props.commandMode === null ?
|
||||
<>
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<label className=" text-gray-800 dark:text-white text-md">Password </label>
|
||||
<input type="text" onChange={(ev) => setPassword(ev.currentTarget.value)} className="w-full max-w-80 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Enter password" required />
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<button type="button" onClick={() => props.onLogin(password)} className="flex content-center items-center gap-2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800 rounded-sm">
|
||||
Login <FontAwesomeIcon className="my-auto" icon={faArrowRight} />
|
||||
</button>
|
||||
{/*
|
||||
return (
|
||||
<Modal
|
||||
className={`
|
||||
inline-flex h-[75%] max-h-[530px] w-[80%] max-w-[1100px] overflow-y-auto
|
||||
scroll-smooth bg-white
|
||||
dark:bg-olympus-800
|
||||
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
|
||||
max-md:border-none
|
||||
`}
|
||||
>
|
||||
<img
|
||||
src="/resources/theme/images/splash/1.jpg"
|
||||
className={`contents-center w-full object-cover opacity-[7%]`}
|
||||
></img>
|
||||
<div
|
||||
className={`
|
||||
absolute h-full w-full bg-gradient-to-r from-blue-200/25
|
||||
to-transparent
|
||||
`}
|
||||
></div>
|
||||
<div
|
||||
className={`
|
||||
absolute h-full w-full bg-gradient-to-t from-olympus-800
|
||||
to-transparent
|
||||
`}
|
||||
></div>
|
||||
<div
|
||||
className={`
|
||||
absolute flex w-full flex-col gap-8 p-16
|
||||
max-lg:p-8
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex w-full flex-row gap-6
|
||||
max-lg:flex-col
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
flex w-[40%] flex-grow flex-col content-center justify-start gap-5
|
||||
max-lg:w-[100%]
|
||||
`}
|
||||
>
|
||||
{!props.checkingPassword ? (
|
||||
<>
|
||||
<div className="flex flex-col items-start">
|
||||
<div
|
||||
className={`
|
||||
pt-1 text-xs text-gray-800
|
||||
dark:text-gray-400
|
||||
`}
|
||||
>
|
||||
Connect to
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-center gap-2 text-gray-800
|
||||
text-md font-bold
|
||||
dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
{window.location.toString()}{" "}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex w-[100%] flex-row content-center items-center gap-2
|
||||
`}
|
||||
>
|
||||
<span className="size-[80px] min-w-14">
|
||||
<img
|
||||
src="..\images\olympus-500x500.png"
|
||||
className={`flex w-full`}
|
||||
></img>
|
||||
</span>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col items-start gap-1
|
||||
`}
|
||||
>
|
||||
<h1
|
||||
className={`
|
||||
flex text-wrap text-4xl font-bold text-gray-800
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
DCS Olympus
|
||||
</h1>
|
||||
<div
|
||||
className={`
|
||||
flex select-none content-center gap-2 rounded-sm text-sm
|
||||
font-semibold text-green-700
|
||||
dark:text-green-400
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className={`my-auto`}
|
||||
/>
|
||||
Version {VERSION}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!props.loginError ? (
|
||||
<>
|
||||
{props.commandMode === null ? (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col items-start
|
||||
gap-2
|
||||
`}
|
||||
>
|
||||
<label
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Password{" "}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
onChange={(ev) =>
|
||||
setPassword(ev.currentTarget.value)
|
||||
}
|
||||
className={`
|
||||
block w-full max-w-80 rounded-lg border
|
||||
border-gray-300 bg-gray-50 p-2.5 text-sm
|
||||
text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700
|
||||
dark:text-white dark:placeholder-gray-400
|
||||
dark:focus:border-blue-500
|
||||
dark:focus:ring-blue-500
|
||||
focus:border-blue-500 focus:ring-blue-500
|
||||
`}
|
||||
placeholder="Enter password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => props.onLogin(password)}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2
|
||||
rounded-sm bg-blue-700 px-5 py-2.5 text-sm
|
||||
font-medium text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4
|
||||
focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Login{" "}
|
||||
<FontAwesomeIcon
|
||||
className={`my-auto`}
|
||||
icon={faArrowRight}
|
||||
/>
|
||||
</button>
|
||||
{/*
|
||||
<button type="button" className="flex content-center items-center gap-2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:hover:bg-gray-700 focus:outline-none dark:focus:ring-blue-800 rounded-sm dark:border-gray-600 border-[1px] dark:text-gray-400">
|
||||
View Guide <FontAwesomeIcon className="my-auto text-xs" icon={faExternalLink} />
|
||||
</button>
|
||||
*/}
|
||||
</div>
|
||||
</>
|
||||
:
|
||||
<>
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<label className=" text-gray-800 dark:text-white text-md">Set display name</label>
|
||||
<input type="text" onChange={(ev) => setDisplayName(ev.currentTarget.value)} className="w-full max-w-80 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Enter display name" required />
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<button type="button" onClick={() => props.onContinue(displayName)} className="flex content-center items-center gap-2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800 rounded-sm">
|
||||
Continue <FontAwesomeIcon className="my-auto" icon={faArrowRight} />
|
||||
</button>
|
||||
<button type="button" className="flex content-center items-center gap-2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:hover:bg-gray-700 focus:outline-none dark:focus:ring-blue-800 rounded-sm dark:border-gray-600 border-[1px] dark:text-gray-400">
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
:
|
||||
<><ErrorCallout title='Server could not be reached' description='The Olympus Server at this address could not be reached. Check the address is correct, restart the Olympus server or reinstall Olympus. Ensure the ports set are not already used.'></ErrorCallout><div className='text-gray-200 text-sm font-medium'>Still having issues? See our <a href='' className='underline text-blue-300 hover:no-underline'>troubleshooting guide here</a>.</div></>
|
||||
}
|
||||
</>
|
||||
:
|
||||
<div>
|
||||
<svg aria-hidden="true" className="mx-auto my-auto w-40 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor" />
|
||||
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill" />
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className='flex flex-grow flex-row overflow-hidden gap-3 content-end justify-center max-lg:justify-start max-md:flex-col'>
|
||||
<Card className='flex'>
|
||||
<img src="/resources/theme/images/splash/1.jpg" className='h-[40%] max-h-[120px] contents-center w-full object-cover rounded-md'></img>
|
||||
<div className='flex font-bold mt-2 content-center items-center gap-2 '>
|
||||
YouTube Video Guide <FontAwesomeIcon className="my-auto text-xs text-gray-400" icon={faExternalLink} />
|
||||
</div>
|
||||
<div className='text-xs text-black dark:text-gray-400 overflow-hidden text-ellipsis'>
|
||||
Check out our official video tutorial on how to get started with Olympus - so you can immediately start controlling the battlefield.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col items-start
|
||||
gap-2
|
||||
`}
|
||||
>
|
||||
<label
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Set display name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
onChange={(ev) =>
|
||||
setDisplayName(ev.currentTarget.value)
|
||||
}
|
||||
className={`
|
||||
block w-full max-w-80 rounded-lg border
|
||||
border-gray-300 bg-gray-50 p-2.5 text-sm
|
||||
text-gray-900
|
||||
dark:border-gray-600 dark:bg-gray-700
|
||||
dark:text-white dark:placeholder-gray-400
|
||||
dark:focus:border-blue-500
|
||||
dark:focus:ring-blue-500
|
||||
focus:border-blue-500 focus:ring-blue-500
|
||||
`}
|
||||
placeholder="Enter display name"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card className='flex'>
|
||||
<img src="/resources/theme/images/splash/1.jpg" className='h-[40%] max-h-[120px] contents-center w-full object-cover rounded-md'></img>
|
||||
<div className='flex font-bold mt-2 content-center items-center gap-2'>
|
||||
Wiki Guide <FontAwesomeIcon className="my-auto text-xs text-gray-400" icon={faExternalLink} />
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => props.onContinue(displayName)}
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2
|
||||
rounded-sm bg-blue-700 px-5 py-2.5 text-sm
|
||||
font-medium text-white
|
||||
dark:bg-blue-600 dark:hover:bg-blue-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4
|
||||
focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Continue{" "}
|
||||
<FontAwesomeIcon
|
||||
className={`my-auto`}
|
||||
icon={faArrowRight}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
mb-2 me-2 flex content-center items-center gap-2
|
||||
rounded-sm border-[1px] bg-blue-700 px-5 py-2.5
|
||||
text-sm font-medium text-white
|
||||
dark:border-gray-600 dark:bg-gray-800
|
||||
dark:text-gray-400 dark:hover:bg-gray-700
|
||||
dark:focus:ring-blue-800
|
||||
focus:outline-none focus:ring-4
|
||||
focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
<div className='text-xs text-black dark:text-gray-400 overflow-hidden text-ellipsis'>
|
||||
Find out more about Olympus through our online wiki guide.
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex max-lg:flex-col w-full h-full text-gray-600 text-xs font-light">
|
||||
DCS Olympus (the "MATERIAL" or "Software") is provided completely free to users subject to the terms of the CC BY-NC-SA 4.0 Licence except where such terms conflict with this disclaimer, in which case, the terms of this disclaimer shall prevail. Any party making use of the Software in any manner agrees to be bound by the terms set out in the disclaimer. THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS SA.
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ErrorCallout
|
||||
title="Server could not be reached"
|
||||
description="The Olympus Server at this address could not be reached. Check the address is correct, restart the Olympus server or reinstall Olympus. Ensure the ports set are not already used."
|
||||
></ErrorCallout>
|
||||
<div
|
||||
className={`
|
||||
text-sm font-medium text-gray-200
|
||||
`}
|
||||
>
|
||||
Still having issues? See our{" "}
|
||||
<a
|
||||
href=""
|
||||
className={`
|
||||
text-blue-300 underline
|
||||
hover:no-underline
|
||||
`}
|
||||
>
|
||||
troubleshooting guide here
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={`
|
||||
mx-auto my-auto w-40 animate-spin fill-blue-600
|
||||
text-gray-200
|
||||
dark:text-gray-600
|
||||
`}
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-grow flex-row content-end justify-center gap-3
|
||||
overflow-hidden
|
||||
max-lg:justify-start
|
||||
max-md:flex-col
|
||||
`}
|
||||
>
|
||||
<Card className="flex">
|
||||
<img
|
||||
src="/resources/theme/images/splash/1.jpg"
|
||||
className={`
|
||||
h-[40%] max-h-[120px] contents-center w-full rounded-md
|
||||
object-cover
|
||||
`}
|
||||
></img>
|
||||
<div
|
||||
className={`
|
||||
mt-2 flex content-center items-center gap-2 font-bold
|
||||
`}
|
||||
>
|
||||
YouTube Video Guide{" "}
|
||||
<FontAwesomeIcon
|
||||
className={`my-auto text-xs text-gray-400`}
|
||||
icon={faExternalLink}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
overflow-hidden text-ellipsis text-xs text-black
|
||||
dark:text-gray-400
|
||||
`}
|
||||
>
|
||||
Check out our official video tutorial on how to get started with
|
||||
Olympus - so you can immediately start controlling the
|
||||
battlefield.
|
||||
</div>
|
||||
</Card>
|
||||
<Card className="flex">
|
||||
<img
|
||||
src="/resources/theme/images/splash/1.jpg"
|
||||
className={`
|
||||
h-[40%] max-h-[120px] contents-center w-full rounded-md
|
||||
object-cover
|
||||
`}
|
||||
></img>
|
||||
<div
|
||||
className={`
|
||||
mt-2 flex content-center items-center gap-2 font-bold
|
||||
`}
|
||||
>
|
||||
Wiki Guide{" "}
|
||||
<FontAwesomeIcon
|
||||
className={`my-auto text-xs text-gray-400`}
|
||||
icon={faExternalLink}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
overflow-hidden text-ellipsis text-xs text-black
|
||||
dark:text-gray-400
|
||||
`}
|
||||
>
|
||||
Find out more about Olympus through our online wiki guide.
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Modal >
|
||||
}
|
||||
<div
|
||||
className={`
|
||||
flex h-full w-full text-xs font-light text-gray-600
|
||||
max-lg:flex-col
|
||||
`}
|
||||
>
|
||||
DCS Olympus (the "MATERIAL" or "Software") is provided completely free
|
||||
to users subject to the terms of the CC BY-NC-SA 4.0 Licence except
|
||||
where such terms conflict with this disclaimer, in which case, the
|
||||
terms of this disclaimer shall prevail. Any party making use of the
|
||||
Software in any manner agrees to be bound by the terms set out in the
|
||||
disclaimer. THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS
|
||||
SA.
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,18 +3,54 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import React from "react";
|
||||
|
||||
export function Menu(props: {
|
||||
title: string,
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
onBack?: () => void,
|
||||
showBackButton?: boolean,
|
||||
children?: JSX.Element | JSX.Element[],
|
||||
title: string;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onBack?: () => void;
|
||||
showBackButton?: boolean;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
}) {
|
||||
return <div data-open={props.open} className="w-[430px] absolute top-[62px] left-16 z-ui-0 h-screen overflow-y-auto transition-transform data-[open='false']:-translate-x-full bg-gray-200 dark:bg-olympus-800/90 backdrop-blur-lg backdrop-grayscale" tabIndex={-1}>
|
||||
<h5 className="w-full inline-flex items-center py-3 pb-2 px-5 shadow-lg font-semibold text-gray-800 dark:text-gray-400">
|
||||
{props.showBackButton && <FontAwesomeIcon onClick={props.onBack ?? (() => { })} icon={faArrowLeft} className="mr-1 cursor-pointer p-2 rounded-md dark:hover:bg-gray-700 dark:text-gray-500 dark:hover:text-white"/>} {props.title}
|
||||
<FontAwesomeIcon onClick={props.onClose} icon={faClose} className="flex text-lg items-center cursor-pointer justify-center ml-auto p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 dark:hover:text-white dark:text-gray-500 "/>
|
||||
</h5>
|
||||
{props.children}
|
||||
</div>
|
||||
}
|
||||
return (
|
||||
<div
|
||||
data-open={props.open}
|
||||
className={`
|
||||
absolute left-16 top-[62px] w-[430px] z-ui-0 h-screen overflow-y-auto
|
||||
bg-gray-200 backdrop-blur-lg backdrop-grayscale transition-transform
|
||||
dark:bg-olympus-800/90
|
||||
data-[open='false']:-translate-x-full
|
||||
`}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<h5
|
||||
className={`
|
||||
inline-flex w-full items-center px-5 py-3 pb-2 font-semibold
|
||||
text-gray-800 shadow-lg
|
||||
dark:text-gray-400
|
||||
`}
|
||||
>
|
||||
{props.showBackButton && (
|
||||
<FontAwesomeIcon
|
||||
onClick={props.onBack ?? (() => {})}
|
||||
icon={faArrowLeft}
|
||||
className={`
|
||||
mr-1 cursor-pointer rounded-md p-2
|
||||
dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white
|
||||
`}
|
||||
/>
|
||||
)}{" "}
|
||||
{props.title}
|
||||
<FontAwesomeIcon
|
||||
onClick={props.onClose}
|
||||
icon={faClose}
|
||||
className={`
|
||||
ml-auto flex cursor-pointer items-center justify-center rounded-md
|
||||
p-2 text-lg
|
||||
dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white
|
||||
hover:bg-gray-200
|
||||
`}
|
||||
/>
|
||||
</h5>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,96 +1,253 @@
|
||||
import React, { useState } from 'react'
|
||||
import { OlRoundStateButton, OlStateButton, OlLockStateButton } from '../components/olstatebutton';
|
||||
import { faSkull, faCamera, faFlag, faLink, faUnlink, faBars } from '@fortawesome/free-solid-svg-icons';
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
OlRoundStateButton,
|
||||
OlStateButton,
|
||||
OlLockStateButton,
|
||||
} from "../components/olstatebutton";
|
||||
import {
|
||||
faSkull,
|
||||
faCamera,
|
||||
faFlag,
|
||||
faLink,
|
||||
faUnlink,
|
||||
faBars,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { EventsConsumer } from '../../eventscontext';
|
||||
import { StateConsumer } from '../../statecontext';
|
||||
import { OlDropdownItem, OlDropdown } from '../components/oldropdown';
|
||||
import { OlLabelToggle } from '../components/ollabeltoggle';
|
||||
import { getApp, IP, connectedToServer } from '../../olympusapp';
|
||||
import { olButtonsVisibilityAirbase, olButtonsVisibilityAircraft, olButtonsVisibilityDcs, olButtonsVisibilityGroundunit, olButtonsVisibilityGroundunitSam, olButtonsVisibilityHelicopter, olButtonsVisibilityHuman, olButtonsVisibilityNavyunit, olButtonsVisibilityOlympus } from '../components/olicons';
|
||||
import { EventsConsumer } from "../../eventscontext";
|
||||
import { StateConsumer } from "../../statecontext";
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
import { getApp, IP, connectedToServer } from "../../olympusapp";
|
||||
import {
|
||||
olButtonsVisibilityAirbase,
|
||||
olButtonsVisibilityAircraft,
|
||||
olButtonsVisibilityDcs,
|
||||
olButtonsVisibilityGroundunit,
|
||||
olButtonsVisibilityGroundunitSam,
|
||||
olButtonsVisibilityHelicopter,
|
||||
olButtonsVisibilityHuman,
|
||||
olButtonsVisibilityNavyunit,
|
||||
olButtonsVisibilityOlympus,
|
||||
} from "../components/olicons";
|
||||
|
||||
export function Header() {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
|
||||
return <StateConsumer>
|
||||
{(appState) =>
|
||||
<EventsConsumer>
|
||||
{(events) =>
|
||||
<nav className={`${collapsed? 'h-[60px]': 'h-fit'} flex w-screen bg-gray-300 border-gray-200 dark:bg-olympus-900 dark:border-gray-800 px-3 z-ui-2 drop-shadow-md`}>
|
||||
<div className="w-full max-w-full flex flex-wrap overflow-hidden items-center justify-end gap-3 my-2">
|
||||
<div className="flex flex-row items-center justify-start gap-6 flex-none mr-auto basis-5/6 sm:basis-0">
|
||||
<img src="images/icon.png" className='h-10 w-10 p-0 rounded-md'></img>
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="pt-1 text-gray-800 dark:text-gray-400 text-xs">Connected to</div>
|
||||
<div className="flex text-gray-800 dark:text-gray-200 text-sm font-extrabold items-center justify-center gap-2">{IP} <FontAwesomeIcon icon={connectedToServer ? faLink : faUnlink} data-connected={connectedToServer} className="py-auto text-green-400 data-[connected='true']:dark:text-green-400 dark:text-red-500" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<OlStateButton onClick={() => setCollapsed(!collapsed) } checked={!collapsed} icon={faBars} tooltip={"Show more options"}></OlStateButton>
|
||||
<div>
|
||||
<OlLockStateButton checked={false} onClick={() => {}} tooltip="Lock/unlock protected units (from scripted mission)"/>
|
||||
</div>
|
||||
<div className="flex flex-row h-fit items-center justify-start gap-1">
|
||||
{
|
||||
Object.entries({
|
||||
'human': olButtonsVisibilityHuman,'olympus': olButtonsVisibilityOlympus, 'dcs': olButtonsVisibilityDcs
|
||||
}).map((entry) => {
|
||||
return <OlRoundStateButton
|
||||
onClick={() => {
|
||||
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
|
||||
}}
|
||||
checked={!appState.mapHiddenTypes[entry[0]]}
|
||||
icon={entry[1]}
|
||||
tooltip={"Hide/show " + entry[0] + " units" } />
|
||||
})
|
||||
}
|
||||
</div>
|
||||
<div className='h-8 w-0 border-l-[2px] border-gray-700'></div>
|
||||
<div className="flex flex-row h-fit items-center justify-start gap-1">
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType( 'blue', !appState.mapHiddenTypes['blue'] )}
|
||||
checked={!appState.mapHiddenTypes['blue']}
|
||||
icon={faFlag} className={"!text-blue-500"}
|
||||
tooltip={"Hide/show blue units" } />
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType('red', !appState.mapHiddenTypes['red'] )}
|
||||
checked={!appState.mapHiddenTypes['red']}
|
||||
icon={faFlag} className={"!text-red-500"}
|
||||
tooltip={"Hide/show red units" } />
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType('neutral', !appState.mapHiddenTypes['neutral'] )}
|
||||
checked={!appState.mapHiddenTypes['neutral']}
|
||||
icon={faFlag} className={"!text-gray-500"}
|
||||
tooltip={"Hide/show neutral units" } />
|
||||
</div>
|
||||
<div className='h-8 w-0 border-l-[2px] border-gray-700'></div>
|
||||
<div className="flex flex-row h-fit items-center justify-start gap-1">
|
||||
{
|
||||
Object.entries({
|
||||
'aircraft': olButtonsVisibilityAircraft,'helicopter': olButtonsVisibilityHelicopter, 'groundunit-sam': olButtonsVisibilityGroundunitSam,
|
||||
'groundunit': olButtonsVisibilityGroundunit, 'navyunit': olButtonsVisibilityNavyunit, 'airbase': olButtonsVisibilityAirbase, 'dead': faSkull
|
||||
}).map((entry) => {
|
||||
return <OlRoundStateButton
|
||||
onClick={() => {
|
||||
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
|
||||
}}
|
||||
checked={!appState.mapHiddenTypes[entry[0]]}
|
||||
icon={entry[1]}
|
||||
tooltip={"Hide/show " + entry[0] + " units" } />
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<OlLabelToggle toggled={false} leftLabel={"Live"} rightLabel={"Map"} onClick={() => {}}></OlLabelToggle>
|
||||
<OlStateButton checked={false} icon={faCamera} onClick={() => {}} tooltip="Activate/deactivate camera plugin" />
|
||||
<OlDropdown label={appState.activeMapSource} className="w-80">
|
||||
{appState.mapSources.map((source) => {
|
||||
return <OlDropdownItem className="w-full" onClick={() => getApp().getMap().setLayerName(source)}>{ source }</OlDropdownItem>
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
</EventsConsumer>
|
||||
}
|
||||
</StateConsumer>
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
|
||||
return (
|
||||
<StateConsumer>
|
||||
{(appState) => (
|
||||
<EventsConsumer>
|
||||
{() => (
|
||||
<nav
|
||||
className={`
|
||||
${collapsed ? "h-[60px]" : "h-fit"}
|
||||
flex w-screen border-gray-200 bg-gray-300 px-3 drop-shadow-md
|
||||
z-ui-2
|
||||
dark:border-gray-800 dark:bg-olympus-900
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
my-2 flex w-full max-w-full flex-wrap items-center justify-end
|
||||
gap-3 overflow-hidden
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
mr-auto flex flex-none basis-5/6 flex-row items-center
|
||||
justify-start gap-6
|
||||
sm:basis-0
|
||||
`}
|
||||
>
|
||||
<img
|
||||
src="images/icon.png"
|
||||
className="h-10 w-10 rounded-md p-0"
|
||||
></img>
|
||||
<div className="flex flex-col items-start">
|
||||
<div
|
||||
className={`
|
||||
pt-1 text-xs text-gray-800
|
||||
dark:text-gray-400
|
||||
`}
|
||||
>
|
||||
Connected to
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex items-center justify-center gap-2 text-sm
|
||||
font-extrabold text-gray-800
|
||||
dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
{IP}{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={connectedToServer ? faLink : faUnlink}
|
||||
data-connected={connectedToServer}
|
||||
className={`
|
||||
py-auto text-green-400
|
||||
dark:text-red-500
|
||||
data-[connected='true']:dark:text-green-400
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OlStateButton
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
checked={!collapsed}
|
||||
icon={faBars}
|
||||
tooltip={"Show more options"}
|
||||
></OlStateButton>
|
||||
<div>
|
||||
<OlLockStateButton
|
||||
checked={false}
|
||||
onClick={() => {}}
|
||||
tooltip="Lock/unlock protected units (from scripted mission)"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
>
|
||||
{Object.entries({
|
||||
human: olButtonsVisibilityHuman,
|
||||
olympus: olButtonsVisibilityOlympus,
|
||||
dcs: olButtonsVisibilityDcs,
|
||||
}).map((entry) => {
|
||||
return (
|
||||
<OlRoundStateButton
|
||||
key={entry[0]}
|
||||
onClick={() => {
|
||||
getApp()
|
||||
.getMap()
|
||||
.setHiddenType(
|
||||
entry[0],
|
||||
!appState.mapHiddenTypes[entry[0]]
|
||||
);
|
||||
}}
|
||||
checked={!appState.mapHiddenTypes[entry[0]]}
|
||||
icon={entry[1]}
|
||||
tooltip={"Hide/show " + entry[0] + " units"}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
h-8 w-0 border-l-[2px] border-gray-700
|
||||
`}
|
||||
></div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
>
|
||||
<OlRoundStateButton
|
||||
onClick={() =>
|
||||
getApp()
|
||||
.getMap()
|
||||
.setHiddenType("blue", !appState.mapHiddenTypes["blue"])
|
||||
}
|
||||
checked={!appState.mapHiddenTypes["blue"]}
|
||||
icon={faFlag}
|
||||
className={"!text-blue-500"}
|
||||
tooltip={"Hide/show blue units"}
|
||||
/>
|
||||
<OlRoundStateButton
|
||||
onClick={() =>
|
||||
getApp()
|
||||
.getMap()
|
||||
.setHiddenType("red", !appState.mapHiddenTypes["red"])
|
||||
}
|
||||
checked={!appState.mapHiddenTypes["red"]}
|
||||
icon={faFlag}
|
||||
className={"!text-red-500"}
|
||||
tooltip={"Hide/show red units"}
|
||||
/>
|
||||
<OlRoundStateButton
|
||||
onClick={() =>
|
||||
getApp()
|
||||
.getMap()
|
||||
.setHiddenType(
|
||||
"neutral",
|
||||
!appState.mapHiddenTypes["neutral"]
|
||||
)
|
||||
}
|
||||
checked={!appState.mapHiddenTypes["neutral"]}
|
||||
icon={faFlag}
|
||||
className={"!text-gray-500"}
|
||||
tooltip={"Hide/show neutral units"}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
h-8 w-0 border-l-[2px] border-gray-700
|
||||
`}
|
||||
></div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-row items-center justify-start gap-1
|
||||
`}
|
||||
>
|
||||
{Object.entries({
|
||||
aircraft: olButtonsVisibilityAircraft,
|
||||
helicopter: olButtonsVisibilityHelicopter,
|
||||
"groundunit-sam": olButtonsVisibilityGroundunitSam,
|
||||
groundunit: olButtonsVisibilityGroundunit,
|
||||
navyunit: olButtonsVisibilityNavyunit,
|
||||
airbase: olButtonsVisibilityAirbase,
|
||||
dead: faSkull,
|
||||
}).map((entry) => {
|
||||
return (
|
||||
<OlRoundStateButton
|
||||
key={entry[0]}
|
||||
onClick={() => {
|
||||
getApp()
|
||||
.getMap()
|
||||
.setHiddenType(
|
||||
entry[0],
|
||||
!appState.mapHiddenTypes[entry[0]]
|
||||
);
|
||||
}}
|
||||
checked={!appState.mapHiddenTypes[entry[0]]}
|
||||
icon={entry[1]}
|
||||
tooltip={"Hide/show " + entry[0] + " units"}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<OlLabelToggle
|
||||
toggled={false}
|
||||
leftLabel={"Live"}
|
||||
rightLabel={"Map"}
|
||||
onClick={() => {}}
|
||||
></OlLabelToggle>
|
||||
<OlStateButton
|
||||
checked={false}
|
||||
icon={faCamera}
|
||||
onClick={() => {}}
|
||||
tooltip="Activate/deactivate camera plugin"
|
||||
/>
|
||||
<OlDropdown label={appState.activeMapSource} className="w-80">
|
||||
{appState.mapSources.map((source) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
key={source}
|
||||
className="w-full"
|
||||
onClick={() => getApp().getMap().setLayerName(source)}
|
||||
>
|
||||
{source}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
</EventsConsumer>
|
||||
)}
|
||||
</StateConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,243 @@
|
||||
import React from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { faArrowRightLong, faCheckCircle, faDatabase, faExternalLink, faExternalLinkAlt, faFile, faFileAlt, faFileExport, faFileImport, faTimesCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import {
|
||||
faArrowRightLong,
|
||||
faCheckCircle,
|
||||
faDatabase,
|
||||
faExternalLink,
|
||||
faExternalLinkAlt,
|
||||
faFile,
|
||||
faFileAlt,
|
||||
faFileExport,
|
||||
faFileImport,
|
||||
faTimesCircle,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { VERSION } from "../../olympusapp";
|
||||
import { faGithub } from "@fortawesome/free-brands-svg-icons";
|
||||
|
||||
export function MainMenu(props: {
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
children?: JSX.Element | JSX.Element[],
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
}) {
|
||||
return <Menu
|
||||
title="Main Menu"
|
||||
open={props.open}
|
||||
showBackButton={false}
|
||||
onClose={props.onClose}
|
||||
return (
|
||||
<Menu
|
||||
title="Main Menu"
|
||||
open={props.open}
|
||||
showBackButton={false}
|
||||
onClose={props.onClose}
|
||||
>
|
||||
<div className="flex flex-col p-5 gap-1 font-normal text-gray-900 dark:text-white">
|
||||
<div className="mb-1">
|
||||
<div className="flex gap-2 mb-2 select-none rounded-sm content-center text-green-700 dark:text-green-400 font-bold text-lg"><FontAwesomeIcon icon={faCheckCircle} className="my-auto" />Olympus Version {VERSION}</div>
|
||||
<div className="text-gray-400 text-sm">You can use the Olympus Manager to update port, passwords or other settings.</div>
|
||||
</div>
|
||||
|
||||
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faGithub} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}View GitHub Repo <FontAwesomeIcon icon={faExternalLinkAlt} className="my-auto w-4 text-gray-800 dark:text-gray-500 text-sm" /><div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
|
||||
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faFile} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}View User Guide <FontAwesomeIcon icon={faExternalLinkAlt} className="my-auto w-4 text-gray-800 dark:text-gray-500 text-sm" /><div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
|
||||
<hr className="w-auto m-2 my-1 bg-gray-700 border-[1px] dark:border-olympus-500"></hr>
|
||||
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faDatabase} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}Open Olympus Manager<div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
|
||||
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faDatabase} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}Database Manager<div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
|
||||
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faFileExport} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}Export to file<div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
|
||||
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faFileImport} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}Import from file<div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
|
||||
<hr className="w-auto m-2 my-1 bg-gray-700 border-[1px] dark:border-olympus-500"></hr>
|
||||
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faTimesCircle} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}Close Olympus<div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-1 p-5 font-normal text-gray-900
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
<div className="mb-1">
|
||||
<div
|
||||
className={`
|
||||
mb-2 flex select-none content-center gap-2 rounded-sm text-lg
|
||||
font-bold text-green-700
|
||||
dark:text-green-400
|
||||
`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className={`my-auto`}
|
||||
/>
|
||||
Olympus Version {VERSION}
|
||||
</div>
|
||||
<div className="text-sm text-gray-400">
|
||||
You can use the Olympus Manager to update port, passwords or other
|
||||
settings.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`
|
||||
group flex cursor-pointer select-none content-center gap-3
|
||||
rounded-md p-2
|
||||
dark:hover:bg-olympus-500
|
||||
hover:bg-gray-900/10
|
||||
`}
|
||||
>
|
||||
{/*<FontAwesomeIcon icon={faGithub} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
|
||||
View GitHub Repo{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={faExternalLinkAlt}
|
||||
className={`
|
||||
my-auto w-4 text-sm text-gray-800
|
||||
dark:text-gray-500
|
||||
`}
|
||||
/>
|
||||
<div className={`ml-auto flex items-center`}>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className={`
|
||||
my-auto px-2 text-right text-gray-800 transition-transform
|
||||
dark:text-olympus-50
|
||||
group-hover:translate-x-2
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex cursor-pointer select-none content-center gap-3
|
||||
rounded-md p-2
|
||||
dark:hover:bg-olympus-500
|
||||
hover:bg-gray-900/10
|
||||
`}
|
||||
>
|
||||
{/*<FontAwesomeIcon icon={faFile} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
|
||||
View User Guide{" "}
|
||||
<FontAwesomeIcon
|
||||
icon={faExternalLinkAlt}
|
||||
className={`
|
||||
my-auto w-4 text-sm text-gray-800
|
||||
dark:text-gray-500
|
||||
`}
|
||||
/>
|
||||
<div className={`ml-auto flex items-center`}>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className={`
|
||||
my-auto px-2 text-right text-gray-800 transition-transform
|
||||
dark:text-olympus-50
|
||||
group-hover:translate-x-2
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
className={`
|
||||
m-2 my-1 w-auto border-[1px] bg-gray-700
|
||||
dark:border-olympus-500
|
||||
`}
|
||||
></hr>
|
||||
<div
|
||||
className={`
|
||||
group flex cursor-pointer select-none content-center gap-3
|
||||
rounded-md p-2
|
||||
dark:hover:bg-olympus-500
|
||||
hover:bg-gray-900/10
|
||||
`}
|
||||
>
|
||||
{/*<FontAwesomeIcon icon={faDatabase} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
|
||||
Open Olympus Manager
|
||||
<div
|
||||
className={`ml-auto flex items-center`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className={`
|
||||
my-auto px-2 text-right text-gray-800 transition-transform
|
||||
dark:text-olympus-50
|
||||
group-hover:translate-x-2
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex cursor-pointer select-none content-center gap-3
|
||||
rounded-md p-2
|
||||
dark:hover:bg-olympus-500
|
||||
hover:bg-gray-900/10
|
||||
`}
|
||||
>
|
||||
{/*<FontAwesomeIcon icon={faDatabase} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
|
||||
Database Manager
|
||||
<div
|
||||
className={`ml-auto flex items-center`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className={`
|
||||
my-auto px-2 text-right text-gray-800 transition-transform
|
||||
dark:text-olympus-50
|
||||
group-hover:translate-x-2
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex cursor-pointer select-none content-center gap-3
|
||||
rounded-md p-2
|
||||
dark:hover:bg-olympus-500
|
||||
hover:bg-gray-900/10
|
||||
`}
|
||||
>
|
||||
{/*<FontAwesomeIcon icon={faFileExport} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
|
||||
Export to file
|
||||
<div
|
||||
className={`ml-auto flex items-center`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className={`
|
||||
my-auto px-2 text-right text-gray-800 transition-transform
|
||||
dark:text-olympus-50
|
||||
group-hover:translate-x-2
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex cursor-pointer select-none content-center gap-3
|
||||
rounded-md p-2
|
||||
dark:hover:bg-olympus-500
|
||||
hover:bg-gray-900/10
|
||||
`}
|
||||
>
|
||||
{/*<FontAwesomeIcon icon={faFileImport} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
|
||||
Import from file
|
||||
<div
|
||||
className={`ml-auto flex items-center`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className={`
|
||||
my-auto px-2 text-right text-gray-800 transition-transform
|
||||
dark:text-olympus-50
|
||||
group-hover:translate-x-2
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
className={`
|
||||
m-2 my-1 w-auto border-[1px] bg-gray-700
|
||||
dark:border-olympus-500
|
||||
`}
|
||||
></hr>
|
||||
<div
|
||||
className={`
|
||||
group flex cursor-pointer select-none content-center gap-3
|
||||
rounded-md p-2
|
||||
dark:hover:bg-olympus-500
|
||||
hover:bg-gray-900/10
|
||||
`}
|
||||
>
|
||||
{/*<FontAwesomeIcon icon={faTimesCircle} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
|
||||
Close Olympus
|
||||
<div
|
||||
className={`ml-auto flex items-center`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRightLong}
|
||||
className={`
|
||||
my-auto px-2 text-right text-gray-800 transition-transform
|
||||
dark:text-olympus-50
|
||||
group-hover:translate-x-2
|
||||
`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,78 +2,123 @@ import React, { useState, useEffect } from "react";
|
||||
import { zeroAppend } from "../../other/utils";
|
||||
import { DateAndTime } from "../../interfaces";
|
||||
|
||||
export function MiniMapPanel(props: {
|
||||
export function MiniMapPanel(props: {}) {
|
||||
var [frameRate, setFrameRate] = useState(0);
|
||||
var [load, setLoad] = useState(0);
|
||||
var [elapsedTime, setElapsedTime] = useState(0);
|
||||
var [missionTime, setMissionTime] = useState({
|
||||
h: 0,
|
||||
m: 0,
|
||||
s: 0,
|
||||
} as DateAndTime["time"]);
|
||||
var [connected, setConnected] = useState(false);
|
||||
var [paused, setPaused] = useState(false);
|
||||
var [showMissionTime, setShowMissionTime] = useState(false);
|
||||
|
||||
}) {
|
||||
var [frameRate, setFrameRate] = useState(0);
|
||||
var [load, setLoad] = useState(0);
|
||||
var [elapsedTime, setElapsedTime] = useState(0);
|
||||
var [missionTime, setMissionTime] = useState({ h: 0, m: 0, s: 0 } as DateAndTime["time"]);
|
||||
var [connected, setConnected] = useState(false);
|
||||
var [paused, setPaused] = useState(false);
|
||||
var [showMissionTime, setShowMissionTime] = useState(false);
|
||||
document.addEventListener("serverStatusUpdated", (ev) => {
|
||||
setFrameRate(ev.detail.frameRate);
|
||||
setLoad(ev.detail.load);
|
||||
setElapsedTime(ev.detail.elapsedTime);
|
||||
setMissionTime(ev.detail.missionTime);
|
||||
setConnected(ev.detail.connected);
|
||||
setPaused(ev.detail.paused);
|
||||
});
|
||||
|
||||
document.addEventListener("serverStatusUpdated", (ev) => {
|
||||
setFrameRate(ev.detail.frameRate);
|
||||
setLoad(ev.detail.load);
|
||||
setElapsedTime(ev.detail.elapsedTime);
|
||||
setMissionTime(ev.detail.missionTime);
|
||||
setConnected(ev.detail.connected);
|
||||
setPaused(ev.detail.paused);
|
||||
})
|
||||
|
||||
// A bit of a hack to set the rounded borders to the minimap
|
||||
useEffect(() => {
|
||||
let miniMap = document.querySelector(".leaflet-control-minimap");
|
||||
if (miniMap) {
|
||||
miniMap.classList.add("rounded-t-lg")
|
||||
}
|
||||
})
|
||||
|
||||
// Compute the time string depending on mission or elapsed time
|
||||
let hours = 0;
|
||||
let minutes = 0;
|
||||
let seconds = 0;
|
||||
|
||||
if (showMissionTime) {
|
||||
hours = missionTime.h;
|
||||
minutes = missionTime.m;
|
||||
seconds = missionTime.s;
|
||||
} else {
|
||||
hours = Math.floor(elapsedTime / 3600);
|
||||
minutes = Math.floor((elapsedTime / 60)) % 60;
|
||||
seconds = Math.round(elapsedTime) % 60;
|
||||
// A bit of a hack to set the rounded borders to the minimap
|
||||
useEffect(() => {
|
||||
let miniMap = document.querySelector(".leaflet-control-minimap");
|
||||
if (miniMap) {
|
||||
miniMap.classList.add("rounded-t-lg");
|
||||
}
|
||||
});
|
||||
|
||||
let timeString = `${zeroAppend(hours, 2)}:${zeroAppend(minutes, 2)}:${zeroAppend(seconds, 2)}`
|
||||
// Compute the time string depending on mission or elapsed time
|
||||
let hours = 0;
|
||||
let minutes = 0;
|
||||
let seconds = 0;
|
||||
|
||||
// Choose frame rate string color
|
||||
let frameRateColor = "#8BFF63";
|
||||
if (frameRate < 30)
|
||||
frameRateColor = "#F05252";
|
||||
else if (frameRate >= 30 && frameRate < 60)
|
||||
frameRateColor = "#FF9900"
|
||||
if (showMissionTime) {
|
||||
hours = missionTime.h;
|
||||
minutes = missionTime.m;
|
||||
seconds = missionTime.s;
|
||||
} else {
|
||||
hours = Math.floor(elapsedTime / 3600);
|
||||
minutes = Math.floor(elapsedTime / 60) % 60;
|
||||
seconds = Math.round(elapsedTime) % 60;
|
||||
}
|
||||
|
||||
// Choose load string color
|
||||
let loadColor = "#8BFF63";
|
||||
if (load > 1000)
|
||||
loadColor = "#F05252";
|
||||
else if (load >= 100 && load < 1000)
|
||||
loadColor = "#FF9900"
|
||||
let timeString = `${zeroAppend(hours, 2)}:${zeroAppend(minutes, 2)}:${zeroAppend(seconds, 2)}`;
|
||||
|
||||
return <div onClick={() => setShowMissionTime(!showMissionTime)} className="absolute w-[288px] top-[233px] right-[10px] z-ui-0 text-sm dark:text-gray-200 bg-gray-200 dark:bg-olympus-800/90 backdrop-blur-lg backdrop-grayscale flex items-center justify-between p-3 rounded-b-lg">
|
||||
{
|
||||
!connected ?
|
||||
<div className="font-semibold animate-pulse flex items-center gap-2"><div className="bg-[#F05252] relative w-4 h-4 rounded-full"></div>Server disconnected</div> :
|
||||
paused ?
|
||||
<div className="font-semibold animate-pulse flex items-center gap-2"><div className="bg-[#FF9900] relative w-4 h-4 rounded-full"></div>Server paused</div> :
|
||||
<>
|
||||
<div className="font-semibold">FPS: <span style={{ 'color': frameRateColor }} className="font-semibold">{frameRate}</span> </div>
|
||||
<div className="font-semibold">Load: <span style={{ 'color': loadColor }} className="font-semibold">{load}</span> </div>
|
||||
<div className="font-semibold">{showMissionTime ? "MT" : "ET"}: {timeString} </div>
|
||||
<div className="bg-[#8BFF63] relative w-4 h-4 rounded-full"></div>
|
||||
</>
|
||||
}
|
||||
// Choose frame rate string color
|
||||
let frameRateColor = "#8BFF63";
|
||||
if (frameRate < 30) frameRateColor = "#F05252";
|
||||
else if (frameRate >= 30 && frameRate < 60) frameRateColor = "#FF9900";
|
||||
|
||||
// Choose load string color
|
||||
let loadColor = "#8BFF63";
|
||||
if (load > 1000) loadColor = "#F05252";
|
||||
else if (load >= 100 && load < 1000) loadColor = "#FF9900";
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => setShowMissionTime(!showMissionTime)}
|
||||
className={`
|
||||
absolute right-[10px] top-[233px] w-[288px] z-ui-0 flex items-center
|
||||
justify-between rounded-b-lg bg-gray-200 p-3 text-sm backdrop-blur-lg
|
||||
backdrop-grayscale
|
||||
dark:bg-olympus-800/90 dark:text-gray-200
|
||||
`}
|
||||
>
|
||||
{!connected ? (
|
||||
<div
|
||||
className={`
|
||||
flex animate-pulse items-center gap-2 font-semibold
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`relative h-4 w-4 rounded-full bg-[#F05252]`}
|
||||
></div>
|
||||
Server disconnected
|
||||
</div>
|
||||
) : paused ? (
|
||||
<div
|
||||
className={`
|
||||
flex animate-pulse items-center gap-2 font-semibold
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`relative h-4 w-4 rounded-full bg-[#FF9900]`}
|
||||
></div>
|
||||
Server paused
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="font-semibold">
|
||||
FPS:{" "}
|
||||
<span
|
||||
style={{ color: frameRateColor }}
|
||||
className={`font-semibold`}
|
||||
>
|
||||
{frameRate}
|
||||
</span>{" "}
|
||||
</div>
|
||||
<div className="font-semibold">
|
||||
Load:{" "}
|
||||
<span
|
||||
style={{ color: loadColor }}
|
||||
className={`font-semibold`}
|
||||
>
|
||||
{load}
|
||||
</span>{" "}
|
||||
</div>
|
||||
<div className="font-semibold">
|
||||
{showMissionTime ? "MT" : "ET"}: {timeString}{" "}
|
||||
</div>
|
||||
<div
|
||||
className={`relative h-4 w-4 rounded-full bg-[#8BFF63]`}
|
||||
></div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,68 +7,197 @@ import { MapOptions } from "../../types/types";
|
||||
import { getApp } from "../../olympusapp";
|
||||
|
||||
export function Options(props: {
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
options: MapOptions,
|
||||
children?: JSX.Element | JSX.Element[],
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
options: MapOptions;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
}) {
|
||||
return <Menu
|
||||
title="User preferences"
|
||||
open={props.open}
|
||||
showBackButton={false}
|
||||
onClose={props.onClose}
|
||||
return (
|
||||
<Menu
|
||||
title="User preferences"
|
||||
open={props.open}
|
||||
showBackButton={false}
|
||||
onClose={props.onClose}
|
||||
>
|
||||
<div className="flex flex-col p-5 gap-2 font-normal text-gray-800 dark:text-white ">
|
||||
<div
|
||||
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
|
||||
onClick={() => { getApp().getMap().setOption("showUnitLabels", !props.options.showUnitLabels) }}
|
||||
>
|
||||
<OlCheckbox checked={props.options.showUnitLabels} onChange={() => { }}></OlCheckbox>
|
||||
<span>Show Unit Labels</span>
|
||||
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">L</kbd>
|
||||
</div>
|
||||
<div
|
||||
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
|
||||
onClick={() => { getApp().getMap().setOption("showUnitsEngagementRings", !props.options.showUnitsEngagementRings) }}
|
||||
>
|
||||
<OlCheckbox checked={props.options.showUnitsEngagementRings} onChange={() => { }}></OlCheckbox>
|
||||
<span>Show Threat Rings</span>
|
||||
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">Q</kbd>
|
||||
</div>
|
||||
<div
|
||||
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
|
||||
onClick={() => { getApp().getMap().setOption("showUnitsAcquisitionRings", !props.options.showUnitsAcquisitionRings) }}
|
||||
>
|
||||
<OlCheckbox checked={props.options.showUnitsAcquisitionRings} onChange={() => { }}></OlCheckbox>
|
||||
<span>Show Detection rings</span>
|
||||
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">E</kbd>
|
||||
</div>
|
||||
<div
|
||||
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
|
||||
onClick={() => { getApp().getMap().setOption("showUnitTargets", !props.options.showUnitTargets) }}
|
||||
>
|
||||
<OlCheckbox checked={props.options.showUnitTargets} onChange={() => { }}></OlCheckbox>
|
||||
<span>Show Detection lines</span>
|
||||
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">F</kbd>
|
||||
</div>
|
||||
<div
|
||||
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
|
||||
onClick={() => { getApp().getMap().setOption("hideUnitsShortRangeRings", !props.options.hideUnitsShortRangeRings) }}
|
||||
>
|
||||
<OlCheckbox checked={props.options.hideUnitsShortRangeRings} onChange={() => { }}></OlCheckbox>
|
||||
<span>Hide Short range Rings</span>
|
||||
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">R</kbd>
|
||||
</div>
|
||||
<div
|
||||
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
|
||||
onClick={() => { getApp().getMap().setOption("hideGroupMembers", !props.options.hideGroupMembers) }}
|
||||
>
|
||||
<OlCheckbox checked={props.options.hideGroupMembers} onChange={() => { }}></OlCheckbox>
|
||||
<span>Hide Group members</span>
|
||||
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">G</kbd>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-col gap-2 p-5 font-normal text-gray-800
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer gap-4
|
||||
p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
getApp()
|
||||
.getMap()
|
||||
.setOption("showUnitLabels", !props.options.showUnitLabels);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox
|
||||
checked={props.options.showUnitLabels}
|
||||
onChange={() => {}}
|
||||
></OlCheckbox>
|
||||
<span>Show Unit Labels</span>
|
||||
<kbd
|
||||
className={`
|
||||
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
|
||||
text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
L
|
||||
</kbd>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer gap-4
|
||||
p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
getApp()
|
||||
.getMap()
|
||||
.setOption(
|
||||
"showUnitsEngagementRings",
|
||||
!props.options.showUnitsEngagementRings
|
||||
);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox
|
||||
checked={props.options.showUnitsEngagementRings}
|
||||
onChange={() => {}}
|
||||
></OlCheckbox>
|
||||
<span>Show Threat Rings</span>
|
||||
<kbd
|
||||
className={`
|
||||
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
|
||||
text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
Q
|
||||
</kbd>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer gap-4
|
||||
p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
getApp()
|
||||
.getMap()
|
||||
.setOption(
|
||||
"showUnitsAcquisitionRings",
|
||||
!props.options.showUnitsAcquisitionRings
|
||||
);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox
|
||||
checked={props.options.showUnitsAcquisitionRings}
|
||||
onChange={() => {}}
|
||||
></OlCheckbox>
|
||||
<span>Show Detection rings</span>
|
||||
<kbd
|
||||
className={`
|
||||
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
|
||||
text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
E
|
||||
</kbd>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer gap-4
|
||||
p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
getApp()
|
||||
.getMap()
|
||||
.setOption("showUnitTargets", !props.options.showUnitTargets);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox
|
||||
checked={props.options.showUnitTargets}
|
||||
onChange={() => {}}
|
||||
></OlCheckbox>
|
||||
<span>Show Detection lines</span>
|
||||
<kbd
|
||||
className={`
|
||||
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
|
||||
text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
F
|
||||
</kbd>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer gap-4
|
||||
p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
getApp()
|
||||
.getMap()
|
||||
.setOption(
|
||||
"hideUnitsShortRangeRings",
|
||||
!props.options.hideUnitsShortRangeRings
|
||||
);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox
|
||||
checked={props.options.hideUnitsShortRangeRings}
|
||||
onChange={() => {}}
|
||||
></OlCheckbox>
|
||||
<span>Hide Short range Rings</span>
|
||||
<kbd
|
||||
className={`
|
||||
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
|
||||
text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
R
|
||||
</kbd>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
group flex flex-row rounded-md justify-content cursor-pointer gap-4
|
||||
p-2
|
||||
dark:hover:bg-olympus-400
|
||||
`}
|
||||
onClick={() => {
|
||||
getApp()
|
||||
.getMap()
|
||||
.setOption("hideGroupMembers", !props.options.hideGroupMembers);
|
||||
}}
|
||||
>
|
||||
<OlCheckbox
|
||||
checked={props.options.hideGroupMembers}
|
||||
onChange={() => {}}
|
||||
></OlCheckbox>
|
||||
<span>Hide Group members</span>
|
||||
<kbd
|
||||
className={`
|
||||
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
|
||||
text-xs font-semibold text-gray-800
|
||||
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
|
||||
`}
|
||||
>
|
||||
G
|
||||
</kbd>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
{/*
|
||||
<hr className="w-auto m-2 my-1 bg-gray-700 border-[1px] dark:border-olympus-500"></hr>
|
||||
<div className="flex flex-col content-center items-start justify-between p-2 gap-2">
|
||||
<div className="flex flex-col">
|
||||
@@ -97,6 +226,7 @@ export function Options(props: {
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</Menu>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,92 @@
|
||||
import React from 'react'
|
||||
import { OlStateButton } from '../components/olstatebutton';
|
||||
import { faPlus, faGamepad, faRuler, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare } from '@fortawesome/free-solid-svg-icons';
|
||||
import { EventsConsumer } from '../../eventscontext';
|
||||
import { StateConsumer } from '../../statecontext';
|
||||
import React from "react";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import {
|
||||
faPlus,
|
||||
faGamepad,
|
||||
faRuler,
|
||||
faPencil,
|
||||
faEllipsisV,
|
||||
faCog,
|
||||
faQuestionCircle,
|
||||
faPlusSquare,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { EventsConsumer } from "../../eventscontext";
|
||||
import { StateConsumer } from "../../statecontext";
|
||||
|
||||
export function SideBar() {
|
||||
return <StateConsumer>
|
||||
{(appState) =>
|
||||
<EventsConsumer>
|
||||
{(events) =>
|
||||
<nav className="flex flex-col z-ui-1 h-full bg-gray-300 dark:bg-olympus-900">
|
||||
<div className="flex-1 flex-wrap items-center justify-center p-4 w-16">
|
||||
<div className="flex flex-col items-center justify-center gap-2.5">
|
||||
<OlStateButton onClick={events.toggleMainMenuVisible} checked={appState.mainMenuVisible} icon={faEllipsisV} tooltip="Hide/show main menu"></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleSpawnMenuVisible} checked={appState.spawnMenuVisible} icon={faPlusSquare} tooltip="Hide/show unit spawn menu"></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleUnitControlMenuVisible} checked={appState.unitControlMenuVisible} icon={faGamepad} tooltip=""></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleMeasureMenuVisible} checked={appState.measureMenuVisible} icon={faRuler} tooltip=""></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleDrawingMenuVisible} checked={appState.drawingMenuVisible} icon={faPencil} tooltip="Hide/show drawing menu"></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap content-end justify-center p-4 w-16">
|
||||
<div className="flex flex-col items-center justify-center gap-2.5">
|
||||
<OlStateButton onClick={() => window.open("https://github.com/Pax1601/DCSOlympus/wiki")} checked={false} icon={faQuestionCircle} tooltip="Open user guide on separate window"></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleOptionsMenuVisible} checked={appState.optionsMenuVisible} icon={faCog} tooltip="Hide/show settings menu"></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
}
|
||||
</EventsConsumer>
|
||||
}
|
||||
</StateConsumer>
|
||||
return (
|
||||
<StateConsumer>
|
||||
{(appState) => (
|
||||
<EventsConsumer>
|
||||
{(events) => (
|
||||
<nav
|
||||
className={`
|
||||
flex flex-col z-ui-1 h-full bg-gray-300
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
>
|
||||
<div className={`
|
||||
w-16 flex-1 flex-wrap items-center justify-center p-4
|
||||
`}>
|
||||
<div className={`
|
||||
flex flex-col items-center justify-center gap-2.5
|
||||
`}>
|
||||
<OlStateButton
|
||||
onClick={events.toggleMainMenuVisible}
|
||||
checked={appState.mainMenuVisible}
|
||||
icon={faEllipsisV}
|
||||
tooltip="Hide/show main menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={events.toggleSpawnMenuVisible}
|
||||
checked={appState.spawnMenuVisible}
|
||||
icon={faPlusSquare}
|
||||
tooltip="Hide/show unit spawn menu"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={events.toggleUnitControlMenuVisible}
|
||||
checked={appState.unitControlMenuVisible}
|
||||
icon={faGamepad}
|
||||
tooltip=""
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={events.toggleMeasureMenuVisible}
|
||||
checked={appState.measureMenuVisible}
|
||||
icon={faRuler}
|
||||
tooltip=""
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={events.toggleDrawingMenuVisible}
|
||||
checked={appState.drawingMenuVisible}
|
||||
icon={faPencil}
|
||||
tooltip="Hide/show drawing menu"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-16 flex-wrap content-end justify-center p-4">
|
||||
<div className={`
|
||||
flex flex-col items-center justify-center gap-2.5
|
||||
`}>
|
||||
<OlStateButton
|
||||
onClick={() =>
|
||||
window.open("https://github.com/Pax1601/DCSOlympus/wiki")
|
||||
}
|
||||
checked={false}
|
||||
icon={faQuestionCircle}
|
||||
tooltip="Open user guide on separate window"
|
||||
></OlStateButton>
|
||||
<OlStateButton
|
||||
onClick={events.toggleOptionsMenuVisible}
|
||||
checked={appState.optionsMenuVisible}
|
||||
icon={faCog}
|
||||
tooltip="Hide/show settings menu"
|
||||
></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
</EventsConsumer>
|
||||
)}
|
||||
</StateConsumer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,115 +1,179 @@
|
||||
import React, { useState } from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||
import { OlSearchBar } from "../components/olsearchbar";
|
||||
import { OlAccordion } from "../components/olaccordion";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlUnitEntryList } from "../components/olunitlistentry";
|
||||
import { UnitSpawnMenu } from "./unitspawnmenu";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
import { olButtonsVisibilityAircraft, olButtonsVisibilityGroundunit, olButtonsVisibilityGroundunitSam, olButtonsVisibilityHelicopter, olButtonsVisibilityNavyunit } from "../components/olicons";
|
||||
import {
|
||||
olButtonsVisibilityAircraft,
|
||||
olButtonsVisibilityGroundunit,
|
||||
olButtonsVisibilityGroundunitSam,
|
||||
olButtonsVisibilityHelicopter,
|
||||
olButtonsVisibilityNavyunit,
|
||||
} from "../components/olicons";
|
||||
|
||||
library.add(faPlus);
|
||||
|
||||
function filterUnits(blueprints: { [key: string]: UnitBlueprint }, filterString: string) {
|
||||
var filteredUnits = {};
|
||||
if (blueprints) {
|
||||
Object.entries(blueprints).forEach(([key, value]) => {
|
||||
if (value.enabled && (filterString === "" || value.label.includes(filterString)))
|
||||
filteredUnits[key] = value;
|
||||
});
|
||||
}
|
||||
return filteredUnits;
|
||||
function filterUnits(
|
||||
blueprints: { [key: string]: UnitBlueprint },
|
||||
filterString: string
|
||||
) {
|
||||
var filteredUnits = {};
|
||||
if (blueprints) {
|
||||
Object.entries(blueprints).forEach(([key, value]) => {
|
||||
if (
|
||||
value.enabled &&
|
||||
(filterString === "" || value.label.includes(filterString))
|
||||
)
|
||||
filteredUnits[key] = value;
|
||||
});
|
||||
}
|
||||
return filteredUnits;
|
||||
}
|
||||
|
||||
export function SpawnMenu(props: {
|
||||
open: boolean,
|
||||
onClose: () => void,
|
||||
children?: JSX.Element | JSX.Element[],
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
}) {
|
||||
var [blueprint, setBlueprint] = useState(null as (null | UnitBlueprint));
|
||||
var [filterString, setFilterString] = useState("");
|
||||
var [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
|
||||
var [filterString, setFilterString] = useState("");
|
||||
|
||||
/* Filter aircrafts, helicopters, and navyunits */
|
||||
const filteredAircraft = filterUnits(getApp()?.getAircraftDatabase()?.blueprints, filterString);
|
||||
const filteredHelicopters = filterUnits(getApp()?.getHelicopterDatabase()?.blueprints, filterString);
|
||||
const filteredNavyUnits = filterUnits(getApp()?.getNavyUnitDatabase()?.blueprints, filterString);
|
||||
/* Filter aircrafts, helicopters, and navyunits */
|
||||
const filteredAircraft = filterUnits(
|
||||
getApp()?.getAircraftDatabase()?.blueprints,
|
||||
filterString
|
||||
);
|
||||
const filteredHelicopters = filterUnits(
|
||||
getApp()?.getHelicopterDatabase()?.blueprints,
|
||||
filterString
|
||||
);
|
||||
const filteredNavyUnits = filterUnits(
|
||||
getApp()?.getNavyUnitDatabase()?.blueprints,
|
||||
filterString
|
||||
);
|
||||
|
||||
/* Split ground units between air defence and all others */
|
||||
var filteredAirDefense = {};
|
||||
var filteredGroundUnits = {};
|
||||
Object.keys(getApp()?.getGroundUnitDatabase()?.blueprints ?? {}).forEach((key) => {
|
||||
var blueprint = getApp()?.getGroundUnitDatabase()?.blueprints[key];
|
||||
var type = blueprint.label;
|
||||
if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type)) {
|
||||
filteredAirDefense[key] = blueprint;
|
||||
} else {
|
||||
filteredGroundUnits[key] = blueprint;
|
||||
}
|
||||
});
|
||||
filteredAirDefense = filterUnits(filteredAirDefense, filterString);
|
||||
filteredGroundUnits = filterUnits(filteredGroundUnits, filterString);
|
||||
/* Split ground units between air defence and all others */
|
||||
var filteredAirDefense = {};
|
||||
var filteredGroundUnits = {};
|
||||
Object.keys(getApp()?.getGroundUnitDatabase()?.blueprints ?? {}).forEach(
|
||||
(key) => {
|
||||
var blueprint = getApp()?.getGroundUnitDatabase()?.blueprints[key];
|
||||
var type = blueprint.label;
|
||||
if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type)) {
|
||||
filteredAirDefense[key] = blueprint;
|
||||
} else {
|
||||
filteredGroundUnits[key] = blueprint;
|
||||
}
|
||||
}
|
||||
);
|
||||
filteredAirDefense = filterUnits(filteredAirDefense, filterString);
|
||||
filteredGroundUnits = filterUnits(filteredGroundUnits, filterString);
|
||||
|
||||
/* When the menu is closed, reset the blueprint */
|
||||
if (!props.open && blueprint !== null)
|
||||
setBlueprint(null);
|
||||
/* When the menu is closed, reset the blueprint */
|
||||
if (!props.open && blueprint !== null) setBlueprint(null);
|
||||
|
||||
return <Menu {...props}
|
||||
title="Spawn menu"
|
||||
showBackButton={blueprint !== null}
|
||||
onBack={() => setBlueprint(null)}
|
||||
>
|
||||
<>
|
||||
{(blueprint === null) && <div className="p-5">
|
||||
<OlSearchBar onChange={(ev) => setFilterString(ev.target.value)} />
|
||||
<OlAccordion title={`Aircraft`}>
|
||||
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
|
||||
{Object.keys(filteredAircraft).map((key) => {
|
||||
const blueprint = getApp().getAircraftDatabase().blueprints[key];
|
||||
return <OlUnitEntryList key={key} icon={olButtonsVisibilityAircraft} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title={`Helicopters`}>
|
||||
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
|
||||
{Object.keys(filteredHelicopters).map((key) => {
|
||||
const blueprint = getApp().getHelicopterDatabase().blueprints[key];
|
||||
return <OlUnitEntryList key={key} icon={olButtonsVisibilityHelicopter} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title={`SAM & AAA`}>
|
||||
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
|
||||
{Object.keys(filteredAirDefense).map((key) => {
|
||||
const blueprint = getApp().getGroundUnitDatabase().blueprints[key];
|
||||
return <OlUnitEntryList key={key} icon={olButtonsVisibilityGroundunitSam} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title={`Ground Units`}>
|
||||
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
|
||||
{Object.keys(filteredGroundUnits).map((key) => {
|
||||
const blueprint = getApp().getGroundUnitDatabase().blueprints[key];
|
||||
return <OlUnitEntryList key={key} icon={olButtonsVisibilityGroundunit} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title={`Ships and submarines`}>
|
||||
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
|
||||
{Object.keys(filteredNavyUnits).map((key) => {
|
||||
const blueprint = getApp().getNavyUnitDatabase().blueprints[key];
|
||||
return <OlUnitEntryList key={key} icon={olButtonsVisibilityNavyunit} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title="Effects (smokes, explosions etc)">
|
||||
return (
|
||||
<Menu
|
||||
{...props}
|
||||
title="Spawn menu"
|
||||
showBackButton={blueprint !== null}
|
||||
onBack={() => setBlueprint(null)}
|
||||
>
|
||||
<>
|
||||
{blueprint === null && (
|
||||
<div className="p-5">
|
||||
<OlSearchBar onChange={(ev) => setFilterString(ev.target.value)} />
|
||||
<OlAccordion title={`Aircraft`}>
|
||||
<div className="flex max-h-80 flex-col gap-1 overflow-y-scroll">
|
||||
{Object.keys(filteredAircraft).map((key) => {
|
||||
const blueprint =
|
||||
getApp().getAircraftDatabase().blueprints[key];
|
||||
return (
|
||||
<OlUnitEntryList
|
||||
key={key}
|
||||
icon={olButtonsVisibilityAircraft}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title={`Helicopters`}>
|
||||
<div className="flex max-h-80 flex-col gap-1 overflow-y-scroll">
|
||||
{Object.keys(filteredHelicopters).map((key) => {
|
||||
const blueprint =
|
||||
getApp().getHelicopterDatabase().blueprints[key];
|
||||
return (
|
||||
<OlUnitEntryList
|
||||
key={key}
|
||||
icon={olButtonsVisibilityHelicopter}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title={`SAM & AAA`}>
|
||||
<div className="flex max-h-80 flex-col gap-1 overflow-y-scroll">
|
||||
{Object.keys(filteredAirDefense).map((key) => {
|
||||
const blueprint =
|
||||
getApp().getGroundUnitDatabase().blueprints[key];
|
||||
return (
|
||||
<OlUnitEntryList
|
||||
key={key}
|
||||
icon={olButtonsVisibilityGroundunitSam}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title={`Ground Units`}>
|
||||
<div className="flex max-h-80 flex-col gap-1 overflow-y-scroll">
|
||||
{Object.keys(filteredGroundUnits).map((key) => {
|
||||
const blueprint =
|
||||
getApp().getGroundUnitDatabase().blueprints[key];
|
||||
return (
|
||||
<OlUnitEntryList
|
||||
key={key}
|
||||
icon={olButtonsVisibilityGroundunit}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title={`Ships and submarines`}>
|
||||
<div className="flex max-h-80 flex-col gap-1 overflow-y-scroll">
|
||||
{Object.keys(filteredNavyUnits).map((key) => {
|
||||
const blueprint =
|
||||
getApp().getNavyUnitDatabase().blueprints[key];
|
||||
return (
|
||||
<OlUnitEntryList
|
||||
key={key}
|
||||
icon={olButtonsVisibilityNavyunit}
|
||||
blueprint={blueprint}
|
||||
onClick={() => setBlueprint(blueprint)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title="Effects (smokes, explosions etc)"></OlAccordion>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</OlAccordion>
|
||||
</div>
|
||||
}
|
||||
|
||||
{!(blueprint === null) && <UnitSpawnMenu blueprint={blueprint} />}
|
||||
</>
|
||||
</Menu>
|
||||
}
|
||||
{!(blueprint === null) && <UnitSpawnMenu blueprint={blueprint} />}
|
||||
</>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,89 +1,128 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Unit } from '../../unit/unit';
|
||||
import { ContextActionSet } from '../../unit/contextactionset';
|
||||
import { OlStateButton } from '../components/olstatebutton';
|
||||
import { getApp } from '../../olympusapp';
|
||||
import { ContextAction } from '../../unit/contextaction';
|
||||
import { CONTEXT_ACTION } from '../../constants/constants';
|
||||
import { FaInfoCircle } from 'react-icons/fa';
|
||||
import React, { useState } from "react";
|
||||
import { Unit } from "../../unit/unit";
|
||||
import { ContextActionSet } from "../../unit/contextactionset";
|
||||
import { OlStateButton } from "../components/olstatebutton";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { ContextAction } from "../../unit/contextaction";
|
||||
import { CONTEXT_ACTION } from "../../constants/constants";
|
||||
import { FaInfoCircle } from "react-icons/fa";
|
||||
|
||||
export function UnitMouseControlBar(props: {
|
||||
export function UnitMouseControlBar(props: {}) {
|
||||
var [open, setOpen] = useState(false);
|
||||
var [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
var [contextActionsSet, setContextActionsSet] = useState(
|
||||
new ContextActionSet()
|
||||
);
|
||||
var [activeContextAction, setActiveContextAction] = useState(
|
||||
null as null | ContextAction
|
||||
);
|
||||
|
||||
}) {
|
||||
var [open, setOpen] = useState(false);
|
||||
var [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
var [contextActionsSet, setContextActionsSet] = useState(new ContextActionSet());
|
||||
var [activeContextAction, setActiveContextAction] = useState(null as null | ContextAction);
|
||||
/* When a unit is selected, open the menu */
|
||||
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
|
||||
setOpen(true);
|
||||
setSelectedUnits(ev.detail as Unit[]);
|
||||
|
||||
/* When a unit is selected, open the menu */
|
||||
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
|
||||
setOpen(true);
|
||||
setSelectedUnits(ev.detail as Unit[]);
|
||||
updateData();
|
||||
});
|
||||
|
||||
updateData();
|
||||
})
|
||||
/* When a unit is deselected, refresh the view */
|
||||
document.addEventListener("unitDeselection", (ev: CustomEventInit) => {
|
||||
/* TODO add delay to avoid doing it too many times */
|
||||
updateData();
|
||||
});
|
||||
|
||||
/* When a unit is deselected, refresh the view */
|
||||
document.addEventListener("unitDeselection", (ev: CustomEventInit) => {
|
||||
/* TODO add delay to avoid doing it too many times */
|
||||
updateData();
|
||||
})
|
||||
/* When all units are deselected clean the view */
|
||||
document.addEventListener("clearSelection", () => {
|
||||
setOpen(false);
|
||||
setSelectedUnits([]);
|
||||
updateData();
|
||||
});
|
||||
|
||||
/* When all units are deselected clean the view */
|
||||
document.addEventListener("clearSelection", () => {
|
||||
setOpen(false);
|
||||
setSelectedUnits([]);
|
||||
updateData();
|
||||
})
|
||||
/* Deselect the context action when exiting state */
|
||||
document.addEventListener("mapStateChanged", (ev) => {
|
||||
setOpen(ev.detail === CONTEXT_ACTION);
|
||||
});
|
||||
|
||||
/* Deselect the context action when exiting state */
|
||||
document.addEventListener("mapStateChanged", (ev) => {
|
||||
setOpen(ev.detail === CONTEXT_ACTION);
|
||||
})
|
||||
/* Update the current values of the shown data */
|
||||
function updateData() {
|
||||
var newContextActionSet = new ContextActionSet();
|
||||
|
||||
getApp()
|
||||
.getUnitsManager()
|
||||
.getSelectedUnits()
|
||||
.forEach((unit: Unit) => {
|
||||
unit.appendContextActions(newContextActionSet);
|
||||
});
|
||||
|
||||
/* Update the current values of the shown data */
|
||||
function updateData() {
|
||||
var newContextActionSet = new ContextActionSet();
|
||||
setContextActionsSet(newContextActionSet);
|
||||
setActiveContextAction(null);
|
||||
}
|
||||
|
||||
getApp().getUnitsManager().getSelectedUnits().forEach((unit: Unit) => {
|
||||
unit.appendContextActions(newContextActionSet);
|
||||
})
|
||||
|
||||
setContextActionsSet(newContextActionSet);
|
||||
setActiveContextAction(null);
|
||||
}
|
||||
|
||||
return <> {
|
||||
open && <>
|
||||
<div className='flex gap-2 rounded-md absolute top-20 left-[50%] translate-x-[-50%] bg-gray-200 dark:bg-olympus-900 z-ui-2 p-2'>
|
||||
{
|
||||
Object.values(contextActionsSet.getContextActions()).map((contextAction) => {
|
||||
return <OlStateButton checked={contextAction === activeContextAction} icon={contextAction.getIcon()} tooltip={contextAction.getLabel()} onClick={() => {
|
||||
if (contextAction.getOptions().executeImmediately) {
|
||||
setActiveContextAction(null);
|
||||
contextAction.executeCallback(null, null);
|
||||
} else {
|
||||
if (activeContextAction != contextAction) {
|
||||
setActiveContextAction(contextAction);
|
||||
getApp().getMap().setState(CONTEXT_ACTION, { contextAction: contextAction });
|
||||
} else {
|
||||
setActiveContextAction(null);
|
||||
getApp().getMap().setState(CONTEXT_ACTION, { contextAction: null });
|
||||
}
|
||||
}
|
||||
}} />
|
||||
})
|
||||
}
|
||||
</div>
|
||||
{ activeContextAction && <div className='flex gap-2 p-4 items-center rounded-md absolute top-36 left-[50%] translate-x-[-50%] bg-gray-200 dark:bg-olympus-800 z-ui-1'>
|
||||
<FaInfoCircle className="text-blue-500 mr-2 text-sm"/>
|
||||
<div className='border-l-[1px] px-5 dark:text-gray-400'>
|
||||
{activeContextAction.getDescription()}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{" "}
|
||||
{open && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-20 flex translate-x-[-50%] gap-2
|
||||
rounded-md bg-gray-200 p-2 z-ui-2
|
||||
dark:bg-olympus-900
|
||||
`}
|
||||
>
|
||||
{Object.values(contextActionsSet.getContextActions()).map(
|
||||
(contextAction) => {
|
||||
return (
|
||||
<OlStateButton
|
||||
checked={contextAction === activeContextAction}
|
||||
icon={contextAction.getIcon()}
|
||||
tooltip={contextAction.getLabel()}
|
||||
onClick={() => {
|
||||
if (contextAction.getOptions().executeImmediately) {
|
||||
setActiveContextAction(null);
|
||||
contextAction.executeCallback(null, null);
|
||||
} else {
|
||||
if (activeContextAction != contextAction) {
|
||||
setActiveContextAction(contextAction);
|
||||
getApp()
|
||||
.getMap()
|
||||
.setState(CONTEXT_ACTION, {
|
||||
contextAction: contextAction,
|
||||
});
|
||||
} else {
|
||||
setActiveContextAction(null);
|
||||
getApp()
|
||||
.getMap()
|
||||
.setState(CONTEXT_ACTION, { contextAction: null });
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
{activeContextAction && (
|
||||
<div
|
||||
className={`
|
||||
absolute left-[50%] top-36 flex translate-x-[-50%] items-center
|
||||
gap-2 rounded-md bg-gray-200 p-4 z-ui-1
|
||||
dark:bg-olympus-800
|
||||
`}
|
||||
>
|
||||
<FaInfoCircle className="mr-2 text-sm text-blue-500" />
|
||||
<div
|
||||
className={`
|
||||
border-l-[1px] px-5
|
||||
dark:text-gray-400
|
||||
`}
|
||||
>
|
||||
{activeContextAction.getDescription()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
|
||||
import { OlNumberInput } from "../components/olnumberinput";
|
||||
import { OlLabelToggle } from "../components/ollabeltoggle";
|
||||
import { OlRangeSlider } from "../components/olrangeslider";
|
||||
import { OlDropdownItem, OlDropdown } from '../components/oldropdown';
|
||||
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
|
||||
import { LoadoutBlueprint, UnitBlueprint } from "../../interfaces";
|
||||
import { Coalition } from "../../types/types";
|
||||
import { getApp } from "../../olympusapp";
|
||||
@@ -12,138 +12,243 @@ import { IDLE, SPAWN_UNIT } from "../../constants/constants";
|
||||
import { ftToM, getUnitCategoryByBlueprint } from "../../other/utils";
|
||||
import { LatLng } from "leaflet";
|
||||
|
||||
export function UnitSpawnMenu(props: {
|
||||
blueprint: UnitBlueprint
|
||||
}) {
|
||||
/* Compute the min and max values depending on the unit type */
|
||||
const minNumber = 1;
|
||||
const maxNumber = 4;
|
||||
const minAltitude = 0;
|
||||
const maxAltitude = 30000;
|
||||
const altitudeStep = 500;
|
||||
|
||||
/* State initialization */
|
||||
var [spawnCoalition, setSpawnCoalition] = useState("blue" as Coalition);
|
||||
var [spawnNumber, setSpawnNumber] = useState(1);
|
||||
var [spawnRole, setSpawnRole] = useState("");
|
||||
var [spawnLoadoutName, setSpawnLoadout] = useState("");
|
||||
var [spawnAltitude, setSpawnAltitude] = useState((maxAltitude - minAltitude) / 2);
|
||||
var [spawnAltitudeType, setSpawnAltitudeType] = useState(false);
|
||||
export function UnitSpawnMenu(props: { blueprint: UnitBlueprint }) {
|
||||
/* Compute the min and max values depending on the unit type */
|
||||
const minNumber = 1;
|
||||
const maxNumber = 4;
|
||||
const minAltitude = 0;
|
||||
const maxAltitude = 30000;
|
||||
const altitudeStep = 500;
|
||||
|
||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||
useEffect(() => {
|
||||
if (props.blueprint !== null) {
|
||||
getApp()?.getMap()?.setState(SPAWN_UNIT, {
|
||||
spawnRequestTable: {
|
||||
category: getUnitCategoryByBlueprint(props.blueprint),
|
||||
unit: {
|
||||
unitType: props.blueprint.name,
|
||||
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
|
||||
skill: "High",
|
||||
liveryID: "",
|
||||
altitude: ftToM(spawnAltitude),
|
||||
loadout: props.blueprint.loadouts?.find((loadout) => { return loadout.name === spawnLoadoutName})?.code ?? ""
|
||||
},
|
||||
coalition: spawnCoalition
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (getApp()?.getMap()?.getState() === SPAWN_UNIT)
|
||||
getApp()?.getMap()?.setState(IDLE);
|
||||
}
|
||||
})
|
||||
/* State initialization */
|
||||
var [spawnCoalition, setSpawnCoalition] = useState("blue" as Coalition);
|
||||
var [spawnNumber, setSpawnNumber] = useState(1);
|
||||
var [spawnRole, setSpawnRole] = useState("");
|
||||
var [spawnLoadoutName, setSpawnLoadout] = useState("");
|
||||
var [spawnAltitude, setSpawnAltitude] = useState(
|
||||
(maxAltitude - minAltitude) / 2
|
||||
);
|
||||
var [spawnAltitudeType, setSpawnAltitudeType] = useState(false);
|
||||
|
||||
/* Get a list of all the roles */
|
||||
const roles: string[] = [];
|
||||
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
|
||||
loadout.roles.forEach((role) => {
|
||||
!roles.includes(role) && roles.push(role);
|
||||
})
|
||||
})
|
||||
/* When the menu is opened show the unit preview on the map as a cursor */
|
||||
useEffect(() => {
|
||||
if (props.blueprint !== null) {
|
||||
getApp()
|
||||
?.getMap()
|
||||
?.setState(SPAWN_UNIT, {
|
||||
spawnRequestTable: {
|
||||
category: getUnitCategoryByBlueprint(props.blueprint),
|
||||
unit: {
|
||||
unitType: props.blueprint.name,
|
||||
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
|
||||
skill: "High",
|
||||
liveryID: "",
|
||||
altitude: ftToM(spawnAltitude),
|
||||
loadout:
|
||||
props.blueprint.loadouts?.find((loadout) => {
|
||||
return loadout.name === spawnLoadoutName;
|
||||
})?.code ?? "",
|
||||
},
|
||||
coalition: spawnCoalition,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (getApp()?.getMap()?.getState() === SPAWN_UNIT)
|
||||
getApp()?.getMap()?.setState(IDLE);
|
||||
}
|
||||
});
|
||||
|
||||
/* Initialize the role */
|
||||
spawnRole === "" && roles.length > 0 && setSpawnRole(roles[0]);
|
||||
/* Get a list of all the roles */
|
||||
const roles: string[] = [];
|
||||
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
|
||||
loadout.roles.forEach((role) => {
|
||||
!roles.includes(role) && roles.push(role);
|
||||
});
|
||||
});
|
||||
|
||||
/* Get a list of all the loadouts */
|
||||
const loadouts: LoadoutBlueprint[] = [];
|
||||
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
|
||||
loadout.roles.includes(spawnRole) && loadouts.push(loadout);
|
||||
})
|
||||
/* Initialize the role */
|
||||
spawnRole === "" && roles.length > 0 && setSpawnRole(roles[0]);
|
||||
|
||||
/* Initialize the loadout */
|
||||
spawnLoadoutName === "" && loadouts.length > 0 && setSpawnLoadout(loadouts[0].name)
|
||||
const spawnLoadout = props.blueprint.loadouts?.find((loadout) => { return loadout.name === spawnLoadoutName; })
|
||||
/* Get a list of all the loadouts */
|
||||
const loadouts: LoadoutBlueprint[] = [];
|
||||
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
|
||||
loadout.roles.includes(spawnRole) && loadouts.push(loadout);
|
||||
});
|
||||
|
||||
return <div className="flex flex-col">
|
||||
<OlUnitSummary blueprint={props.blueprint} coalition={spawnCoalition} />
|
||||
<div className="px-5 pt-6 pb-8 h-fit flex flex-col gap-5">
|
||||
<div className="flex flex-row content-center justify-between w-full">
|
||||
<OlCoalitionToggle
|
||||
coalition={spawnCoalition}
|
||||
onClick={() => {
|
||||
spawnCoalition === 'blue' && setSpawnCoalition('neutral');
|
||||
spawnCoalition === 'neutral' && setSpawnCoalition('red');
|
||||
spawnCoalition === 'red' && setSpawnCoalition('blue');
|
||||
}}
|
||||
/>
|
||||
<OlNumberInput
|
||||
value={spawnNumber}
|
||||
min={minNumber}
|
||||
max={maxNumber}
|
||||
onDecrease={() => { setSpawnNumber(Math.max(minNumber, spawnNumber - 1)) }}
|
||||
onIncrease={() => { setSpawnNumber(Math.min(maxNumber, spawnNumber + 1)) }}
|
||||
onChange={(ev) => { !isNaN(Number(ev.target.value)) && setSpawnNumber(Math.max(minNumber, Math.min(maxNumber, Number(ev.target.value)))) }}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-row content-center items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<span className="font-normal dark:text-white">Altitude</span>
|
||||
<span className="font-bold dark:text-blue-500">{`${Intl.NumberFormat("en-US").format(spawnAltitude)} FT`}</span>
|
||||
</div>
|
||||
<OlLabelToggle toggled={spawnAltitudeType} leftLabel={"AGL"} rightLabel={"ASL"} onClick={() => setSpawnAltitudeType(!spawnAltitudeType)} />
|
||||
</div>
|
||||
<OlRangeSlider onChange={(ev) => setSpawnAltitude(Number(ev.target.value))} value={spawnAltitude} min={minAltitude} max={maxAltitude} step={altitudeStep} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-row content-center justify-between">
|
||||
<span className="font-normal dark:text-white h-8">Role</span>
|
||||
</div>
|
||||
<OlDropdown label={spawnRole} className="w-full">
|
||||
{
|
||||
roles.map((role) => {
|
||||
return <OlDropdownItem onClick={() => { setSpawnRole(role); setSpawnLoadout(""); }} className="w-full">
|
||||
{role}
|
||||
</OlDropdownItem>
|
||||
})
|
||||
}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-row content-center justify-between">
|
||||
<span className="font-normal dark:text-white h-8">Weapons</span>
|
||||
</div>
|
||||
<OlDropdown label={spawnLoadoutName} className="w-full w-max-full">
|
||||
{
|
||||
loadouts.map((loadout) => {
|
||||
return <OlDropdownItem onClick={() => { setSpawnLoadout(loadout.name) }} className="w-full">
|
||||
<span className="text-left w-full w-max-full text-nowrap text-ellipsis overflow-hidden">
|
||||
{loadout.name}
|
||||
</span>
|
||||
</OlDropdownItem>
|
||||
})
|
||||
}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
/* Initialize the loadout */
|
||||
spawnLoadoutName === "" &&
|
||||
loadouts.length > 0 &&
|
||||
setSpawnLoadout(loadouts[0].name);
|
||||
const spawnLoadout = props.blueprint.loadouts?.find((loadout) => {
|
||||
return loadout.name === spawnLoadoutName;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<OlUnitSummary blueprint={props.blueprint} coalition={spawnCoalition} />
|
||||
<div className="flex h-fit flex-col gap-5 px-5 pb-8 pt-6">
|
||||
<div className="flex w-full flex-row content-center justify-between">
|
||||
<OlCoalitionToggle
|
||||
coalition={spawnCoalition}
|
||||
onClick={() => {
|
||||
spawnCoalition === "blue" && setSpawnCoalition("neutral");
|
||||
spawnCoalition === "neutral" && setSpawnCoalition("red");
|
||||
spawnCoalition === "red" && setSpawnCoalition("blue");
|
||||
}}
|
||||
/>
|
||||
<OlNumberInput
|
||||
value={spawnNumber}
|
||||
min={minNumber}
|
||||
max={maxNumber}
|
||||
onDecrease={() => {
|
||||
setSpawnNumber(Math.max(minNumber, spawnNumber - 1));
|
||||
}}
|
||||
onIncrease={() => {
|
||||
setSpawnNumber(Math.min(maxNumber, spawnNumber + 1));
|
||||
}}
|
||||
onChange={(ev) => {
|
||||
!isNaN(Number(ev.target.value)) &&
|
||||
setSpawnNumber(
|
||||
Math.max(
|
||||
minNumber,
|
||||
Math.min(maxNumber, Number(ev.target.value))
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="dark:bg-olympus-200/30 h-fit p-4 flex flex-col gap-1">
|
||||
{spawnLoadout && spawnLoadout.items.map((item) => {
|
||||
return <div className="flex gap-2 content-center">
|
||||
<div className="my-auto font-bold text-sm rounded-full text-gray-500 dark:bg-[#17212D] w-6 py-0.5 text-center">{item.quantity}</div>
|
||||
<div className="my-auto text-sm dark:text-gray-300">{item.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className={`
|
||||
flex flex-row content-center items-center justify-between
|
||||
`}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className={`
|
||||
font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Altitude
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
font-bold
|
||||
dark:text-blue-500
|
||||
`}
|
||||
>{`${Intl.NumberFormat("en-US").format(spawnAltitude)} FT`}</span>
|
||||
</div>
|
||||
<OlLabelToggle
|
||||
toggled={spawnAltitudeType}
|
||||
leftLabel={"AGL"}
|
||||
rightLabel={"ASL"}
|
||||
onClick={() => setSpawnAltitudeType(!spawnAltitudeType)}
|
||||
/>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
onChange={(ev) => setSpawnAltitude(Number(ev.target.value))}
|
||||
value={spawnAltitude}
|
||||
min={minAltitude}
|
||||
max={maxAltitude}
|
||||
step={altitudeStep}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-row content-center justify-between">
|
||||
<span
|
||||
className={`
|
||||
h-8 font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Role
|
||||
</span>
|
||||
</div>
|
||||
<OlDropdown label={spawnRole} className="w-full">
|
||||
{roles.map((role) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
onClick={() => {
|
||||
setSpawnRole(role);
|
||||
setSpawnLoadout("");
|
||||
}}
|
||||
className={`w-full`}
|
||||
>
|
||||
{role}
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex flex-row content-center justify-between">
|
||||
<span
|
||||
className={`
|
||||
h-8 font-normal
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Weapons
|
||||
</span>
|
||||
</div>
|
||||
<OlDropdown
|
||||
label={spawnLoadoutName}
|
||||
className={`w-full w-max-full`}
|
||||
>
|
||||
{loadouts.map((loadout) => {
|
||||
return (
|
||||
<OlDropdownItem
|
||||
onClick={() => {
|
||||
setSpawnLoadout(loadout.name);
|
||||
}}
|
||||
className={`w-full`}
|
||||
>
|
||||
<span
|
||||
className={`
|
||||
w-full overflow-hidden text-ellipsis text-nowrap text-left
|
||||
w-max-full
|
||||
`}
|
||||
>
|
||||
{loadout.name}
|
||||
</span>
|
||||
</OlDropdownItem>
|
||||
);
|
||||
})}
|
||||
</OlDropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
flex h-fit flex-col gap-1 p-4
|
||||
dark:bg-olympus-200/30
|
||||
`}
|
||||
>
|
||||
{spawnLoadout &&
|
||||
spawnLoadout.items.map((item) => {
|
||||
return (
|
||||
<div className="flex content-center gap-2">
|
||||
<div
|
||||
className={`
|
||||
my-auto w-6 rounded-full py-0.5 text-center text-sm
|
||||
font-bold text-gray-500
|
||||
dark:bg-[#17212D]
|
||||
`}
|
||||
>
|
||||
{item.quantity}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
my-auto text-sm
|
||||
dark:text-gray-300
|
||||
`}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,96 +1,95 @@
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #FFFFFFAA;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
background: #ffffffaa;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
/* IE and Edge */
|
||||
scrollbar-width: none;
|
||||
/* Firefox */
|
||||
-ms-overflow-style: none;
|
||||
/* IE and Edge */
|
||||
scrollbar-width: none;
|
||||
/* Firefox */
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
/* removing default appearance */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
/* creating a custom design */
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border-radius: 15px;
|
||||
/* overflow: hidden; remove this line*/
|
||||
|
||||
/* New additions */
|
||||
height: 6px;
|
||||
background: #4B5563;
|
||||
}
|
||||
|
||||
/* Thumb: webkit */
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
/* removing default appearance */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
/* creating a custom design */
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
background-color: #4B5563;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #6B7280;
|
||||
|
||||
/* box-shadow: -407px 0 0 400px #4B5563; emove this line */
|
||||
transition: .2s ease-in-out;
|
||||
}
|
||||
|
||||
/* Thumb: Firefox */
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
background-color: #4B5563;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
border: 1px solid #6B7280;
|
||||
|
||||
/* box-shadow: -407px 0 0 400px #4B5563; emove this line */
|
||||
transition: .2s ease-in-out;
|
||||
}
|
||||
|
||||
/* Hover, active & focus Thumb: Webkit */
|
||||
input[type="range"]::-webkit-slider-thumb:hover {
|
||||
box-shadow: 0 0 0 5px #3F83F822
|
||||
}
|
||||
input[type="range"]:active::-webkit-slider-thumb {
|
||||
box-shadow: 0 0 0 7px #3F83F855
|
||||
}
|
||||
input[type="range"]:focus::-webkit-slider-thumb {
|
||||
box-shadow: 0 0 0 7px #3F83F855
|
||||
}
|
||||
|
||||
/* Hover, active & focus Thumb: Firefox */
|
||||
input[type="range"]::-moz-range-thumb:hover {
|
||||
box-shadow: 0 0 0 10px #3F83F822
|
||||
}
|
||||
input[type="range"]:active::-moz-range-thumb {
|
||||
box-shadow: 0 0 0 13px #3F83F855
|
||||
}
|
||||
input[type="range"]:focus::-moz-range-thumb {
|
||||
box-shadow: 0 0 0 13px #3F83F855
|
||||
}
|
||||
/* removing default appearance */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
/* creating a custom design */
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border-radius: 15px;
|
||||
/* overflow: hidden; remove this line*/
|
||||
|
||||
/* New additions */
|
||||
height: 6px;
|
||||
background: #4b5563;
|
||||
}
|
||||
|
||||
/* Thumb: webkit */
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
/* removing default appearance */
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
/* creating a custom design */
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
background-color: #4b5563;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #6b7280;
|
||||
|
||||
/* box-shadow: -407px 0 0 400px #4B5563; emove this line */
|
||||
transition: 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* Thumb: Firefox */
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
background-color: #4b5563;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
border: 1px solid #6b7280;
|
||||
|
||||
/* box-shadow: -407px 0 0 400px #4B5563; emove this line */
|
||||
transition: 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* Hover, active & focus Thumb: Webkit */
|
||||
input[type="range"]::-webkit-slider-thumb:hover {
|
||||
box-shadow: 0 0 0 5px #3f83f822;
|
||||
}
|
||||
input[type="range"]:active::-webkit-slider-thumb {
|
||||
box-shadow: 0 0 0 7px #3f83f855;
|
||||
}
|
||||
input[type="range"]:focus::-webkit-slider-thumb {
|
||||
box-shadow: 0 0 0 7px #3f83f855;
|
||||
}
|
||||
|
||||
/* Hover, active & focus Thumb: Firefox */
|
||||
input[type="range"]::-moz-range-thumb:hover {
|
||||
box-shadow: 0 0 0 10px #3f83f822;
|
||||
}
|
||||
input[type="range"]:active::-moz-range-thumb {
|
||||
box-shadow: 0 0 0 13px #3f83f855;
|
||||
}
|
||||
input[type="range"]:focus::-moz-range-thumb {
|
||||
box-shadow: 0 0 0 13px #3f83f855;
|
||||
}
|
||||
|
||||
@@ -1,191 +1,234 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import './ui.css'
|
||||
import React, { useEffect, useState } from "react";
|
||||
import "./ui.css";
|
||||
|
||||
import { EventsProvider } from '../eventscontext'
|
||||
import { StateProvider } from '../statecontext'
|
||||
import { EventsProvider } from "../eventscontext";
|
||||
import { StateProvider } from "../statecontext";
|
||||
|
||||
import { Header } from './panels/header'
|
||||
import { SpawnMenu } from './panels/spawnmenu'
|
||||
import { UnitControlMenu } from './panels/unitcontrolmenu'
|
||||
import { MainMenu } from './panels/mainmenu'
|
||||
import { SideBar } from './panels/sidebar';
|
||||
import { Options } from './panels/options';
|
||||
import { MapHiddenTypes, MapOptions } from '../types/types'
|
||||
import { BLUE_COMMANDER, GAME_MASTER, IDLE, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, RED_COMMANDER } from '../constants/constants'
|
||||
import { getApp, setupApp } from '../olympusapp'
|
||||
import { LoginModal } from './modals/login'
|
||||
import { sha256 } from 'js-sha256'
|
||||
import { MiniMapPanel } from './panels/minimappanel'
|
||||
import { UnitMouseControlBar } from './panels/unitmousecontrolbar'
|
||||
import { Header } from "./panels/header";
|
||||
import { SpawnMenu } from "./panels/spawnmenu";
|
||||
import { UnitControlMenu } from "./panels/unitcontrolmenu";
|
||||
import { MainMenu } from "./panels/mainmenu";
|
||||
import { SideBar } from "./panels/sidebar";
|
||||
import { Options } from "./panels/options";
|
||||
import { MapHiddenTypes, MapOptions } from "../types/types";
|
||||
import {
|
||||
BLUE_COMMANDER,
|
||||
GAME_MASTER,
|
||||
IDLE,
|
||||
MAP_HIDDEN_TYPES_DEFAULTS,
|
||||
MAP_OPTIONS_DEFAULTS,
|
||||
RED_COMMANDER,
|
||||
} from "../constants/constants";
|
||||
import { getApp, setupApp } from "../olympusapp";
|
||||
import { LoginModal } from "./modals/login";
|
||||
import { sha256 } from "js-sha256";
|
||||
import { MiniMapPanel } from "./panels/minimappanel";
|
||||
import { UnitMouseControlBar } from "./panels/unitmousecontrolbar";
|
||||
|
||||
export type OlympusState = {
|
||||
mainMenuVisible: boolean,
|
||||
spawnMenuVisible: boolean,
|
||||
unitControlMenuVisible: boolean,
|
||||
measureMenuVisible: boolean,
|
||||
drawingMenuVisible: boolean,
|
||||
optionsMenuVisible: boolean,
|
||||
mapHiddenTypes: MapHiddenTypes;
|
||||
mapOptions: MapOptions;
|
||||
}
|
||||
mainMenuVisible: boolean;
|
||||
spawnMenuVisible: boolean;
|
||||
unitControlMenuVisible: boolean;
|
||||
measureMenuVisible: boolean;
|
||||
drawingMenuVisible: boolean;
|
||||
optionsMenuVisible: boolean;
|
||||
mapHiddenTypes: MapHiddenTypes;
|
||||
mapOptions: MapOptions;
|
||||
};
|
||||
|
||||
export function UI() {
|
||||
var [loginModalVisible, setLoginModalVisible] = useState(true);
|
||||
var [mainMenuVisible, setMainMenuVisible] = useState(false);
|
||||
var [spawnMenuVisible, setSpawnMenuVisible] = useState(false);
|
||||
var [unitControlMenuVisible, setUnitControlMenuVisible] = useState(false);
|
||||
var [measureMenuVisible, setMeasureMenuVisible] = useState(false);
|
||||
var [drawingMenuVisible, setDrawingMenuVisible] = useState(false);
|
||||
var [optionsMenuVisible, setOptionsMenuVisible] = useState(false);
|
||||
var [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
var [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
var [checkingPassword, setCheckingPassword] = useState(false);
|
||||
var [loginError, setLoginError] = useState(false);
|
||||
var [commandMode, setCommandMode] = useState(null as null | string);
|
||||
var [mapSources, setMapSources] = useState([] as string[]);
|
||||
var [activeMapSource, setActiveMapSource] = useState("");
|
||||
var [loginModalVisible, setLoginModalVisible] = useState(true);
|
||||
var [mainMenuVisible, setMainMenuVisible] = useState(false);
|
||||
var [spawnMenuVisible, setSpawnMenuVisible] = useState(false);
|
||||
var [unitControlMenuVisible, setUnitControlMenuVisible] = useState(false);
|
||||
var [measureMenuVisible, setMeasureMenuVisible] = useState(false);
|
||||
var [drawingMenuVisible, setDrawingMenuVisible] = useState(false);
|
||||
var [optionsMenuVisible, setOptionsMenuVisible] = useState(false);
|
||||
var [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
|
||||
var [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
|
||||
var [checkingPassword, setCheckingPassword] = useState(false);
|
||||
var [loginError, setLoginError] = useState(false);
|
||||
var [commandMode, setCommandMode] = useState(null as null | string);
|
||||
var [mapSources, setMapSources] = useState([] as string[]);
|
||||
var [activeMapSource, setActiveMapSource] = useState("");
|
||||
|
||||
document.addEventListener("hiddenTypesChanged", (ev) => {
|
||||
setMapHiddenTypes({ ...getApp().getMap().getHiddenTypes() });
|
||||
})
|
||||
document.addEventListener("hiddenTypesChanged", (ev) => {
|
||||
setMapHiddenTypes({ ...getApp().getMap().getHiddenTypes() });
|
||||
});
|
||||
|
||||
document.addEventListener("mapOptionsChanged", (ev) => {
|
||||
setMapOptions({ ...getApp().getMap().getOptions() });
|
||||
})
|
||||
document.addEventListener("mapOptionsChanged", (ev) => {
|
||||
setMapOptions({ ...getApp().getMap().getOptions() });
|
||||
});
|
||||
|
||||
document.addEventListener("mapStateChanged", (ev) => {
|
||||
if ((ev as CustomEvent).detail == IDLE) {
|
||||
hideAllMenus();
|
||||
}
|
||||
})
|
||||
document.addEventListener("mapStateChanged", (ev) => {
|
||||
if ((ev as CustomEvent).detail == IDLE) {
|
||||
hideAllMenus();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mapSourceChanged", (ev) => {
|
||||
var source = (ev as CustomEvent).detail;
|
||||
if (source !== activeMapSource)
|
||||
setActiveMapSource(source);
|
||||
})
|
||||
document.addEventListener("mapSourceChanged", (ev) => {
|
||||
var source = (ev as CustomEvent).detail;
|
||||
if (source !== activeMapSource) setActiveMapSource(source);
|
||||
});
|
||||
|
||||
document.addEventListener("configLoaded", (ev) => {
|
||||
let config = getApp().getConfig();
|
||||
var sources = Object.keys(config.mapMirrors).concat(
|
||||
Object.keys(config.mapLayers)
|
||||
);
|
||||
setMapSources(sources);
|
||||
setActiveMapSource(sources[0]);
|
||||
});
|
||||
|
||||
document.addEventListener("configLoaded", (ev) => {
|
||||
let config = getApp().getConfig();
|
||||
var sources = Object.keys(config.mapMirrors).concat(Object.keys(config.mapLayers));
|
||||
setMapSources(sources);
|
||||
setActiveMapSource(sources[0]);
|
||||
})
|
||||
function hideAllMenus() {
|
||||
setMainMenuVisible(false);
|
||||
setSpawnMenuVisible(false);
|
||||
setUnitControlMenuVisible(false);
|
||||
setMeasureMenuVisible(false);
|
||||
setDrawingMenuVisible(false);
|
||||
setOptionsMenuVisible(false);
|
||||
}
|
||||
|
||||
function hideAllMenus() {
|
||||
setMainMenuVisible(false);
|
||||
setSpawnMenuVisible(false);
|
||||
setUnitControlMenuVisible(false);
|
||||
setMeasureMenuVisible(false);
|
||||
setDrawingMenuVisible(false);
|
||||
setOptionsMenuVisible(false);
|
||||
}
|
||||
function checkPassword(password: string) {
|
||||
setCheckingPassword(true);
|
||||
var hash = sha256.create();
|
||||
getApp().getServerManager().setPassword(hash.update(password).hex());
|
||||
getApp()
|
||||
.getServerManager()
|
||||
.getMission(
|
||||
(response) => {
|
||||
const commandMode = response.mission.commandModeOptions.commandMode;
|
||||
try {
|
||||
[GAME_MASTER, BLUE_COMMANDER, RED_COMMANDER].includes(commandMode)
|
||||
? setCommandMode(commandMode)
|
||||
: setLoginError(true);
|
||||
} catch {
|
||||
setLoginError(true);
|
||||
}
|
||||
setCheckingPassword(false);
|
||||
},
|
||||
() => {
|
||||
setLoginError(true);
|
||||
setCheckingPassword(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function checkPassword(password: string) {
|
||||
setCheckingPassword(true);
|
||||
var hash = sha256.create();
|
||||
getApp().getServerManager().setPassword(hash.update(password).hex());
|
||||
getApp().getServerManager().getMission((response) => {
|
||||
const commandMode = response.mission.commandModeOptions.commandMode;
|
||||
try {
|
||||
[GAME_MASTER, BLUE_COMMANDER, RED_COMMANDER].includes(commandMode) ? setCommandMode(commandMode) : setLoginError(true);
|
||||
} catch {
|
||||
setLoginError(true);
|
||||
}
|
||||
setCheckingPassword(false);
|
||||
},
|
||||
() => {
|
||||
setLoginError(true);
|
||||
setCheckingPassword(false);
|
||||
},
|
||||
)
|
||||
}
|
||||
function connect(username: string) {
|
||||
getApp().getServerManager().setUsername(username);
|
||||
getApp().getServerManager().startUpdate();
|
||||
setLoginModalVisible(false);
|
||||
}
|
||||
|
||||
function connect(username: string) {
|
||||
getApp().getServerManager().setUsername(username);
|
||||
getApp().getServerManager().startUpdate();
|
||||
setLoginModalVisible(false);
|
||||
}
|
||||
/* Temporary during devel */
|
||||
//useEffect(() => {
|
||||
// window.setTimeout(() => {
|
||||
// checkPassword("admin");
|
||||
// connect("devel");
|
||||
// }, 1000)
|
||||
//}, [])
|
||||
|
||||
/* Temporary during devel */
|
||||
//useEffect(() => {
|
||||
// window.setTimeout(() => {
|
||||
// checkPassword("admin");
|
||||
// connect("devel");
|
||||
// }, 1000)
|
||||
//}, [])
|
||||
|
||||
|
||||
return (
|
||||
<div className="absolute top-0 left-0 h-screen w-screen font-sans overflow-hidden" onLoad={setupApp}>
|
||||
<StateProvider value={{
|
||||
mainMenuVisible: mainMenuVisible,
|
||||
spawnMenuVisible: spawnMenuVisible,
|
||||
unitControlMenuVisible: unitControlMenuVisible,
|
||||
measureMenuVisible: measureMenuVisible,
|
||||
drawingMenuVisible: drawingMenuVisible,
|
||||
optionsMenuVisible: optionsMenuVisible,
|
||||
mapOptions: mapOptions,
|
||||
mapHiddenTypes: mapHiddenTypes,
|
||||
mapSources: mapSources,
|
||||
activeMapSource: activeMapSource
|
||||
}}>
|
||||
<EventsProvider value={
|
||||
{
|
||||
setMainMenuVisible: setMainMenuVisible,
|
||||
setSpawnMenuVisible: setSpawnMenuVisible,
|
||||
setUnitControlMenuVisible: setUnitControlMenuVisible,
|
||||
setDrawingMenuVisible: setDrawingMenuVisible,
|
||||
setMeasureMenuVisible: setMeasureMenuVisible,
|
||||
setOptionsMenuVisible: setOptionsMenuVisible,
|
||||
toggleMainMenuVisible: () => { hideAllMenus(); setMainMenuVisible(!mainMenuVisible) },
|
||||
toggleSpawnMenuVisible: () => { hideAllMenus(); setSpawnMenuVisible(!spawnMenuVisible) },
|
||||
toggleUnitControlMenuVisible: () => { hideAllMenus(); setUnitControlMenuVisible(!unitControlMenuVisible) },
|
||||
toggleMeasureMenuVisible: () => { hideAllMenus(); setMeasureMenuVisible(!measureMenuVisible) },
|
||||
toggleDrawingMenuVisible: () => { hideAllMenus(); setDrawingMenuVisible(!drawingMenuVisible) },
|
||||
toggleOptionsMenuVisible: () => { hideAllMenus(); setOptionsMenuVisible(!optionsMenuVisible) },
|
||||
}
|
||||
}>
|
||||
<div className='absolute top-0 left-0 h-full w-full flex flex-col'>
|
||||
<Header />
|
||||
<div className='flex h-full'>
|
||||
{loginModalVisible &&
|
||||
<>
|
||||
<div className="fixed top-0 left-0 w-full h-full z-ui-3 bg-[#111111]/95 "></div>
|
||||
<LoginModal
|
||||
onLogin={(password) => { checkPassword(password) }}
|
||||
onContinue={(username) => { connect(username) }}
|
||||
onBack={() => { setCommandMode(null) }}
|
||||
checkingPassword={checkingPassword}
|
||||
loginError={loginError}
|
||||
commandMode={commandMode}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
<SideBar />
|
||||
<MainMenu
|
||||
open={mainMenuVisible}
|
||||
onClose={() => setMainMenuVisible(false)}
|
||||
/>
|
||||
<SpawnMenu
|
||||
open={spawnMenuVisible}
|
||||
onClose={() => setSpawnMenuVisible(false)}
|
||||
/>
|
||||
<Options
|
||||
open={optionsMenuVisible}
|
||||
onClose={() => setOptionsMenuVisible(false)}
|
||||
options={mapOptions}
|
||||
/>
|
||||
<MiniMapPanel />
|
||||
<UnitControlMenu />
|
||||
<div id='map-container' className='h-full w-screen' />
|
||||
<UnitMouseControlBar />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</EventsProvider>
|
||||
</StateProvider>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
absolute left-0 top-0 h-screen w-screen overflow-hidden font-sans
|
||||
`}
|
||||
onLoad={setupApp}
|
||||
>
|
||||
<StateProvider
|
||||
value={{
|
||||
mainMenuVisible: mainMenuVisible,
|
||||
spawnMenuVisible: spawnMenuVisible,
|
||||
unitControlMenuVisible: unitControlMenuVisible,
|
||||
measureMenuVisible: measureMenuVisible,
|
||||
drawingMenuVisible: drawingMenuVisible,
|
||||
optionsMenuVisible: optionsMenuVisible,
|
||||
mapOptions: mapOptions,
|
||||
mapHiddenTypes: mapHiddenTypes,
|
||||
mapSources: mapSources,
|
||||
activeMapSource: activeMapSource,
|
||||
}}
|
||||
>
|
||||
<EventsProvider
|
||||
value={{
|
||||
setMainMenuVisible: setMainMenuVisible,
|
||||
setSpawnMenuVisible: setSpawnMenuVisible,
|
||||
setUnitControlMenuVisible: setUnitControlMenuVisible,
|
||||
setDrawingMenuVisible: setDrawingMenuVisible,
|
||||
setMeasureMenuVisible: setMeasureMenuVisible,
|
||||
setOptionsMenuVisible: setOptionsMenuVisible,
|
||||
toggleMainMenuVisible: () => {
|
||||
hideAllMenus();
|
||||
setMainMenuVisible(!mainMenuVisible);
|
||||
},
|
||||
toggleSpawnMenuVisible: () => {
|
||||
hideAllMenus();
|
||||
setSpawnMenuVisible(!spawnMenuVisible);
|
||||
},
|
||||
toggleUnitControlMenuVisible: () => {
|
||||
hideAllMenus();
|
||||
setUnitControlMenuVisible(!unitControlMenuVisible);
|
||||
},
|
||||
toggleMeasureMenuVisible: () => {
|
||||
hideAllMenus();
|
||||
setMeasureMenuVisible(!measureMenuVisible);
|
||||
},
|
||||
toggleDrawingMenuVisible: () => {
|
||||
hideAllMenus();
|
||||
setDrawingMenuVisible(!drawingMenuVisible);
|
||||
},
|
||||
toggleOptionsMenuVisible: () => {
|
||||
hideAllMenus();
|
||||
setOptionsMenuVisible(!optionsMenuVisible);
|
||||
},
|
||||
}}
|
||||
>
|
||||
<div className="absolute left-0 top-0 flex h-full w-full flex-col">
|
||||
<Header />
|
||||
<div className="flex h-full">
|
||||
{loginModalVisible && (
|
||||
<>
|
||||
<div className={`
|
||||
fixed left-0 top-0 h-full w-full z-ui-3 bg-[#111111]/95
|
||||
`}></div>
|
||||
<LoginModal
|
||||
onLogin={(password) => {
|
||||
checkPassword(password);
|
||||
}}
|
||||
onContinue={(username) => {
|
||||
connect(username);
|
||||
}}
|
||||
onBack={() => {
|
||||
setCommandMode(null);
|
||||
}}
|
||||
checkingPassword={checkingPassword}
|
||||
loginError={loginError}
|
||||
commandMode={commandMode}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<SideBar />
|
||||
<MainMenu
|
||||
open={mainMenuVisible}
|
||||
onClose={() => setMainMenuVisible(false)}
|
||||
/>
|
||||
<SpawnMenu
|
||||
open={spawnMenuVisible}
|
||||
onClose={() => setSpawnMenuVisible(false)}
|
||||
/>
|
||||
<Options
|
||||
open={optionsMenuVisible}
|
||||
onClose={() => setOptionsMenuVisible(false)}
|
||||
options={mapOptions}
|
||||
/>
|
||||
<MiniMapPanel />
|
||||
<UnitControlMenu />
|
||||
<div id="map-container" className="h-full w-screen" />
|
||||
<UnitMouseControlBar />
|
||||
</div>
|
||||
</div>
|
||||
</EventsProvider>
|
||||
</StateProvider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user