feat: add locale selector; move audio; detect connectivity
This commit is contained in:
parent
ada2b49700
commit
84dabc67f2
72 changed files with 183 additions and 123 deletions
4
client/src/globals.d.ts
vendored
4
client/src/globals.d.ts
vendored
|
@ -49,8 +49,8 @@ interface TelemetryData {
|
|||
'jerk-x': number
|
||||
'jerk-y': number
|
||||
'voltage': number
|
||||
'acc-profile': Mode | '-999'
|
||||
'gear': Gear | '-999'
|
||||
'acc-profile': Mode
|
||||
'gear': Gear
|
||||
'ebrake': boolean
|
||||
'reorient': boolean
|
||||
'gpws': boolean
|
||||
|
|
|
@ -18,6 +18,7 @@ export const setStationaryTelemetry = () => {
|
|||
'ebrake': false,
|
||||
'reorient': false,
|
||||
'gpws': false,
|
||||
'connected': true,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { Notifications } from '../../Notifications/notifications'
|
||||
import { settingsStore } from '../../stores/settingsStore'
|
||||
import AppContainer from '../AppContainer.svelte'
|
||||
import SettingsSelector from './SettingsSelector.svelte'
|
||||
import SettingsInput from './SettingsInput.svelte'
|
||||
import SettingsToggle from './SettingsToggle.svelte'
|
||||
|
||||
|
@ -20,6 +21,7 @@
|
|||
class="flex gap-6 bg-blue-200 bg-opacity-25 backdrop-blur-xl media-background rounded-3xl flex-wrap px-10 py-20"
|
||||
>
|
||||
<h1 class="text-5xl font-medium text-slate-100 basis-full">Settings</h1>
|
||||
<p class="text-slate-300">Hover over setting names to see helpful tooltips</p>
|
||||
<h2 class="text-2xl font-medium text-slate-200 mt-4 basis-full">General</h2>
|
||||
<div class="flex flex-col gap-2">
|
||||
<SettingsToggle
|
||||
|
@ -39,6 +41,12 @@
|
|||
>
|
||||
RNG Weight
|
||||
</SettingsInput>
|
||||
<SettingsSelector
|
||||
setting="voiceLang"
|
||||
options={['en-US', 'en-RU']}
|
||||
tooltip="Selects the language/locale used for Jankboard voice prompts. Does not affect application language (ie. Jankboard itself will always be in English)."
|
||||
>Voice Prompt Language</SettingsSelector
|
||||
>
|
||||
<button
|
||||
class="mt-10 px-4 py-2 bg-amber-600 hover:brightness-75 text-medium rounded-lg w-min"
|
||||
on:click={resetSettings}>Reset</button
|
||||
|
|
46
client/src/lib/Apps/Settings/SettingsSelector.svelte
Normal file
46
client/src/lib/Apps/Settings/SettingsSelector.svelte
Normal file
|
@ -0,0 +1,46 @@
|
|||
<!--
|
||||
@component
|
||||
|
||||
A selector component that updates settings with the selected value. Designed
|
||||
to be used with settings which have a fixed amount of set values. Only works with
|
||||
settings with number or string values. Prefer the Toggle input type for boolean
|
||||
settings.
|
||||
|
||||
@param setting - The setting to be toggled
|
||||
@param options - The options to be shown in the selector. Must be possible (valid)
|
||||
values for the setting.
|
||||
@param tooltip - Helpful tooltip for the setting
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { settingsStore } from '../../stores/settingsStore'
|
||||
import type { SettingsStoreData } from '../../stores/settingsStore'
|
||||
import { tooltip as tooltipAction } from '@svelte-plugins/tooltips'
|
||||
|
||||
export let setting: keyof SettingsStoreData
|
||||
export let options: string[] | number[]
|
||||
export let tooltip: string = ''
|
||||
|
||||
if (typeof setting !== 'string') {
|
||||
throw new Error('Selector setting must be a string')
|
||||
}
|
||||
|
||||
let selected: string | number = $settingsStore[setting] as string | number
|
||||
|
||||
// Setting is guaranteed to be string by guard clause above
|
||||
// @ts-expect-error
|
||||
$: selected && settingsStore.update(setting, selected)
|
||||
</script>
|
||||
|
||||
<div class="flex gap-2 my-1">
|
||||
<select bind:value={selected} class="w-min bg-slate-400 text-md">
|
||||
{#each options as option}
|
||||
<option value={option}>{option}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<label
|
||||
class="text-xl font-medium text-slate-100"
|
||||
for={setting}
|
||||
use:tooltipAction={{ content: tooltip, action: 'hover', arrow: false }}
|
||||
><slot /></label
|
||||
>
|
||||
</div>
|
|
@ -4,6 +4,7 @@
|
|||
@param accx - Acceleration in x
|
||||
@param accy - Acceleration in y
|
||||
@param orientation - Heading in degrees
|
||||
@param placeholder - Whether or not to show the placeholder skeleton
|
||||
|
||||
Displays the heading direction and acceleration as human readable text
|
||||
-->
|
||||
|
@ -15,9 +16,9 @@
|
|||
export let accx: number
|
||||
export let accy: number
|
||||
export let orientation: number
|
||||
export let placeholder: boolean
|
||||
|
||||
$: accResolved = Math.hypot(accx, accy)
|
||||
$: placeholder = accx === -999 && accy === -999
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2 text-center">
|
||||
|
|
|
@ -29,11 +29,18 @@
|
|||
selectedGear={$telemetryReadonlyStore.gear}
|
||||
selectedMode={$telemetryReadonlyStore['acc-profile']}
|
||||
voltage={$telemetryReadonlyStore.voltage}
|
||||
placeholder={!$telemetryReadonlyStore.connected}
|
||||
/>
|
||||
<div class="h-0.5 mt-1 w-full bg-slate-300 border-0"></div>
|
||||
<div class="mt-8 flex justify-between">
|
||||
<Speedometer speed={speedResolved} />
|
||||
<SpeedLimit speedLimit={-999} />
|
||||
<Speedometer
|
||||
speed={speedResolved}
|
||||
placeholder={!$telemetryReadonlyStore.connected}
|
||||
/>
|
||||
<SpeedLimit
|
||||
speedLimit={5}
|
||||
placeholder={!$telemetryReadonlyStore.connected}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -47,6 +54,7 @@
|
|||
accx={$telemetryReadonlyStore['accx']}
|
||||
accy={$telemetryReadonlyStore['accy']}
|
||||
orientation={$telemetryReadonlyStore['orientation']}
|
||||
placeholder={!$telemetryReadonlyStore.connected}
|
||||
/>
|
||||
</div>
|
||||
<MediaDisplay />
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
@component
|
||||
|
||||
@param speedLimit - Speed limit in Miles Per Hour (MPH)
|
||||
@param placeholder - Whether or not to show the placeholder skeleton
|
||||
|
||||
Displays the speed limit
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let speedLimit: number = 5.0
|
||||
|
||||
$: placeholder = speedLimit === -999
|
||||
export let speedLimit: number
|
||||
export let placeholder: boolean
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@component
|
||||
|
||||
@param speed - Speed in meters per second
|
||||
@param placeholder - Whether or not to show the placeholder skeleton
|
||||
|
||||
Displays the speed in miles per hour
|
||||
-->
|
||||
|
@ -10,10 +11,9 @@
|
|||
import { mps2mph } from '../utils/unitConversions'
|
||||
|
||||
export let speed: number = 0.0
|
||||
export let placeholder: boolean
|
||||
|
||||
$: formatted = mps2mph(speed).toFixed(1)
|
||||
|
||||
$: placeholder = speed === Math.hypot(-999, -999)
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
|
@ -24,10 +24,5 @@
|
|||
>
|
||||
{placeholder ? '-----' : formatted}
|
||||
</div>
|
||||
<div
|
||||
class="text-2xl font-medium transition"
|
||||
class:placeholder={speed === Math.hypot(-999, -999)}
|
||||
>
|
||||
MPH
|
||||
</div>
|
||||
<div class="text-2xl font-medium transition" class:placeholder>MPH</div>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<script lang="ts">
|
||||
export let voltage: number
|
||||
export let placeholder: boolean
|
||||
|
||||
$: formatted = voltage.toFixed(1)
|
||||
|
||||
$: placeholder = voltage === -999
|
||||
</script>
|
||||
|
||||
<span class="flex gap-1">
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
<script lang="ts">
|
||||
export let selectedGear: Gear | '-999'
|
||||
export let selectedGear: Gear
|
||||
export let placeholder: boolean
|
||||
</script>
|
||||
|
||||
<div class="flex justify-center w-full transition">
|
||||
<div
|
||||
class="flex flex-row gap-2 text-neutral-400 text-xl font-bold"
|
||||
class:placeholder={selectedGear === '-999'}
|
||||
class:placeholder
|
||||
>
|
||||
<div class:highlighted={selectedGear === 'park'}>P</div>
|
||||
<div class:highlighted={selectedGear === 'reverse'}>R</div>
|
||||
<div class:highlighted={selectedGear === 'neutral'}>N</div>
|
||||
<div class:highlighted={selectedGear === 'low'}>L</div>
|
||||
<div class:highlighted={selectedGear === 'auto'}>A</div>
|
||||
<div class:highlighted={selectedGear === 'drive'}>D</div>
|
||||
<div class:highlighted={selectedGear === 'park' && !placeholder}>P</div>
|
||||
<div class:highlighted={selectedGear === 'reverse' && !placeholder}>R</div>
|
||||
<div class:highlighted={selectedGear === 'neutral' && !placeholder}>N</div>
|
||||
<div class:highlighted={selectedGear === 'low' && !placeholder}>L</div>
|
||||
<div class:highlighted={selectedGear === 'auto' && !placeholder}>A</div>
|
||||
<div class:highlighted={selectedGear === 'drive' && !placeholder}>D</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -2,29 +2,34 @@
|
|||
@component
|
||||
|
||||
@param selectedMode - Selected mode
|
||||
@param placeholder - Whether or not to show the placeholder skeleton
|
||||
|
||||
Displays the drive mode
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
export let selectedMode: Mode | '-999'
|
||||
export let selectedMode: Mode
|
||||
export let placeholder: boolean
|
||||
|
||||
let modeText = ''
|
||||
|
||||
$: switch (selectedMode) {
|
||||
case 'chill':
|
||||
modeText = 'CHILL'
|
||||
break
|
||||
case 'cruise':
|
||||
modeText = 'CRUISE'
|
||||
break
|
||||
case 'ludicrous':
|
||||
modeText = 'LUDICROUS'
|
||||
break
|
||||
case '-999':
|
||||
$: {
|
||||
if (placeholder) {
|
||||
modeText = 'DISCONNECTED'
|
||||
break
|
||||
} else {
|
||||
switch (selectedMode) {
|
||||
case 'chill':
|
||||
modeText = 'CHILL'
|
||||
break
|
||||
case 'cruise':
|
||||
modeText = 'CRUISE'
|
||||
break
|
||||
case 'ludicrous':
|
||||
modeText = 'LUDICROUS'
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
@param selectedGear - Selected gear
|
||||
@param selectedMode - Selected mode
|
||||
@param voltage - Battery voltage
|
||||
@param placeholder - Whether or not to show placeholder skeleton UIs
|
||||
|
||||
Displays the top bar of the dashboard
|
||||
-->
|
||||
|
@ -13,19 +14,20 @@
|
|||
import GearSelector from './GearSelector.svelte'
|
||||
import ModeSelector from './ModeSelector.svelte'
|
||||
|
||||
export let selectedGear: Gear | '-999'
|
||||
export let selectedMode: Mode | '-999'
|
||||
export let selectedGear: Gear
|
||||
export let selectedMode: Mode
|
||||
export let voltage: number
|
||||
export let placeholder: boolean
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row w-full justify-between">
|
||||
<div>
|
||||
<GearSelector {selectedGear} />
|
||||
<GearSelector {selectedGear} {placeholder} />
|
||||
</div>
|
||||
<div>
|
||||
<ModeSelector {selectedMode} />
|
||||
<ModeSelector {selectedMode} {placeholder} />
|
||||
</div>
|
||||
<div>
|
||||
<BatteryDisplay {voltage} />
|
||||
<BatteryDisplay {voltage} {placeholder} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { T, useTask } from "@threlte/core";
|
||||
import { ContactShadows, Float, Grid, OrbitControls } from "@threlte/extras";
|
||||
import Controls from "./Controls.svelte";
|
||||
import { T, useTask } from '@threlte/core'
|
||||
import { ContactShadows, Float, Grid, OrbitControls } from '@threlte/extras'
|
||||
import Controls from './Controls.svelte'
|
||||
|
||||
import {
|
||||
Vector3,
|
||||
|
@ -9,15 +9,15 @@
|
|||
type Group,
|
||||
type Object3D,
|
||||
type Object3DEventMap,
|
||||
} from "three";
|
||||
} from 'three'
|
||||
import {
|
||||
telemetryReadonlyStore,
|
||||
telemetryStore,
|
||||
} from "../../stores/telemetryStore";
|
||||
import { get } from "svelte/store";
|
||||
import { Vector2 } from "three";
|
||||
import { SmoothMotionController } from "./smoothMotionController";
|
||||
import { onMount } from "svelte";
|
||||
} from '../../stores/telemetryStore'
|
||||
import { get } from 'svelte/store'
|
||||
import { Vector2 } from 'three'
|
||||
import { SmoothMotionController } from './smoothMotionController'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
/* This is the root scene where the robot visualization is built.
|
||||
It renders an infinite grid (it's not actually infinite, but we shouldn't run out
|
||||
|
@ -32,91 +32,91 @@
|
|||
is the most esoteric and jank code ever written.
|
||||
*/
|
||||
|
||||
let shouldOrbit = true;
|
||||
let shouldOrbit = true
|
||||
|
||||
// CONSTANTS
|
||||
const maxAngularVelocity = 2; // Max angular velocity, in radians per second
|
||||
const stoppingThreshold = 0.005; // Threshold in radians for when to consider the rotation close enough to stop
|
||||
const maxAngularVelocity = 2 // Max angular velocity, in radians per second
|
||||
const stoppingThreshold = 0.005 // Threshold in radians for when to consider the rotation close enough to stop
|
||||
|
||||
// Proportional control factor
|
||||
const kP = 2; // Adjust this value based on responsiveness and stability needs
|
||||
const kP = 2 // Adjust this value based on responsiveness and stability needs
|
||||
|
||||
// Sync robot orientation with target rotation
|
||||
let targetRot = 0;
|
||||
let targetRot = 0
|
||||
|
||||
// Updates rotation to match target with PID controller (intended to be invoked in useTask)
|
||||
let rot = 0; // (initial) rotation in radians
|
||||
let angularVelocity = 0;
|
||||
let rot = 0 // (initial) rotation in radians
|
||||
let angularVelocity = 0
|
||||
const updateRotation = (delta: number) => {
|
||||
let angleDifference = targetRot - rot;
|
||||
let angleDifference = targetRot - rot
|
||||
|
||||
// Normalize angle difference to the range [-π, π]
|
||||
angleDifference = ((angleDifference + Math.PI) % (2 * Math.PI)) - Math.PI;
|
||||
angleDifference = ((angleDifference + Math.PI) % (2 * Math.PI)) - Math.PI
|
||||
|
||||
// Calculate the desired angular velocity based on the angle difference
|
||||
let desiredVelocity =
|
||||
Math.sign(angleDifference) *
|
||||
Math.min(maxAngularVelocity, Math.abs(kP * angleDifference));
|
||||
Math.min(maxAngularVelocity, Math.abs(kP * angleDifference))
|
||||
|
||||
// If the object is very close to the target, adjust the desired velocity to zero to prevent overshooting
|
||||
if (Math.abs(angleDifference) < stoppingThreshold) {
|
||||
desiredVelocity = 0;
|
||||
desiredVelocity = 0
|
||||
}
|
||||
|
||||
// Adjust angular velocity towards desired velocity
|
||||
angularVelocity = desiredVelocity;
|
||||
angularVelocity = desiredVelocity
|
||||
|
||||
// Update rotation
|
||||
rot += angularVelocity * delta;
|
||||
rot += angularVelocity * delta
|
||||
|
||||
// Normalize rot to the range [0, 2π]
|
||||
if (rot < 0) rot += 2 * Math.PI;
|
||||
else if (rot > 2 * Math.PI) rot -= 2 * Math.PI;
|
||||
if (rot < 0) rot += 2 * Math.PI
|
||||
else if (rot > 2 * Math.PI) rot -= 2 * Math.PI
|
||||
|
||||
// Snap to the target rotation to prevent tiny oscillations if close enough
|
||||
if (Math.abs(angleDifference) < stoppingThreshold) {
|
||||
rot = targetRot;
|
||||
angularVelocity = 0;
|
||||
rot = targetRot
|
||||
angularVelocity = 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let robotPos: Vector3 = new Vector3(0, 0, 0);
|
||||
let robotPos: Vector3 = new Vector3(0, 0, 0)
|
||||
|
||||
const robotPosition = new Vector2(0, 0); // Initial position
|
||||
const initialVelocity = { x: 0, y: 0 }; // Initial velocity
|
||||
const robotPosition = new Vector2(0, 0) // Initial position
|
||||
const initialVelocity = { x: 0, y: 0 } // Initial velocity
|
||||
// The smooth motion controller utilizes a cubic hermite spline to interpolate between
|
||||
// the current simulation velocity and the robot's actual velocity
|
||||
const controller = new SmoothMotionController(robotPosition, initialVelocity);
|
||||
const controller = new SmoothMotionController(robotPosition, initialVelocity)
|
||||
|
||||
onMount(() => {
|
||||
telemetryReadonlyStore.subscribe((value) => {
|
||||
targetRot = (value["orientation"] * Math.PI) / 180; // convert deg to rad
|
||||
telemetryReadonlyStore.subscribe(value => {
|
||||
targetRot = (value['orientation'] * Math.PI) / 180 // convert deg to rad
|
||||
controller.setTargetVelocity({
|
||||
x: value["chassis-x-speed"],
|
||||
y: value["chassis-y-speed"],
|
||||
});
|
||||
shouldOrbit = value.gear === "park" || value.gear === "-999";
|
||||
x: value['chassis-x-speed'],
|
||||
y: value['chassis-y-speed'],
|
||||
})
|
||||
// shouldOrbit = value.gear === "park" || value.gear === "-999";
|
||||
if (shouldOrbit) {
|
||||
robotPos = new Vector3(0, 0, 0);
|
||||
controller.reset();
|
||||
robotPos = new Vector3(0, 0, 0)
|
||||
controller.reset()
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
useTask((delta) => {
|
||||
useTask(delta => {
|
||||
if (!shouldOrbit) {
|
||||
updateRotation(delta);
|
||||
updateRotation(delta)
|
||||
|
||||
controller.update(delta);
|
||||
robotPos.x = controller.getPosition().x;
|
||||
robotPos.z = controller.getPosition().y;
|
||||
controller.update(delta)
|
||||
robotPos.x = controller.getPosition().x
|
||||
robotPos.z = controller.getPosition().y
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
let capsule: Group<Object3DEventMap>;
|
||||
let capRef: Group<Object3DEventMap>;
|
||||
let capsule: Group<Object3DEventMap>
|
||||
let capRef: Group<Object3DEventMap>
|
||||
$: if (capsule) {
|
||||
capRef = capsule;
|
||||
capRef = capsule
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -145,7 +145,6 @@
|
|||
|
||||
<ContactShadows scale={10} blur={2} far={2.5} opacity={0.5} />
|
||||
|
||||
|
||||
<!-- <Hornet
|
||||
position.y={2}
|
||||
position.z={robotPos.z}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
/* stores global app wide settings */
|
||||
|
||||
import { writable } from "svelte/store";
|
||||
import { writable } from 'svelte/store'
|
||||
|
||||
type SupportedLanguage = "en" | "rus";
|
||||
type SupportedLanguage = 'en-US' | 'en-RU'
|
||||
|
||||
export interface SettingsStoreData {
|
||||
disableAnnoyances: boolean;
|
||||
goWoke: boolean;
|
||||
fastStartup: boolean;
|
||||
randomWeight: number;
|
||||
voiceLang: SupportedLanguage;
|
||||
disableAnnoyances: boolean
|
||||
goWoke: boolean
|
||||
fastStartup: boolean
|
||||
randomWeight: number
|
||||
voiceLang: SupportedLanguage
|
||||
}
|
||||
|
||||
export const defaults: SettingsStoreData = {
|
||||
|
@ -17,26 +17,26 @@ export const defaults: SettingsStoreData = {
|
|||
goWoke: false, // go woke (for showing parents or other officials where DEI has taken over), disables "offensive" sequences
|
||||
fastStartup: false, // skip the loading splash screen (for development purposes. Setting this from within the app has no effect.)
|
||||
randomWeight: 1, // the weight of random events (multiplied by the original probability)
|
||||
voiceLang: "en",
|
||||
};
|
||||
voiceLang: 'en-US',
|
||||
}
|
||||
|
||||
const createSequenceStore = () => {
|
||||
const { subscribe, set, update } = writable<SettingsStoreData>(defaults);
|
||||
const { subscribe, set, update } = writable<SettingsStoreData>(defaults)
|
||||
return {
|
||||
subscribe,
|
||||
update: (
|
||||
data: keyof SettingsStoreData,
|
||||
newValue: SettingsStoreData[typeof data]
|
||||
) => {
|
||||
update((store) => {
|
||||
update(store => {
|
||||
// @ts-expect-error
|
||||
store[data] = newValue;
|
||||
return store;
|
||||
});
|
||||
store[data] = newValue
|
||||
return store
|
||||
})
|
||||
},
|
||||
reset: () => set(defaults),
|
||||
set: (data: SettingsStoreData) => set(data),
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const settingsStore = createSequenceStore();
|
||||
export const settingsStore = createSequenceStore()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { get } from "svelte/store";
|
||||
import { settingsStore } from "../stores/settingsStore";
|
||||
import { get } from 'svelte/store'
|
||||
import { settingsStore } from '../stores/settingsStore'
|
||||
|
||||
/**
|
||||
* Retrieves the voice audio path for the given audio file.
|
||||
|
@ -8,18 +8,13 @@ import { settingsStore } from "../stores/settingsStore";
|
|||
* @param lang - the language of the audio
|
||||
* @return the path of the audio file
|
||||
*/
|
||||
type SupportedLanguage = "en" | "rus";
|
||||
type SupportedLanguage = 'en-US' | 'en-RU'
|
||||
|
||||
let currentLang = "en";
|
||||
|
||||
settingsStore.subscribe((data) => {
|
||||
currentLang = data.voiceLang;
|
||||
});
|
||||
export default function getVoicePath(audio: string, lang?: SupportedLanguage) {
|
||||
console.log(get(settingsStore).voiceLang);
|
||||
console.log(get(settingsStore).voiceLang)
|
||||
if (!lang) {
|
||||
return `/static/voices/${get(settingsStore).voiceLang}/${audio}.wav`;
|
||||
return `/static/voices/${get(settingsStore).voiceLang}/${audio}.wav`
|
||||
}
|
||||
|
||||
return `/static/voices/${lang}/${audio}.wav`;
|
||||
return `/static/voices/${lang}/${audio}.wav`
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue