UPD: UI Animations

This commit is contained in:
Robert Kossessa
2024-02-06 23:53:14 +01:00
parent ed21ccabf0
commit 58f0509d87
6 changed files with 157 additions and 95 deletions

View File

@@ -14,18 +14,41 @@ store.fetchProfiles()
<main class="select-none w-screen h-screen flex flex-col">
<Navbar class="flex-none" />
<div class="flex-1 min-h-0 flex flex-row justify-center">
<div class="basis-1/3 min-w-60 flex-1 flex">
<ProfileManager
v-if="store.connected"
class="flex-1 flex flex-col border-solid border-0 border-r bg-zinc-900 bg-opacity-50" />
<div class="basis-1/3 min-w-60 flex-1 flex overflow-hidden">
<Transition name="slide-left">
<ProfileManager
v-if="store.connected"
class="flex-1 max-w-full flex flex-col border-solid border-0 border-r bg-zinc-900 bg-opacity-50" />
</Transition>
</div>
<DevicePreview
class="basis-1/3 flex-col flex" />
<div class="basis-2/5 flex-1 flex">
<ConfigPane
v-if="store.connected"
class="flex-1 flex flex-col border-solid border-0 border-l bg-zinc-900 bg-opacity-50" />
<div class="basis-2/5 flex-1 flex overflow-hidden">
<Transition name="slide-right">
<ConfigPane
v-if="store.connected"
class="flex-1 flex flex-col border-solid border-0 border-l bg-zinc-900 bg-opacity-50" />
</Transition>
</div>
</div>
</main>
</template>
</template>
<style scoped>
.slide-left-enter-active,
.slide-left-leave-active,
.slide-right-enter-active,
.slide-right-leave-active {
transition: transform 500ms ease;
}
.slide-left-enter-from,
.slide-left-leave-to {
transform: translateX(-100%);
}
.slide-right-enter-from,
.slide-right-leave-to {
transform: translateX(100%);
}
</style>

View File

@@ -0,0 +1,10 @@
<template>
<ConfigSection title="Key Mapping" :icon-component="Keyboard">
<WIP />
</ConfigSection>
</template>
<script setup>
import { Keyboard } from 'lucide-vue-next'
import ConfigSection from '@/components/common/ConfigSection.vue'
import WIP from '@/components/WIP.vue'
</script>

View File

@@ -0,0 +1,10 @@
<template>
<ConfigSection title="Knob Mapping" :icon-component="GaugeCircle">
<WIP />
</ConfigSection>
</template>
<script setup>
import { GaugeCircle } from 'lucide-vue-next'
import ConfigSection from '@/components/common/ConfigSection.vue'
import WIP from '@/components/WIP.vue'
</script>

View File

@@ -3,22 +3,27 @@
<div
class="bg-contain bg-top bg-no-repeat h-full w-full relative"
:style="{backgroundImage: `linear-gradient(to bottom, black, rgba(0,0,0,0.2) 12%, rgba(0,0,0,0.3) 95%, black), url(${RenderNano})`}">
<div v-if="store.connected" class="px-10 h-12 flex justify-between items-center">
<h2>Nano_D++</h2>
<div class="font-mono text-sm">
<span class="text-muted-foreground">Firmware: </span>
<ScrambleText text="v1.3.2a" />
<Transition name="fade">
<div v-if="store.connected" class="px-10 h-12 flex justify-between items-center">
<h2>Nano_D++</h2>
<div class="font-mono text-sm">
<span class="text-muted-foreground">Firmware: </span>
<ScrambleText text="v1.3.2a" />
</div>
</div>
</div>
<DeviceLEDRing
v-if="store.connected" :value="barValue"
class="absolute h-[66%] top-[12.5%] left-0 right-0 mx-auto" />
</Transition>
<Transition name="fade">
<DeviceLEDRing
v-if="store.connected" :value="barValue"
class="absolute h-[66%] top-[12.5%] left-0 right-0 mx-auto" />
</Transition>
<div
class="rounded-full aspect-square absolute h-[30%] top-[30.5%] left-0 right-0 mx-auto flex flex-col justify-center items-center overflow-hidden"
style="background: linear-gradient(45deg, black 30%, #252525 50%, #232323 60%, black)">
<div
v-if="store.connected"
class="flex flex-col items-center text-center pb-2 mix-blend-screen">
class="absolute flex flex-col items-center text-center pb-2 mix-blend-screen">
<img :src="LogoMidi" alt="midi-logo" class="opacity-50 h-4">
<h2 class="font-pixellg text-5xl">{{ parseInt(value) }}</h2>
<div class="font-pixelsm text-md">HIGH PASS</div>
@@ -44,11 +49,13 @@
:class="{'outline outline-white': store.selectedFeature==='knob',
'hover:outline outline-zinc-400': store.selectedFeature!=='knob'}"
@click="store.selectConfigFeature('knob')" />
<DeviceKeys
v-if="store.connected"
class="absolute w-[72.7%] top-[77.2%] gap-[2.8%] left-0 right-0 mx-auto"
:selected="store.selectedFeature === 'key' && store.selectedKey"
@select="store.selectKey" />
<Transition name="fade-slow">
<DeviceKeys
v-if="store.connected"
class="absolute w-[72.7%] top-[77.2%] gap-[2.8%] left-0 right-0 mx-auto"
:selected="store.selectedFeature === 'key' && store.selectedKey"
@select="store.selectKey" />
</Transition>
</div>
</div>
</template>
@@ -94,6 +101,7 @@ const offlineTexts = [
let offlineTextIndex = 0
const nextOfflineText = () => {
console.log('nextOfflineText', offlineText.value)
if (offlineText.value === '') {
offlineText.value = offlineTexts[offlineTextIndex]
offlineTextIndex = (offlineTextIndex + 1) % offlineTexts.length
@@ -107,4 +115,22 @@ const nextOfflineText = () => {
onMounted(() => {
animateValue()
})
</script>
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 500ms ease;
}
.fade-slow-enter-active,
.fade-slow-leave-active {
transition: opacity 1000ms ease;
}
.fade-enter-from,
.fade-leave-to,
.fade-slow-enter-from,
.fade-slow-leave-to {
opacity: 0;
}
</style>

View File

@@ -3,41 +3,26 @@
<div>
<div
class="w-full h-12 px-4 flex items-center justify-between flex-nowrap text-nowrap bg-zinc-900">
<button class="flex items-center" @click="showProfileConfig=false">
<button class="flex items-center min-w-0 font-heading" @click="showProfileConfig=false">
<component :is="showProfileConfig ? ArrowLeft : List" class="w-5 h-full mr-1 shrink-0" />
<span class="font-heading mr-2">
<ScrambleText :text="showProfileConfig ? store.selectedProfile?.name : $t('profiles.title')" />
<ScrambleText
v-if="!showProfileConfig" class="text-sm text-zinc-600"
scramble-on-mount
:fill-interval="20"
:delay="500"
:text="`(${store.profiles.length}/${ maxProfiles})`" />
</span>
</button>
<button
v-if="!showProfileConfig"
class="bg-zinc-200 text-black hover:bg-zinc-100 rounded-full aspect-square h-7 flex justify-center items-center"
@click="store.addProfile">
<Plus class="h-4" />
</button>
</div>
<Separator />
</div>
<div v-if="showFilter">
<div class="flex w-full h-12 items-center">
<label for="filter" class="flex h-full items-center cursor-text">
<Search class="ml-4 mr-2 mb-0.5 h-4 w-4 shrink-0 opacity-50 float-left" />
</label>
<input
id="filter"
v-model="filter"
:placeholder="$t('profiles.filter_placeholder')"
class="grow h-full bg-transparent text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0">
<button
class="h-full flex text-zinc-200 bg-zinc-900 justify-center items-center aspect-square border-solid border-0 border-l hover:bg-zinc-800">
<Plus />
<ScrambleText
:text="showProfileConfig ? store.selectedProfile?.name : $t('profiles.title')"
class="text-ellipsis overflow-hidden min-w-0" />
<ScrambleText
v-if="!showProfileConfig" class="text-sm text-zinc-600 text-ellipsis overflow-hidden min-w-0"
scramble-on-mount
:fill-interval="20"
:delay="500"
:text="`(${store.profiles.length}/${ maxProfiles})`" />
</button>
<Transition name="fade">
<button
v-if="!showProfileConfig"
class="bg-zinc-200 text-black hover:bg-zinc-100 rounded-full aspect-square h-7 flex justify-center items-center"
@click="store.addProfile">
<Plus class="h-4" />
</button>
</Transition>
</div>
<Separator />
</div>
@@ -45,7 +30,7 @@
<div
v-if="renderProfileList"
class="absolute w-full">
<div v-if="filteredProfiles.length === 0">
<div v-if="store.profiles.length === 0">
<div class="flex flex-col items-center justify-center h-32">
<ScrambleText
scramble-on-mount :fill-interval="5" class="text-sm text-muted-foreground"
@@ -97,8 +82,8 @@
</template>
<script setup>
import { Separator } from '@/components/ui/separator'
import { ChevronRight, Plus, Search, ArrowLeft, List } from 'lucide-vue-next'
import { computed, ref, watch } from 'vue'
import { ChevronRight, Plus, ArrowLeft, List } from 'lucide-vue-next'
import { ref, watch } from 'vue'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
import ScrambleText from '@/components/common/ScrambleText.vue'
import { useStore } from '@/store.js'
@@ -137,30 +122,30 @@ watch(showProfileConfig, value => {
}
})
const filteredProfiles = computed(() => {
if (!filter.value) {
return store.profiles
}
const filterLower = filter.value.toLowerCase()
return store.profiles.filter(profile => {
const nameLower = profile.name.toLowerCase()
const idLower = profile.id.toLowerCase()
const tagLower = profile.profileTag.toLowerCase()
return nameLower.includes(filterLower) || idLower.includes(filterLower) || tagLower.includes(filterLower)
})
})
const filteredProfilesByTag = computed(() => {
const map = new Map()
filteredProfiles.value.forEach(profile => {
const tag = profile.profileTag || 'Uncategorized'
if (!map.has(tag)) {
map.set(tag, [])
}
map.get(tag).push(profile)
})
return map
})
// const filteredProfiles = computed(() => {
// if (!filter.value) {
// return store.profiles
// }
// const filterLower = filter.value.toLowerCase()
// return store.profiles.filter(profile => {
// const nameLower = profile.name.toLowerCase()
// const idLower = profile.id.toLowerCase()
// const tagLower = profile.profileTag.toLowerCase()
// return nameLower.includes(filterLower) || idLower.includes(filterLower) || tagLower.includes(filterLower)
// })
// })
//
// const filteredProfilesByTag = computed(() => {
// const map = new Map()
// filteredProfiles.value.forEach(profile => {
// const tag = profile.profileTag || 'Uncategorized'
// if (!map.has(tag)) {
// map.set(tag, [])
// }
// map.get(tag).push(profile)
// })
// return map
// })
const onProfileDrop = (event, categoryIndex) => {
if (event.moved) {
@@ -192,12 +177,18 @@ const dragOptions = {
transition: transform 150ms ease;
}
.slide-enter-active {
transition-delay: 150ms;
}
.slide-enter-from,
.slide-leave-to {
transform: translateX(-100%);
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 150ms ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -3,10 +3,12 @@ import Axios from 'axios'
import schema from '@/data/profileSchema.json'
import Ajv from 'ajv'
import WIP from '@/components/WIP.vue'
import MappingConfig from '@/components/old/MappingConfig.vue'
import KnobFeedbackConfig from '@/components/config/knob/KnobFeedbackConfig.vue'
import KnobLightConfig from '@/components/config/knob/KnobLightConfig.vue'
import KeyLightConfig from '@/components/config/keys/KeyLightConfig.vue'
import KnobMappingConfig from '@/components/config/knob/KnobMappingConfig.vue'
import KeyMappingConfig from '@/components/config/keys/KeyMappingConfig.vue'
import { shallowRef } from 'vue'
const ajv = new Ajv()
@@ -22,17 +24,17 @@ export const useStore = defineStore('main', {
configPages: {
knob: {
mapping: {
titleKey: 'config_options.mapping_configuration.title', component: MappingConfig,
titleKey: 'config_options.mapping_configuration.title', component: shallowRef(KnobMappingConfig),
}, feedback: {
titleKey: 'config_options.feedback_designer.title', component: KnobFeedbackConfig,
titleKey: 'config_options.feedback_designer.title', component: shallowRef(KnobFeedbackConfig),
}, lighting: {
titleKey: 'config_options.light_designer.title', component: KnobLightConfig,
titleKey: 'config_options.light_designer.title', component: shallowRef(KnobLightConfig),
},
}, key: {
mapping: {
titleKey: 'config_options.mapping_configuration.title', component: MappingConfig,
titleKey: 'config_options.mapping_configuration.title', component: shallowRef(KeyMappingConfig),
}, lighting: {
titleKey: 'config_options.light_designer.title', component: KeyLightConfig,
titleKey: 'config_options.light_designer.title', component: shallowRef(KeyLightConfig),
},
},
},