ADD: Profile management functionality & store
This commit is contained in:
114
src/components/ProfileButton.vue
Normal file
114
src/components/ProfileButton.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div
|
||||
class="h-12 flex profile-row"
|
||||
@mouseenter="hover=true" @mouseleave="hover=false">
|
||||
<button
|
||||
:class="{'font-semibold bg-zinc-200 hover:bg-zinc-100 text-black' : selected,
|
||||
'hover:bg-zinc-900 bg-opacity-50 text-white': !selected,
|
||||
'text-ellipsis': !editing}"
|
||||
class="flex-1 h-full text-left whitespace-nowrap overflow-hidden"
|
||||
@click="!editing && $emit('select') && $refs.profileTitle.scramble()">
|
||||
<FileDigit
|
||||
:class="{'text-zinc-600': selected,
|
||||
'text-muted-foreground': !selected,
|
||||
'w-0': hover}"
|
||||
class="h-4 ml-10 mb-1 inline-block" />
|
||||
<input
|
||||
v-if="editing" ref="profileNameInput" v-model="profile.name"
|
||||
onfocus="this.select()" :placeholder="$t('profiles.name_placeholder')"
|
||||
class="pl-10 w-full h-full bg-transparent focus-visible:ring-0 focus-visible:outline-none"
|
||||
style="color: inherit; background: inherit">
|
||||
<template v-else>
|
||||
<ScrambleText
|
||||
ref="profileTitle"
|
||||
:class="{'text-black': selected, 'text-zinc-100': !selected}"
|
||||
:text="profile.name" />
|
||||
<span
|
||||
class="text-xs text-zinc-600"
|
||||
:class="{'hidden': hover}"> uID:{{ profile.id }}</span>
|
||||
</template>
|
||||
</button>
|
||||
<template v-if="!confirmDelete">
|
||||
<button
|
||||
:class="{'bg-zinc-200 hover:bg-zinc-100 text-black' : selected,
|
||||
'hover:bg-opacity-100 bg-zinc-900 text-zinc-100 bg-opacity-50': !selected,
|
||||
'w-12': editing,
|
||||
'w-0': !editing}"
|
||||
class="flex h-12 justify-center items-center flex-shrink-0"
|
||||
@click="editing=false">
|
||||
<Check class="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
:class="{'bg-zinc-200 hover:bg-zinc-100 text-black' : selected,
|
||||
'hover:bg-opacity-100 bg-zinc-900 text-zinc-100 bg-opacity-50': !selected,
|
||||
'w-12' : hover && !editing}"
|
||||
class="flex w-0 h-12 justify-center items-center flex-shrink-0"
|
||||
@click="editing=true; $nextTick(()=>{$refs.profileNameInput.focus()})">
|
||||
<PenLine class="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
:class="{'bg-zinc-200 hover:bg-zinc-100 text-black' : selected,
|
||||
'hover:bg-opacity-100 bg-zinc-900 text-zinc-100 bg-opacity-50': !selected,
|
||||
'w-12' : hover && !editing}"
|
||||
class="flex w-0 h-12 justify-center items-center flex-shrink-0">
|
||||
<Copy class="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
:class="{'bg-orange-600 hover:bg-orange-500 text-black' : selected,
|
||||
'hover:bg-opacity-100 bg-orange-900 text-zinc-100 bg-opacity-50': !selected,
|
||||
'w-12' : hover && !editing}"
|
||||
class="flex w-0 h-12 justify-center items-center flex-shrink-0"
|
||||
@click="confirmDelete=true">
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button
|
||||
:class="{'bg-orange-600 hover:bg-orange-500 text-black' : selected,
|
||||
'hover:bg-opacity-100 bg-orange-900 text-zinc-100 bg-opacity-50': !selected,
|
||||
'w-12' : hover && !editing}"
|
||||
class="flex w-0 h-12 justify-center items-center flex-shrink-0"
|
||||
@click="$emit('delete', profile.id)">
|
||||
<Check class="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
:class="{'bg-zinc-200 hover:bg-zinc-100 text-black' : selected,
|
||||
'hover:bg-opacity-100 bg-zinc-900 text-zinc-100 bg-opacity-50': !selected,
|
||||
'w-12' : hover && !editing}"
|
||||
class="flex w-0 h-12 justify-center items-center flex-shrink-0"
|
||||
@click="confirmDelete=false">
|
||||
<X class="h-4 w-4" />
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Check, Copy, FileDigit, PenLine, Trash2, X } from 'lucide-vue-next'
|
||||
import ScrambleText from '@/components/effects/ScrambleText.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineEmits(['select', 'duplicate', 'delete'])
|
||||
|
||||
const profile = defineModel({
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
id: '1234',
|
||||
name: 'Profile Name',
|
||||
}),
|
||||
})
|
||||
|
||||
defineProps({
|
||||
selected: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const editing = ref(false)
|
||||
|
||||
const confirmDelete = ref(false)
|
||||
|
||||
const hover = ref(false)
|
||||
|
||||
</script>
|
||||
@@ -23,7 +23,8 @@
|
||||
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 disabled:cursor-not-allowed disabled:opacity-50">
|
||||
<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">
|
||||
<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 />
|
||||
</button>
|
||||
</div>
|
||||
@@ -37,7 +38,8 @@
|
||||
</div>
|
||||
<div>
|
||||
<Collapsible
|
||||
v-for="[profileTag, tagProfiles] in filteredProfilesByTag" :key="profileTag" v-model:open="collapse[profileTag]"
|
||||
v-for="[profileTag, tagProfiles] in filteredProfilesByTag" :key="profileTag"
|
||||
v-model:open="collapse[profileTag]"
|
||||
:default-open="true">
|
||||
<CollapsibleTrigger
|
||||
class="w-full h-12 py-2 text-left text-muted-foreground text-sm hover:bg-zinc-900">
|
||||
@@ -45,53 +47,33 @@
|
||||
{{ profileTag }}<span class="font-heading text-sm text-zinc-600"> ({{ tagProfiles.length }})</span>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div
|
||||
v-for="profile in tagProfiles" :key="profile.id"
|
||||
class="h-12 flex profile-row">
|
||||
<button
|
||||
:data-selected="currentProfile===profile.id"
|
||||
class="flex-1 h-full text-left data-[selected=true]:font-semibold hover:bg-zinc-900 data-[selected=true]:bg-zinc-200 hover:data-[selected=true]:bg-zinc-100 data-[selected=true]:text-black flex-nowrap text-ellipsis overflow-hidden whitespace-nowrap"
|
||||
@click="currentProfile=profile.id; profileTitles[profile.id].scramble()">
|
||||
<FileDigit
|
||||
:data-selected="currentProfile===profile.id"
|
||||
class="h-4 w-4 mb-1 ml-10 mr-2 inline-block text-muted-foreground data-[selected=true]:text-zinc-600" />
|
||||
<ScrambleText :ref="el => { profileTitles[profile.id] = el }" :text="profile.name" />
|
||||
<span class="text-xs text-zinc-600"> uID:{{ profile.id }}</span>
|
||||
</button>
|
||||
<button
|
||||
:data-selected="currentProfile===profile.id"
|
||||
class="flex w-0 h-12 transition-all text-zinc-100 justify-center items-center profile-button hover:bg-opacity-100 bg-opacity-50 bg-zinc-900 data-[selected=true]:bg-zinc-200 hover:data-[selected=true]:bg-zinc-100 data-[selected=true]:text-black">
|
||||
<Copy class="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
:data-selected="currentProfile===profile.id"
|
||||
class="flex w-0 h-12 transition-all bg-orange-900 text-zinc-100 justify-center items-center profile-button hover:bg-orange-700 data-[selected=true]:bg-orange-600 hover:data-[selected=true]:bg-orange-500 data-[selected=true]:text-black">
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<ProfileButton
|
||||
v-for="(profile, index) in tagProfiles" :key="profile.id" v-model="tagProfiles[index]"
|
||||
:selected="currentProfile===profile.id"
|
||||
@select="currentProfile=profile.id" />
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>
|
||||
<SchemaTest />
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import SchemaTest from '@/components/SchemaTest.vue'
|
||||
import { Separator } from '@/components/ui/separator/index.js'
|
||||
import { FileDigit, ChevronRight, Search, Trash2, Copy, Plus } from 'lucide-vue-next'
|
||||
import axios from 'axios'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible'
|
||||
import { ChevronRight, Plus, Search } from 'lucide-vue-next'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
||||
import ScrambleText from '@/components/effects/ScrambleText.vue'
|
||||
import { store } from '@/store.js'
|
||||
import ProfileButton from '@/components/ProfileButton.vue'
|
||||
|
||||
const maxProfiles = 32
|
||||
|
||||
const profiles = ref([])
|
||||
const editingId = ref(null)
|
||||
|
||||
const profiles = computed({
|
||||
get: () => store.device.profiles,
|
||||
set: val => store.device.profiles = val,
|
||||
})
|
||||
const filter = ref('')
|
||||
const collapse = ref({})
|
||||
|
||||
@@ -122,25 +104,10 @@ const filteredProfilesByTag = computed(() => {
|
||||
})
|
||||
return map
|
||||
})
|
||||
|
||||
function fetchProfiles() {
|
||||
axios.get('http://localhost:3001/profiles').then(res => {
|
||||
profiles.value = res.data
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchProfiles()
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
[data-state=open] > .chevrot {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.profile-row:hover .profile-button {
|
||||
width: 3rem /* 48px */;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user