mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Implemented session data for audio
This commit is contained in:
@@ -10,10 +10,10 @@ export function OlFrequencyInput(props: { value: number; className?: string; onC
|
||||
flex gap-2
|
||||
`}>
|
||||
<OlNumberInput
|
||||
min={1}
|
||||
min={0}
|
||||
max={400}
|
||||
onChange={(e) => {
|
||||
let newValue = Math.max(Math.min(Number(e.target.value), 400), 1) * 1000000;
|
||||
let newValue = Math.max(Math.min(Number(e.target.value), 400), 0) * 1000000;
|
||||
let decimalPart = frequency - Math.floor(frequency / 1000000) * 1000000;
|
||||
frequency = newValue + decimalPart;
|
||||
props.onChange(frequency);
|
||||
|
||||
@@ -3,7 +3,10 @@ import React from "react";
|
||||
export function OlLabelToggle(props: { toggled: boolean | undefined; leftLabel: string; rightLabel: string; onClick: () => void }) {
|
||||
return (
|
||||
<button
|
||||
onClick={props.onClick}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
props.onClick();
|
||||
}}
|
||||
className={`
|
||||
relative flex h-10 w-[120px] flex-none cursor-pointer select-none
|
||||
flex-row content-center justify-between rounded-md border px-1 py-[5px]
|
||||
|
||||
@@ -21,7 +21,10 @@ export function OlNumberInput(props: {
|
||||
<div className="relative flex max-w-[8rem] items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onDecrease}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
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
|
||||
@@ -45,6 +48,7 @@ export function OlNumberInput(props: {
|
||||
<input
|
||||
type="text"
|
||||
onChange={props.onChange}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
min={props.min}
|
||||
max={props.max}
|
||||
className={`
|
||||
@@ -59,7 +63,10 @@ export function OlNumberInput(props: {
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onIncrease}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
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
|
||||
|
||||
@@ -30,13 +30,14 @@ export function OlStateButton(props: {
|
||||
textColor = "#243141";
|
||||
}
|
||||
|
||||
const opacity = (hover && !props.checked) ? "AA" : "FF";
|
||||
const opacity = hover && !props.checked ? "AA" : "FF";
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
ref={buttonRef}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
props.onClick();
|
||||
props.onClick ?? setHover(false);
|
||||
}}
|
||||
|
||||
@@ -2,10 +2,16 @@ import React from "react";
|
||||
|
||||
export function OlToggle(props: { toggled: boolean | undefined; onClick: () => void }) {
|
||||
return (
|
||||
<div className="inline-flex cursor-pointer items-center" onClick={props.onClick}>
|
||||
<div
|
||||
className="inline-flex cursor-pointer items-center"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
props.onClick();
|
||||
}}
|
||||
>
|
||||
<button className="peer sr-only" />
|
||||
<div
|
||||
data-toggled={props.toggled === true? 'true': props.toggled === undefined? 'undefined': 'false'}
|
||||
data-toggled={props.toggled === true ? "true" : props.toggled === undefined ? "undefined" : "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
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal } from "./components/modal";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowRight, faCheck, faUpload } from "@fortawesome/free-solid-svg-icons";
|
||||
import { getApp } from "../../olympusapp";
|
||||
import { OlympusState } from "../../constants/constants";
|
||||
import { SessionDataLoadedEvent } from "../../events";
|
||||
|
||||
export function FileSourceLoadPrompt(props: { open: boolean }) {
|
||||
const [files, setFiles] = useState([] as { filename: string; volume: number }[]);
|
||||
const [loaded, setLoaded] = useState([] as boolean[]);
|
||||
|
||||
useEffect(() => {
|
||||
SessionDataLoadedEvent.on((sessionData) => {
|
||||
if (getApp().getState() === OlympusState.LOAD_FILES) return; // TODO don't like this, is hacky Should avoid reading state directly
|
||||
if (sessionData.fileSources) {
|
||||
setFiles([...sessionData.fileSources]);
|
||||
setLoaded(
|
||||
sessionData.fileSources.map((file) => {
|
||||
return false;
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={props.open}
|
||||
className={`
|
||||
inline-flex h-fit max-h-[800px] w-[600px] overflow-y-auto scroll-smooth
|
||||
bg-white p-10
|
||||
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
|
||||
`}
|
||||
>
|
||||
<div className="flex h-full w-full flex-col gap-12">
|
||||
<div className={`flex flex-col items-start gap-2`}>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-white
|
||||
`}
|
||||
>
|
||||
Please, select the files for the following audio sources
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
Browsers can't automatically load files from your computer, therefore you must click on the following buttons to select the original files for each
|
||||
audio file source.
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
text-gray-800 text-md
|
||||
dark:text-gray-500
|
||||
`}
|
||||
>
|
||||
If you don't want to reload your audio sources, press "Skip".
|
||||
</span>
|
||||
<div className="mt-4 w-full">
|
||||
{files.map((fileData, idx) => {
|
||||
return (
|
||||
<div
|
||||
className={`flex w-full content-center justify-between gap-4`}
|
||||
>
|
||||
<span className={`my-auto truncate text-white`}>{fileData.filename}</span>
|
||||
<button
|
||||
type="button"
|
||||
disabled={loaded[idx] === true || (idx > 0 && loaded[idx - 1] == false)}
|
||||
data-disabled={loaded[idx] === true || (idx > 0 && loaded[idx - 1] == false)}
|
||||
onClick={() => {
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.click();
|
||||
input.onchange = (e: Event) => {
|
||||
let target = e.target as HTMLInputElement;
|
||||
if (target && target.files) {
|
||||
var file = target.files[0];
|
||||
getApp().getAudioManager().addFileSource(file).setVolume(fileData.volume);
|
||||
loaded[idx] = true;
|
||||
setLoaded([...loaded]);
|
||||
if (idx === loaded.length - 1) getApp().setState(OlympusState.IDLE);
|
||||
}
|
||||
};
|
||||
}}
|
||||
className={`
|
||||
mb-2 me-2 ml-auto flex cursor-pointer content-center
|
||||
items-center gap-2 rounded-sm bg-blue-600 px-5 py-2.5
|
||||
text-sm font-medium text-white
|
||||
data-[disabled="true"]:bg-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-800
|
||||
hover:bg-blue-700
|
||||
`}
|
||||
>
|
||||
{loaded[idx] ? "Loaded" : "Load"}
|
||||
<FontAwesomeIcon className={`my-auto`} icon={loaded[idx] ? faCheck : faUpload} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {getApp().setState(OlympusState.IDLE)}}
|
||||
className={`
|
||||
mb-2 me-2 ml-auto 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
|
||||
`}
|
||||
>
|
||||
Skip
|
||||
<FontAwesomeIcon className={`my-auto`} icon={faArrowRight} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -202,18 +202,7 @@ export function AudioMenu(props: { open: boolean; onClose: () => void; children?
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-300
|
||||
hover:bg-blue-800
|
||||
`}
|
||||
onClick={() => {
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.click();
|
||||
input.onchange = (e: Event) => {
|
||||
let target = e.target as HTMLInputElement;
|
||||
if (target && target.files) {
|
||||
var file = target.files[0];
|
||||
getApp().getAudioManager().addFileSource(file);
|
||||
}
|
||||
};
|
||||
}}
|
||||
onClick={() => getApp().getAudioManager().addFileSource()}
|
||||
>
|
||||
Add audio source
|
||||
</button>
|
||||
|
||||
@@ -18,11 +18,14 @@ export const RadioSinkPanel = forwardRef((props: { radio: RadioSink; shortcutKey
|
||||
<div
|
||||
data-receiving={props.radio.getReceiving()}
|
||||
className={`
|
||||
box-border flex flex-col content-center justify-between gap-2 rounded-md
|
||||
border-2 border-transparent bg-olympus-200/30 px-4 py-3
|
||||
box-border flex cursor-pointer flex-col content-center justify-between
|
||||
gap-2 rounded-md border-2 border-transparent bg-olympus-200/30 px-4 py-3
|
||||
data-[receiving='true']:border-white
|
||||
`}
|
||||
ref={ref}
|
||||
onClick={() => {
|
||||
setExpanded(!expanded);
|
||||
}}
|
||||
>
|
||||
<div className="flex content-center justify-between gap-2">
|
||||
<div
|
||||
|
||||
@@ -8,6 +8,7 @@ import { OlRangeSlider } from "../../components/olrangeslider";
|
||||
import { FileSource } from "../../../audio/filesource";
|
||||
import { MicrophoneSource } from "../../../audio/microphonesource";
|
||||
import { TextToSpeechSource } from "../../../audio/texttospeechsource";
|
||||
import { FaUpload } from "react-icons/fa";
|
||||
|
||||
export const AudioSourcePanel = forwardRef((props: { source: AudioSource; onExpanded: () => void }, ref: ForwardedRef<HTMLDivElement>) => {
|
||||
const [meterLevel, setMeterLevel] = useState(0);
|
||||
@@ -48,7 +49,47 @@ export const AudioSourcePanel = forwardRef((props: { source: AudioSource; onExpa
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full overflow-hidden">
|
||||
<span className={`my-auto truncate`}>{props.source.getName()}</span>
|
||||
<div className={`my-auto w-full truncate`}>
|
||||
{props.source.getName() === "" ? (
|
||||
props.source instanceof FileSource ? (
|
||||
<div
|
||||
className="flex w-full content-center justify-between"
|
||||
>
|
||||
<span className={`my-auto text-red-500`}>No file selected</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
var input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.click();
|
||||
input.onchange = (e: Event) => {
|
||||
let target = e.target as HTMLInputElement;
|
||||
if (target && target.files) {
|
||||
var file = target.files[0];
|
||||
(props.source as FileSource).setFile(file)
|
||||
|
||||
}
|
||||
};
|
||||
}}
|
||||
className={`
|
||||
flex cursor-pointer content-center items-center gap-2
|
||||
rounded-sm bg-blue-600 px-5 py-2.5 text-sm font-medium
|
||||
text-white
|
||||
data-[disabled="true"]:bg-blue-800
|
||||
focus:outline-none focus:ring-4 focus:ring-blue-800
|
||||
hover:bg-blue-700
|
||||
`}
|
||||
>
|
||||
<FaUpload className={`my-auto`} />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
"No name"
|
||||
)
|
||||
) : (
|
||||
props.source.getName()
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!(props.source instanceof MicrophoneSource) && !(props.source instanceof TextToSpeechSource) && (
|
||||
<div
|
||||
@@ -68,22 +109,22 @@ export const AudioSourcePanel = forwardRef((props: { source: AudioSource; onExpa
|
||||
<>
|
||||
{(props.source instanceof FileSource || props.source instanceof TextToSpeechSource) && (
|
||||
<div className="flex flex-col gap-2 rounded-md bg-olympus-400 p-2">
|
||||
{props.source instanceof TextToSpeechSource &&
|
||||
<input
|
||||
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={text}
|
||||
onChange={(ev) => {
|
||||
setText(ev.target.value);
|
||||
}}
|
||||
></input>
|
||||
}
|
||||
{props.source instanceof TextToSpeechSource && (
|
||||
<input
|
||||
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={text}
|
||||
onChange={(ev) => {
|
||||
setText(ev.target.value);
|
||||
}}
|
||||
></input>
|
||||
)}
|
||||
<div className="flex gap-4">
|
||||
<OlStateButton
|
||||
checked={false}
|
||||
@@ -97,7 +138,8 @@ export const AudioSourcePanel = forwardRef((props: { source: AudioSource; onExpa
|
||||
<OlRangeSlider
|
||||
value={props.source.getDuration() > 0 ? (props.source.getCurrentPosition() / props.source.getDuration()) * 100 : 0}
|
||||
onChange={(ev) => {
|
||||
if (props.source instanceof FileSource || props.source instanceof TextToSpeechSource) props.source.setCurrentPosition(parseFloat(ev.currentTarget.value));
|
||||
if (props.source instanceof FileSource || props.source instanceof TextToSpeechSource)
|
||||
props.source.setCurrentPosition(parseFloat(ev.currentTarget.value));
|
||||
}}
|
||||
className="my-auto"
|
||||
/>
|
||||
@@ -124,9 +166,10 @@ export const AudioSourcePanel = forwardRef((props: { source: AudioSource; onExpa
|
||||
flex-row border-gray-500
|
||||
`}
|
||||
>
|
||||
<div style={{ minWidth: `${meterLevel * 100}%` }} className={`
|
||||
rounded-full bg-gray-200
|
||||
`}></div>
|
||||
<div
|
||||
style={{ minWidth: `${meterLevel * 100}%` }}
|
||||
className={`rounded-full bg-gray-200`}
|
||||
></div>
|
||||
</div>
|
||||
<OlRangeSlider
|
||||
value={props.source.getVolume() * 100}
|
||||
|
||||
@@ -11,7 +11,6 @@ import { MapHiddenTypes, MapOptions } from "../types/types";
|
||||
import { NO_SUBSTATE, OlympusState, OlympusSubState, OptionsSubstate, SpawnSubState, UnitControlSubState } from "../constants/constants";
|
||||
import { getApp, setupApp } from "../olympusapp";
|
||||
import { LoginModal } from "./modals/loginmodal";
|
||||
import { FileSourceLoadPrompt } from "./modals/filesourceloadprompt";
|
||||
|
||||
import { MiniMapPanel } from "./panels/minimappanel";
|
||||
import { UnitControlBar } from "./panels/unitcontrolbar";
|
||||
@@ -70,8 +69,7 @@ export function UI() {
|
||||
<LoginModal open={appState === OlympusState.LOGIN} />
|
||||
<ProtectionPromptModal open={appState === OlympusState.UNIT_CONTROL && appSubState == UnitControlSubState.PROTECTION} />
|
||||
<KeybindModal open={appState === OlympusState.OPTIONS && appSubState === OptionsSubstate.KEYBIND} />
|
||||
<FileSourceLoadPrompt open={appState === OlympusState.LOAD_FILES}/>
|
||||
|
||||
|
||||
<div id="map-container" className="z-0 h-full w-screen" />
|
||||
|
||||
<MainMenu open={appState === OlympusState.MAIN_MENU} onClose={() => getApp().setState(OlympusState.IDLE)} />
|
||||
|
||||
Reference in New Issue
Block a user