mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
More work on flowbite components
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.5.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@tanem/svg-injector": "^10.1.68",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 16 KiB |
@@ -1,10 +1,12 @@
|
||||
import { createContext } from "react";
|
||||
|
||||
export const EventsContext = createContext({
|
||||
setMainMenuVisible: (e: boolean) => {},
|
||||
setSpawnMenuVisible: (e: boolean) => {},
|
||||
setUnitControlMenuVisible: (e: boolean) => {},
|
||||
setMeasureMenuVisible: (e: boolean) => {},
|
||||
setDrawingMenuVisible: (e: boolean) => {},
|
||||
toggleMainMenuVisible: () => {},
|
||||
toggleSpawnMenuVisible: () => {},
|
||||
toggleUnitControlMenuVisible: () => {},
|
||||
toggleMeasureMenuVisible: () => {},
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/***************** UI *******************/
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import './index.css'
|
||||
import { setupApp } from './olympusapp.js'
|
||||
import 'flowbite';
|
||||
import { UI } from './ui.js';
|
||||
|
||||
import './index.css'
|
||||
|
||||
import 'flowbite';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<UI />
|
||||
|
||||
@@ -38,7 +38,7 @@ import { navyUnitDatabase } from "./unit/databases/navyunitdatabase";
|
||||
//import { UnitListPanel } from "./panels/unitlistpanel";
|
||||
//import { ContextManager } from "./context/contextmanager";
|
||||
//import { Context } from "./context/context";
|
||||
var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
|
||||
export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
|
||||
|
||||
export class OlympusApp {
|
||||
/* Global data */
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
@@ -6,21 +6,25 @@ import { EventsProvider } from './eventscontext'
|
||||
import { StateProvider } from './statecontext'
|
||||
import { SpawnMenu } from './ui/panels/spawnmenu'
|
||||
import { UnitControlMenu } from './ui/panels/unitcontrolmenu'
|
||||
import { MainMenu } from './ui/panels/mainmenu'
|
||||
|
||||
export type OlympusState = {
|
||||
mainMenuVisible: boolean,
|
||||
spawnMenuVisible: boolean,
|
||||
unitControlMenuVisible: boolean,
|
||||
measureMenuVisible: boolean,
|
||||
drawingMenuVisible: boolean
|
||||
}
|
||||
1
|
||||
|
||||
export function UI(props) {
|
||||
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);
|
||||
|
||||
function hideAllMenus() {
|
||||
setMainMenuVisible(false);
|
||||
setSpawnMenuVisible(false);
|
||||
setUnitControlMenuVisible(false);
|
||||
setMeasureMenuVisible(false);
|
||||
@@ -30,6 +34,7 @@ export function UI(props) {
|
||||
return (
|
||||
<div className="absolute top-0 left-0 h-screen w-screen font-sans overflow-hidden">
|
||||
<StateProvider value={{
|
||||
mainMenuVisible: mainMenuVisible,
|
||||
spawnMenuVisible: spawnMenuVisible,
|
||||
unitControlMenuVisible: unitControlMenuVisible,
|
||||
measureMenuVisible: measureMenuVisible,
|
||||
@@ -37,10 +42,12 @@ export function UI(props) {
|
||||
}}>
|
||||
<EventsProvider value={
|
||||
{
|
||||
setMainMenuVisible: setMainMenuVisible,
|
||||
setSpawnMenuVisible: setSpawnMenuVisible,
|
||||
setUnitControlMenuVisible: setUnitControlMenuVisible,
|
||||
setDrawingMenuVisible: setDrawingMenuVisible,
|
||||
setMeasureMenuVisible: setMeasureMenuVisible,
|
||||
toggleMainMenuVisible: () => {hideAllMenus(); setMainMenuVisible(!mainMenuVisible)},
|
||||
toggleSpawnMenuVisible: () => {hideAllMenus(); setSpawnMenuVisible(!spawnMenuVisible)},
|
||||
toggleUnitControlMenuVisible: () => {hideAllMenus(); setUnitControlMenuVisible(!unitControlMenuVisible)},
|
||||
toggleMeasureMenuVisible: () => {hideAllMenus(); setMeasureMenuVisible(!measureMenuVisible)},
|
||||
@@ -50,6 +57,7 @@ export function UI(props) {
|
||||
<div className='absolute top-0 left-0 h-full w-full flex flex-col'>
|
||||
<Header />
|
||||
<div id='map-container' className='relative h-screen w-screen top-0 left-0' />
|
||||
<MainMenu open={mainMenuVisible} closeCallback={() => setMainMenuVisible(false)}/>
|
||||
<SpawnMenu open={spawnMenuVisible} closeCallback={() => setSpawnMenuVisible(false)}/>
|
||||
<UnitControlMenu open={unitControlMenuVisible} closeCallback={() => setUnitControlMenuVisible(false)}/>
|
||||
</div>
|
||||
|
||||
43
frontend/react/src/ui/components/olaccordion.tsx
Normal file
43
frontend/react/src/ui/components/olaccordion.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { useId, useEffect, useRef, useState } from "react"
|
||||
|
||||
import 'flowbite';
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowCircleDown, faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export function OlAccordion(props) {
|
||||
var [scrolledUp, setScrolledUp] = useState(true);
|
||||
var [scrolledDown, setScrolledDown] = useState(false);
|
||||
|
||||
const bodyId = useId();
|
||||
const accordionId = useId();
|
||||
const headingId = useId();
|
||||
|
||||
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);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return <div id={accordionId} data-accordion="collapse" data-active-classes="bg-white dark:bg-transparent text-gray-900 dark:text-white" data-inactive-classes="text-gray-500 dark:text-gray-400">
|
||||
<h3 id={headingId}>
|
||||
<button type="button" className="flex items-center justify-between w-full py-2 font-medium rtl:text-right text-gray-500 border-gray-200 dark:border-gray-700 dark:text-gray-300 gap-3" data-accordion-target={"#" + CSS.escape(bodyId)} aria-expanded="false" aria-controls={bodyId}>
|
||||
<span>{props.title}</span>
|
||||
<svg data-accordion-icon className="w-3 h-3 rotate-180 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" strokeWidth="2" d="M9 5 5 1 1 5" />
|
||||
</svg>
|
||||
</button>
|
||||
</h3>
|
||||
<div id={bodyId} className="hidden relative" aria-labelledby={headingId}>
|
||||
<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>
|
||||
<div>{!scrolledDown && <FontAwesomeIcon icon={faArrowCircleDown} className="text-white animate-bounce opacity-20 absolute w-full"/>}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
23
frontend/react/src/ui/components/olsearchbar.tsx
Normal file
23
frontend/react/src/ui/components/olsearchbar.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { faMultiply, faSearch } from "@fortawesome/free-solid-svg-icons"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import React, {useId, useRef} from "react"
|
||||
|
||||
export function OlSearchBar(props) {
|
||||
const searchId = useId();
|
||||
const inputRef = useRef(null);
|
||||
|
||||
function resetSearch() {
|
||||
inputRef.current && ((inputRef.current as HTMLInputElement).value = '');
|
||||
}
|
||||
|
||||
return <div {...props}>
|
||||
<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-3 pointer-events-none">
|
||||
<FontAwesomeIcon icon={faSearch} className="dark:text-gray-400" />
|
||||
</div>
|
||||
<input type="search" ref={inputRef} id={searchId} className="block w-full p-3 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg 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}/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -2,7 +2,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import React from "react"
|
||||
|
||||
export function OlStateButton(props) {
|
||||
return <button {...props} type="button" className="h-[40px] w-[40px] m-auto text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700">
|
||||
return <button {...props} type="button" className={`h-[40px] w-[40px] m-auto text-gray-900 bg-white border border-gray-300 focus:outline-none
|
||||
hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm
|
||||
dark:bg-${props.checked? 'white': 'transparent'} dark:text-${props.checked? 'gray-900': 'white'} dark:border-gray-600
|
||||
dark:hover:bg-${props.checked? 'white': 'gray-700'} dark:hover:border-gray-600 dark:focus:ring-gray-700`}>
|
||||
<FontAwesomeIcon icon={props.icon} />
|
||||
</button>
|
||||
}
|
||||
@@ -11,16 +11,17 @@ export function Menu(props) {
|
||||
props.open ? drawer.show() : drawer.hide();
|
||||
})
|
||||
|
||||
return <div ref={ref} className="w-[430px] absolute top-16 left-0 z-ui h-screen p-4 overflow-y-auto transition-transform -translate-x-full bg-white dark:bg-gray-800" tabIndex={-1} aria-labelledby={labelId}>
|
||||
<h5 id={labelId} className="inline-flex items-center mb-4 text-base font-semibold text-gray-500 dark:text-gray-400">
|
||||
<FontAwesomeIcon icon={props.titleIcon} className="mr-2"/>
|
||||
{props.title}
|
||||
</h5>
|
||||
return <div ref={ref} className="no-scrollbar w-[430px] absolute top-[80px] left-0 z-ui h-screen p-4 overflow-y-auto transition-transform -translate-x-full bg-white dark:bg-gray-800" tabIndex={-1} aria-labelledby={labelId}>
|
||||
<h5 id={labelId} className="w-full inline-flex items-center pb-3 mb-4 border-b-2 dark:border-gray-700 text-base font-semibold text-gray-500 dark:text-gray-400">
|
||||
{props.title}
|
||||
</h5>
|
||||
<button type="button" onClick={props.closeCallback} className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 absolute top-2.5 end-2.5 flex items-center justify-center dark:hover:bg-gray-600 dark:hover:text-white" >
|
||||
<svg className="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" strokeWidth={2} d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||
</svg>
|
||||
<span className="sr-only">Close menu</span>
|
||||
</button>
|
||||
{props.children}
|
||||
|
||||
</div>
|
||||
}
|
||||
@@ -13,9 +13,10 @@ export function Header(props) {
|
||||
{(appState) =>
|
||||
<EventsConsumer>
|
||||
{(events) =>
|
||||
<nav className="bg-white border-gray-200 dark:bg-gray-800 dark:border-gray-700">
|
||||
<nav className="bg-white border-gray-200 dark:bg-[#171C26] dark:border-gray-700">
|
||||
<div className="max-w-screen flex flex-wrap items-center justify-between p-4">
|
||||
<div className="flex flex-row items-center justify-center gap-1">
|
||||
<img src="images/icon.png" className='h-12 border-2 p-0 bg-white rounded-md mr-2 cursor-pointer' onClick={events.toggleMainMenuVisible}></img>
|
||||
<OlStateButton onClick={events.toggleSpawnMenuVisible} checked={appState.spawnMenuVisible} icon="fa-solid fa-plus"></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleUnitControlMenuVisible} checked={appState.unitControlMenuVisible} icon="fa-solid fa-gamepad"></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleMeasureMenuVisible} checked={appState.measureMenuVisible} icon="fa-solid fa-ruler"></OlStateButton>
|
||||
|
||||
19
frontend/react/src/ui/panels/mainmenu.tsx
Normal file
19
frontend/react/src/ui/panels/mainmenu.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { faCheckCircle, faDatabase, faFileAlt, faFileExport, faFileImport } 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) {
|
||||
return <Menu {...props} title="DCS Olympus">
|
||||
<div className="flex flex-col gap-2 text-md font-normal font text-white">
|
||||
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center text-[#8BFF63]"><FontAwesomeIcon icon={faCheckCircle} className="my-auto" />Version {VERSION}</div>
|
||||
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faGithub} className="my-auto text-gray-400" />Overview</div>
|
||||
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faFileAlt} className="my-auto text-gray-400" />User guide</div>
|
||||
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faDatabase} className="my-auto text-gray-400" />Database Manager</div>
|
||||
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faFileExport} className="my-auto text-gray-400" />Export to file</div>
|
||||
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faFileImport} className="my-auto text-gray-400" />Import from file</div>
|
||||
</div>
|
||||
</Menu>
|
||||
}
|
||||
@@ -1,13 +1,25 @@
|
||||
import React from "react";
|
||||
import { Menu } from "./components/menu";
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faJetFighter, 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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
library.add(faPlus);
|
||||
|
||||
export function SpawnMenu(props) {
|
||||
|
||||
return <Menu {...props} title="Spawn menu" titleIcon="fa-solid fa-plus">
|
||||
|
||||
<OlSearchBar className="mb-4" />
|
||||
<OlAccordion title="Aircraft">
|
||||
<div className="flex flex-col gap-2 no-scrollbar max-h-80 overflow-scroll">
|
||||
{getApp() && Object.keys(getApp().getAircraftDatabase().blueprints).map((key) => {
|
||||
return <div className="text-sm text-gray-300 font-thin"><FontAwesomeIcon icon={faJetFighter} className="text-sm mr-2" /> {getApp().getAircraftDatabase().blueprints[key].label}</div>;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title="Helicopter"></OlAccordion>
|
||||
<OlAccordion title="Air Defense"></OlAccordion>
|
||||
</Menu>
|
||||
}
|
||||
Reference in New Issue
Block a user