feat(nds): improve fullscreen button and add volume slider
This commit is contained in:
79
app/components/Controls.vue
Normal file
79
app/components/Controls.vue
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useFullscreen } from "@vueuse/core";
|
||||||
|
|
||||||
|
const app = useAppStore();
|
||||||
|
|
||||||
|
const showFullscreenBtn = ref(true);
|
||||||
|
const { isFullscreen, toggle: toggleFullscreen } = useFullscreen();
|
||||||
|
|
||||||
|
const open = ref(false);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
|
||||||
|
showFullscreenBtn.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="fixed bottom-4 right-4 flex flex-col items-center">
|
||||||
|
<UPopover
|
||||||
|
v-model:open="open"
|
||||||
|
:content="{ side: 'top', align: 'center' }"
|
||||||
|
:ui="{ content: 'bg-transparent border-0 ring-0 shadow-none' }"
|
||||||
|
>
|
||||||
|
<template #default>
|
||||||
|
<UButton
|
||||||
|
:icon="
|
||||||
|
app.settings.volume === 0
|
||||||
|
? 'i-heroicons-speaker-x-mark'
|
||||||
|
: 'i-heroicons-speaker-wave'
|
||||||
|
"
|
||||||
|
color="neutral"
|
||||||
|
variant="link"
|
||||||
|
size="xl"
|
||||||
|
:class="[
|
||||||
|
'transition-opacity',
|
||||||
|
open
|
||||||
|
? 'opacity-100 text-highlighted'
|
||||||
|
: 'opacity-50 hover:opacity-100',
|
||||||
|
]"
|
||||||
|
@keydown.up.prevent="open = true"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #content>
|
||||||
|
<div class="flex items-center justify-center h-24">
|
||||||
|
<USlider
|
||||||
|
:model-value="app.settings.volume * 100"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:step="2"
|
||||||
|
orientation="vertical"
|
||||||
|
color="neutral"
|
||||||
|
@update:model-value="
|
||||||
|
(v) => {
|
||||||
|
app.settings.volume = (v ?? 100) / 100;
|
||||||
|
app.save();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
|
||||||
|
<UButton
|
||||||
|
v-if="showFullscreenBtn"
|
||||||
|
:icon="
|
||||||
|
isFullscreen
|
||||||
|
? 'i-heroicons-arrows-pointing-in'
|
||||||
|
: 'i-heroicons-arrows-pointing-out'
|
||||||
|
"
|
||||||
|
color="neutral"
|
||||||
|
variant="link"
|
||||||
|
size="xl"
|
||||||
|
class="opacity-50 hover:opacity-100 transition-opacity"
|
||||||
|
@click="toggleFullscreen"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -12,8 +12,9 @@ const createAudio = (path: string) => {
|
|||||||
return {
|
return {
|
||||||
play: (volume = 1) => {
|
play: (volume = 1) => {
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
|
const app = useAppStore();
|
||||||
const audio = source.cloneNode() as HTMLAudioElement;
|
const audio = source.cloneNode() as HTMLAudioElement;
|
||||||
audio.volume = volume;
|
audio.volume = Math.min(1, volume * app.settings.volume);
|
||||||
audio.addEventListener("ended", () => audio.remove(), { once: true });
|
audio.addEventListener("ended", () => audio.remove(), { once: true });
|
||||||
audio.play().catch(() => {});
|
audio.play().catch(() => {});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ export const useClockTick = () => {
|
|||||||
const s = now.getSeconds();
|
const s = now.getSeconds();
|
||||||
if (s === lastSecond) return;
|
if (s === lastSecond) return;
|
||||||
lastSecond = s;
|
lastSecond = s;
|
||||||
assets.audio.clockTick.play(s === 0 ? 1 : 0.7);
|
assets.audio.clockTick.play(s === 0 ? 2 : 1.4);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ export const useKeyDown = (callback: KeyDownCallback) => {
|
|||||||
if (app.lagDetected) return;
|
if (app.lagDetected) return;
|
||||||
|
|
||||||
const ndsButton = mapCodeToNDS(event.code);
|
const ndsButton = mapCodeToNDS(event.code);
|
||||||
|
if (
|
||||||
|
ndsButton &&
|
||||||
|
document.activeElement &&
|
||||||
|
document.activeElement !== document.body
|
||||||
|
)
|
||||||
|
return;
|
||||||
callback({
|
callback({
|
||||||
key: ndsButton ? `NDS_${ndsButton}` : event.key,
|
key: ndsButton ? `NDS_${ndsButton}` : event.key,
|
||||||
ndsButton,
|
ndsButton,
|
||||||
|
|||||||
@@ -23,20 +23,9 @@ const bottomScreen = useTemplateRef("bottomScreen");
|
|||||||
const topScreenCanvas = computed(() => topScreen.value?.canvas ?? null);
|
const topScreenCanvas = computed(() => topScreen.value?.canvas ?? null);
|
||||||
const bottomScreenCanvas = computed(() => bottomScreen.value?.canvas ?? null);
|
const bottomScreenCanvas = computed(() => bottomScreen.value?.canvas ?? null);
|
||||||
|
|
||||||
const isIOS = () => /iPad|iPhone|iPod/.test(navigator.userAgent);
|
|
||||||
const isTouchDevice = () =>
|
const isTouchDevice = () =>
|
||||||
"ontouchstart" in window || navigator.maxTouchPoints > 0;
|
"ontouchstart" in window || navigator.maxTouchPoints > 0;
|
||||||
|
|
||||||
const showFullscreenBtn = ref(true);
|
|
||||||
|
|
||||||
const toggleFullscreen = () => {
|
|
||||||
if (document.fullscreenElement) {
|
|
||||||
document.exitFullscreen();
|
|
||||||
} else {
|
|
||||||
document.documentElement.requestFullscreen();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
|
|
||||||
watch([windowSize.width, windowSize.height], ([width, height]) => {
|
watch([windowSize.width, windowSize.height], ([width, height]) => {
|
||||||
@@ -116,10 +105,6 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (isIOS()) {
|
|
||||||
showFullscreenBtn.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTouchDevice()) {
|
if (isTouchDevice()) {
|
||||||
const landscape = window.matchMedia("(orientation: landscape)");
|
const landscape = window.matchMedia("(orientation: landscape)");
|
||||||
|
|
||||||
@@ -229,22 +214,7 @@ useKeyDown(async ({ key, repeated }) => {
|
|||||||
?
|
?
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<Controls />
|
||||||
v-if="showFullscreenBtn"
|
|
||||||
class="fullscreen-btn"
|
|
||||||
@click="toggleFullscreen"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -270,23 +240,4 @@ useKeyDown(async ({ key, repeated }) => {
|
|||||||
.help-btn:hover {
|
.help-btn:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fullscreen-btn {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 16px;
|
|
||||||
right: 16px;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
padding: 4px;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #666;
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0.5;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fullscreen-btn:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const settingsSchema = z.object({
|
|||||||
row: z.number(),
|
row: z.number(),
|
||||||
}),
|
}),
|
||||||
renderingMode: z.enum(["3d", "2d"]),
|
renderingMode: z.enum(["3d", "2d"]),
|
||||||
|
volume: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Settings = z.infer<typeof settingsSchema>;
|
type Settings = z.infer<typeof settingsSchema>;
|
||||||
@@ -31,6 +32,7 @@ type Settings = z.infer<typeof settingsSchema>;
|
|||||||
const defaultSettings = (): Settings => ({
|
const defaultSettings = (): Settings => ({
|
||||||
color: { col: 0, row: 0 },
|
color: { col: 0, row: 0 },
|
||||||
renderingMode: "3d",
|
renderingMode: "3d",
|
||||||
|
volume: 0.5,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useAppStore = defineStore("app", {
|
export const useAppStore = defineStore("app", {
|
||||||
|
|||||||
Reference in New Issue
Block a user