Reformatted project using prettier plugin

This commit is contained in:
Davide Passoni
2024-07-01 17:43:46 +02:00
parent 1acb7d6762
commit 00e2da2aab
524 changed files with 36320 additions and 24305 deletions

View File

@@ -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>
);
}

View File

@@ -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>
}
);
}

View File

@@ -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>
);
}

View File

@@ -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
`}
/>
}
);
}

View File

@@ -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>
);
}

View File

@@ -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

View File

@@ -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>
}
);
}

View File

@@ -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>
}
);
}

View File

@@ -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
`}
/>
);
}

View File

@@ -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>
}
);
}

View File

@@ -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} />}
</>
}
);
}

View File

@@ -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>
);
}

View File

@@ -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>
)
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
}
);
}

View File

@@ -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>
}
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
}
);
}

View File

@@ -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>
}
);
}

View File

@@ -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>
}
);
}

View File

@@ -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>
);
}

View File

@@ -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

View File

@@ -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>
)}
</>
)}
</>
);
}

View File

@@ -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>
);
}

View File

@@ -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;
}

View File

@@ -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>
);
}