feat(stage-web|stage-tamagotchi): better playground, new Volumed, CursorMomentum, and TestDummyMarker components
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
ProviderBasicSettings,
|
||||
ProviderSettingsContainer,
|
||||
ProviderSettingsLayout,
|
||||
TestDummyMarker,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { voiceMap } from '@proj-airi/stage-ui/constants'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
@@ -310,9 +311,14 @@ function handleResetVoiceSettings() {
|
||||
</ProviderSettingsContainer>
|
||||
|
||||
<div flex="~ col gap-6" class="w-full md:w-[60%]">
|
||||
<div rounded-xl>
|
||||
<h2 class="mb-4 text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
|
||||
{{ t('settings.pages.providers.provider.elevenlabs.playground.title') }}
|
||||
<div w-full rounded-xl>
|
||||
<h2 class="mb-4 text-lg text-neutral-500 md:text-2xl dark:text-neutral-400" w-full>
|
||||
<div class="inline-flex items-center gap-4">
|
||||
<TestDummyMarker />
|
||||
<div>
|
||||
{{ t('settings.pages.providers.provider.elevenlabs.playground.title') }}
|
||||
</div>
|
||||
</div>
|
||||
</h2>
|
||||
<div flex="~ col gap-4">
|
||||
<textarea
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ProviderBasicSettings,
|
||||
ProviderSettingsContainer,
|
||||
ProviderSettingsLayout,
|
||||
TestDummyMarker,
|
||||
} from '@proj-airi/stage-ui/components'
|
||||
import { voiceMap } from '@proj-airi/stage-ui/constants'
|
||||
import { useProvidersStore, useSpeechStore } from '@proj-airi/stage-ui/stores'
|
||||
@@ -310,9 +311,14 @@ function handleResetVoiceSettings() {
|
||||
</ProviderSettingsContainer>
|
||||
|
||||
<div flex="~ col gap-6" class="w-full md:w-[60%]">
|
||||
<div rounded-xl>
|
||||
<h2 class="mb-4 text-lg text-neutral-500 md:text-2xl dark:text-neutral-400">
|
||||
{{ t('settings.pages.providers.provider.elevenlabs.playground.title') }}
|
||||
<div w-full rounded-xl>
|
||||
<h2 class="mb-4 text-lg text-neutral-500 md:text-2xl dark:text-neutral-400" w-full>
|
||||
<div class="inline-flex items-center gap-4">
|
||||
<TestDummyMarker />
|
||||
<div>
|
||||
{{ t('settings.pages.providers.provider.elevenlabs.playground.title') }}
|
||||
</div>
|
||||
</div>
|
||||
</h2>
|
||||
<div flex="~ col gap-4">
|
||||
<textarea
|
||||
|
||||
@@ -16,18 +16,30 @@ export default defineConfig({
|
||||
id: 'design-system',
|
||||
title: 'Design System',
|
||||
},
|
||||
{
|
||||
id: 'menu',
|
||||
title: 'Menu',
|
||||
},
|
||||
{
|
||||
id: 'form',
|
||||
title: 'Form',
|
||||
},
|
||||
{
|
||||
id: 'menu',
|
||||
title: 'Menu',
|
||||
},
|
||||
{
|
||||
id: 'providers',
|
||||
title: 'Providers',
|
||||
},
|
||||
{
|
||||
id: 'physics',
|
||||
title: 'Physics',
|
||||
},
|
||||
{
|
||||
id: 'graphics',
|
||||
title: 'Graphics',
|
||||
},
|
||||
{
|
||||
id: 'gadgets',
|
||||
title: 'Gadgets',
|
||||
},
|
||||
{
|
||||
id: 'data-gui',
|
||||
title: 'Data GUI',
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import Volumed from '../Graphics/Volumed.vue'
|
||||
import CursorMomentum from '../Physics/CursorMomentum.vue'
|
||||
import TestDummyMarker from './TestDummyMarker.vue'
|
||||
import TestDummyMarkerFlat from './TestDummyMarkerFlat.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story
|
||||
title="Test Dummy Marker"
|
||||
group="gadgets"
|
||||
:layout="{ type: 'grid', width: 600 }"
|
||||
>
|
||||
<template #controls>
|
||||
<ThemeColorsHueControl />
|
||||
</template>
|
||||
|
||||
<Variant
|
||||
id="flat"
|
||||
title="Flat Version"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<TestDummyMarkerFlat :size="40" />
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="3d-basic"
|
||||
title="3D Basic"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<Volumed :perspective="800">
|
||||
<TestDummyMarkerFlat :size="40" />
|
||||
</Volumed>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="3d-tilted"
|
||||
title="3D Tilted"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<Volumed
|
||||
:perspective="800"
|
||||
transform="rotateX(45deg)"
|
||||
>
|
||||
<TestDummyMarkerFlat :size="40" />
|
||||
</Volumed>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="with-momentum"
|
||||
title="With Cursor Momentum"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<CursorMomentum
|
||||
v-slot="{ currentValue }"
|
||||
:base-speed="0.1"
|
||||
:friction="0.95"
|
||||
:momentum-factor="0.005"
|
||||
>
|
||||
<Volumed
|
||||
:perspective="800"
|
||||
transform="rotateX(45deg)"
|
||||
>
|
||||
<TestDummyMarkerFlat
|
||||
:size="40"
|
||||
:style="{
|
||||
transform: `rotate(${currentValue}deg)`,
|
||||
}"
|
||||
/>
|
||||
</Volumed>
|
||||
</CursorMomentum>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="original"
|
||||
title="Original Implementation"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<TestDummyMarker :size="40" />
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import Volumed from '../Graphics/Volumed.vue'
|
||||
import CursorMomentum from '../Physics/CursorMomentum.vue'
|
||||
import TestDummyMarkerFlat from './TestDummyMarkerFlat.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CursorMomentum v-slot="{ currentValue }" :base-speed="0.1" :friction="0.95" :momentum-factor="0.0005">
|
||||
<Volumed :perspective="800" transform="rotateX(45deg) translateY(3px)">
|
||||
<TestDummyMarkerFlat :size="24" :style="{ transform: `rotate(${currentValue}deg)` }" />
|
||||
</Volumed>
|
||||
</CursorMomentum>
|
||||
</template>
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
size?: number
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="test-dummy-flat"
|
||||
:style="{
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
}"
|
||||
>
|
||||
<div class="marker" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.test-dummy-flat {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.test-dummy-flat .marker {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: conic-gradient(#ffeb3b 0deg 90deg, #000 90deg 180deg, #ffeb3b 180deg 270deg, #000 270deg 360deg);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4px 12px rgba(185, 185, 185, 0.2);
|
||||
}
|
||||
|
||||
.dark .test-dummy-flat .marker {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as TestDummyMarker } from './TestDummyMarker.vue'
|
||||
@@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import Volumed from './Volumed.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story
|
||||
title="Volumed"
|
||||
group="graphics"
|
||||
:layout="{ type: 'grid', width: 600 }"
|
||||
>
|
||||
<template #controls>
|
||||
<ThemeColorsHueControl />
|
||||
</template>
|
||||
|
||||
<Variant
|
||||
id="basic"
|
||||
title="Basic 3D"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<Volumed :perspective="800">
|
||||
<div class="bg-primary-500/20 h-20 w-20" />
|
||||
</Volumed>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="rotated"
|
||||
title="Rotated"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<Volumed
|
||||
:perspective="800"
|
||||
transform="rotateX(45deg) rotateY(45deg)"
|
||||
>
|
||||
<div class="bg-primary-500/20 h-20 w-20" />
|
||||
</Volumed>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="nested"
|
||||
title="Nested 3D"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<Volumed :perspective="800">
|
||||
<Volumed transform="rotateX(45deg)">
|
||||
<div class="bg-primary-500/20 h-20 w-20" />
|
||||
</Volumed>
|
||||
</Volumed>
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
perspective?: number
|
||||
transform?: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="volumed"
|
||||
:style="{
|
||||
perspective: `${perspective}px`,
|
||||
transform,
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.volumed {
|
||||
transform-style: preserve-3d;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as Volumed } from './Volumed.vue'
|
||||
@@ -0,0 +1,133 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
import CursorMomentum from './CursorMomentum.vue'
|
||||
|
||||
// Example components that use momentum
|
||||
const rotatingBox = ref(null)
|
||||
const floatingBall = ref(null)
|
||||
const pulsingCircle = ref(null)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story
|
||||
title="Cursor Momentum"
|
||||
group="physics"
|
||||
:layout="{ type: 'grid', width: 600 }"
|
||||
>
|
||||
<template #controls>
|
||||
<ThemeColorsHueControl />
|
||||
</template>
|
||||
|
||||
<Variant
|
||||
id="rotating-box"
|
||||
title="Rotating Box"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<CursorMomentum
|
||||
v-slot="{ currentValue }"
|
||||
:base-speed="0.1"
|
||||
:friction="0.95"
|
||||
:momentum-factor="0.005"
|
||||
>
|
||||
<div
|
||||
ref="rotatingBox"
|
||||
class="bg-primary-500/20 h-20 w-20"
|
||||
:style="{
|
||||
transform: `rotate(${currentValue}deg)`,
|
||||
}"
|
||||
/>
|
||||
</CursorMomentum>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="floating-ball"
|
||||
title="Floating Ball"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<CursorMomentum
|
||||
v-slot="{ momentum }"
|
||||
:base-speed="0.5"
|
||||
:friction="0.98"
|
||||
:momentum-factor="0.01"
|
||||
>
|
||||
<div
|
||||
ref="floatingBall"
|
||||
class="bg-primary-500/20 h-10 w-10 rounded-full"
|
||||
:style="{
|
||||
transform: `translateY(${Math.sin(momentum * 0.1) * 20}px)`,
|
||||
}"
|
||||
/>
|
||||
</CursorMomentum>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="pulsing-circle"
|
||||
title="Pulsing Circle"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<CursorMomentum
|
||||
v-slot="{ momentum }"
|
||||
:base-speed="1"
|
||||
:friction="0.9"
|
||||
:momentum-factor="0.008"
|
||||
>
|
||||
<div
|
||||
ref="pulsingCircle"
|
||||
class="bg-primary-500/20 h-16 w-16 rounded-full"
|
||||
:style="{
|
||||
transform: `scale(${0.8 + (momentum * 0.2)})`,
|
||||
}"
|
||||
/>
|
||||
</CursorMomentum>
|
||||
</div>
|
||||
</Variant>
|
||||
|
||||
<Variant
|
||||
id="test-dummy"
|
||||
title="Test Dummy Marker"
|
||||
>
|
||||
<div class="h-40 flex items-center justify-center">
|
||||
<CursorMomentum
|
||||
v-slot="{ currentValue }"
|
||||
:base-speed="0.1"
|
||||
:friction="0.95"
|
||||
:momentum-factor="0.005"
|
||||
>
|
||||
<div
|
||||
class="crash-test-dummy"
|
||||
:style="{
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
transform: `rotateX(45deg) rotate(${currentValue}deg)`,
|
||||
}"
|
||||
>
|
||||
<div class="marker" />
|
||||
</div>
|
||||
</CursorMomentum>
|
||||
</div>
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.crash-test-dummy {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
.crash-test-dummy .marker {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: conic-gradient(#ffeb3b 0deg 90deg, #000 90deg 180deg, #ffeb3b 180deg 270deg, #000 270deg 360deg);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4px 12px rgba(185, 185, 185, 0.2);
|
||||
}
|
||||
|
||||
.dark .crash-test-dummy .marker {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,70 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, ref, toRef } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
baseSpeed?: number
|
||||
friction?: number
|
||||
momentumFactor?: number
|
||||
}>(), {
|
||||
baseSpeed: 0.1,
|
||||
friction: 0.95,
|
||||
momentumFactor: 0.005,
|
||||
})
|
||||
|
||||
const momentum = ref(1) // Base momentum
|
||||
const currentValue = ref(0)
|
||||
let lastTimestamp = 0
|
||||
|
||||
// Physics parameters with defaults
|
||||
const FRICTION = toRef(() => props.friction)
|
||||
const BASE_SPEED = toRef(() => props.baseSpeed)
|
||||
const MOMENTUM_FACTOR = toRef(() => props.momentumFactor)
|
||||
|
||||
function updateMomentum(timestamp: number) {
|
||||
if (!lastTimestamp)
|
||||
lastTimestamp = timestamp
|
||||
const deltaTime = timestamp - lastTimestamp
|
||||
lastTimestamp = timestamp
|
||||
|
||||
// Apply friction to gradually return to base speed
|
||||
momentum.value = BASE_SPEED.value + (momentum.value - BASE_SPEED.value) * FRICTION.value
|
||||
|
||||
// Update value based on current momentum
|
||||
currentValue.value += momentum.value * deltaTime
|
||||
|
||||
requestAnimationFrame(updateMomentum)
|
||||
}
|
||||
|
||||
function handleMouseMove(event: MouseEvent) {
|
||||
// Calculate movement speed
|
||||
const speed = Math.sqrt(
|
||||
event.movementX ** 2
|
||||
+ event.movementY ** 2,
|
||||
)
|
||||
|
||||
// Add to current momentum
|
||||
momentum.value += speed * MOMENTUM_FACTOR.value
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
requestAnimationFrame(updateMomentum)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('mousemove', handleMouseMove)
|
||||
})
|
||||
|
||||
// Expose values and momentum to parent
|
||||
defineExpose({
|
||||
momentum,
|
||||
currentValue,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot
|
||||
:momentum="momentum"
|
||||
:current-value="currentValue"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as CursorMomentum } from './CursorMomentum.vue'
|
||||
@@ -1,9 +1,12 @@
|
||||
export { default as BasicTextarea } from './BasicTextarea.vue'
|
||||
export { default as Collapsable } from './Collapsable.vue'
|
||||
export * from './Form'
|
||||
export * from './Gadgets'
|
||||
export * from './Graphics'
|
||||
export { default as Live2DCanvas } from './Live2D/Canvas.vue'
|
||||
export { default as Live2DModel } from './Live2D/Model.vue'
|
||||
export * from './Menu'
|
||||
export * from './Physics'
|
||||
export * from './Providers'
|
||||
export { default as SceneLive2D } from './Scenes/Live2D.vue'
|
||||
export { default as SceneVRM } from './Scenes/VRM.vue'
|
||||
|
||||
Reference in New Issue
Block a user