Skip to content
Snippets Groups Projects
Commit e69e7549 authored by Kasper Seweryn's avatar Kasper Seweryn :pancakes:
Browse files

feat: use vue macros

Part-of: <!47>
parent 6d5e16d7
No related branches found
No related tags found
1 merge request!47Project maintenance
Showing
with 95 additions and 259 deletions
......@@ -21,4 +21,4 @@ Create a pagination bar by passing the number of pages to the `pages` prop. Use
<fw-pagination :pages="8" v-model:page="page" />
```
<fw-pagination :pages="8" v-model:page="page" />
<fw-pagination :pages="9" v-model:page="page" />
......@@ -6,38 +6,13 @@ import { computed } from 'vue'
const { t } = useI18n()
interface Track {
id: number
name: string
artist: {
id: number
name: string
}
cover: {
urls: {
original: string
}
}
}
interface User {
username: string
full_username: string
}
interface Props {
track: Track
user: User
}
const play = defineEmit<[track: Track]>()
const props = defineProps<Props>()
const track = defineProp<Track>('track', { required: true })
const user = defineProp<User>('user', { required: true })
const profileParams = computed(() => {
const [username, domain] = props.user.full_username.split('@')
const [username, domain] = user.value.full_username.split('@')
return { username, domain }
})
......@@ -46,9 +21,9 @@ let navigate = (to: 'track' | 'artist' | 'user') => {}
if (import.meta.env.PROD) {
const router = useRouter()
navigate = (to: 'track' | 'artist' | 'user') => to === 'track'
? router.push({ name: 'library.tracks.detail', params: { id: props.track.id } })
? router.push({ name: 'library.tracks.detail', params: { id: track.value.id } })
: to === 'artist'
? router.push({ name: 'library.artists.detail', params: { id: props.track.artist.id } })
? router.push({ name: 'library.artists.detail', params: { id: track.value.artist.id } })
: router.push({ name: 'profile.full', params: profileParams.value })
}
</script>
......
......@@ -5,35 +5,16 @@ import { useI18n } from 'vue-i18n'
const { t } = useI18n()
interface Album {
id: number
name: string
artist: {
id: number
name: string
}
tracks: object[]
cover: {
urls: {
original: string
}
}
}
interface Props {
album: Album
}
const play = defineEmit<[album: Album]>()
const props = defineProps<Props>()
const album = defineProp<Album>('album', { required: true })
let navigate = (to: 'artist' | 'album') => {}
if (import.meta.env.PROD) {
const router = useRouter()
navigate = (to: 'artist' | 'album') => to === 'album'
? router.push({ name: 'library.albums.detail', params: { id: props.album.id } })
: router.push({ name: 'library.artists.detail', params: { id: props.album.artist.id } })
? router.push({ name: 'library.albums.detail', params: { id: album.value.id } })
: router.push({ name: 'library.artists.detail', params: { id: album.value.artist.id } })
}
</script>
......
......@@ -5,30 +5,14 @@ import { useI18n } from 'vue-i18n'
const { t } = useI18n()
interface Artist {
id: number
name: string
tags: string[]
albums: object[]
cover: {
urls: {
original: string
}
}
}
interface Props {
artist: Artist
}
const play = defineEmit<[artist: Artist]>()
const props = defineProps<Props>()
const artist = defineProp<Artist>('artist', { required: true })
let navigate = () => {}
if (import.meta.env.PROD) {
const router = useRouter()
navigate = () => router.push({ name: 'library.artists.detail', params: { id: props.artist.id } })
navigate = () => router.push({ name: 'library.artists.detail', params: { id: artist.value.id } })
}
</script>
......
......@@ -6,31 +6,16 @@ import { computed } from 'vue'
const { t } = useI18n()
interface Playlist {
id: number
name: string
user: {
full_username: string
username: string
}
tracks_count: number
album_covers: string[]
}
interface Props {
playlist: Playlist
}
const play = defineEmit<[playlist: Playlist]>()
const props = defineProps<Props>()
const playlist = defineProp<Playlist>('playlist', { required: true })
const covers = computed(() => props.playlist.album_covers
const covers = computed(() => playlist.value.album_covers
.filter((src, index, array) => array.indexOf(src) === index)
.slice(0, 4)
)
const profileParams = computed(() => {
const [username, domain] = props.playlist.user.full_username.split('@')
const [username, domain] = playlist.value.user.full_username.split('@')
return { username, domain }
})
......@@ -39,7 +24,7 @@ let navigate = (to: 'playlist' | 'user') => {}
if (import.meta.env.PROD) {
const router = useRouter()
navigate = (to: 'playlist' | 'user') => to === 'playlist'
? router.push({ name: 'library.playlists.detail', params: { id: props.playlist.id } })
? router.push({ name: 'library.playlists.detail', params: { id: playlist.value.id } })
: router.push({ name: 'profile.full', params: profileParams.value })
}
</script>
......
......@@ -3,33 +3,15 @@ import { FwCard, FwOptionsButton } from '~/components'
import { useTimeAgo } from '@vueuse/core'
import { useRouter } from 'vue-router'
interface Podcast {
name: string
artist: {
id: number
name: string
modification_date: '2022-09-11'
cover: {
urls: {
original: string
}
}
}
}
interface Props {
podcast: Podcast
}
const props = defineProps<Props>()
const podcast = defineProp<Podcast>('podcast', { required: true })
const timeAgo = useTimeAgo(new Date(props.podcast.artist.modification_date))
const timeAgo = useTimeAgo(new Date(podcast.value.artist.modification_date))
let navigate = () => {}
if (import.meta.env.PROD) {
const router = useRouter()
navigate = () => router.push({ name: 'library.artists.detail', params: { id: props.podcast.artist.id } })
navigate = () => router.push({ name: 'library.artists.detail', params: { id: podcast.value.artist.id } })
}
</script>
......
......@@ -6,12 +6,6 @@ import { useI18n } from 'vue-i18n'
const { t } = useI18n()
interface Radio {
id: number
name: string
description: string
}
interface Props extends PastelProps {
radio: Radio
small?: boolean
......
<script setup lang="ts">
import { ref } from 'vue'
interface Props {
modelValue: string
icon?: string
placeholder?: string
}
const icon = defineProp<string>()
const placeholder = defineProp<string>()
defineProps<Props>()
const { modelValue: value } = defineModels<{
modelValue: string
}>()
const modelValue = defineModel<string>({
required: true
})
const input = ref()
</script>
......@@ -27,7 +22,7 @@ const input = ref()
</div>
<input
v-bind="$attrs"
v-model="value"
v-model="modelValue"
ref="input"
:placeholder="placeholder"
@click.stop
......
<script setup lang="ts">
import { defineProps } from 'unplugin-vue-macros/macros' assert { type: 'macro' }
interface Props {
container?: boolean
}
defineProps<Props>().withDefaults({
container: true
})
const container = defineProp('container', { default: true })
</script>
<template>
......
......@@ -3,38 +3,40 @@ import { useElementSize } from '@vueuse/core'
import { useI18n } from 'vue-i18n'
import { ref, computed } from 'vue'
import { isMobileView } from '~/composables/useScreenSize';
import { preventNonNumeric } from '~/utils/event-validators'
const { t } = useI18n()
interface Props {
pages: number
}
const pages = defineProp<number>('pages', {
required: true,
validator: (value: number) => value > 0
})
const props = defineProps<Props>()
const page = defineModel<number>('page', {
required: true,
validator: (value: number) => value > 0
})
const goTo = ref('')
const { page } = defineModels<{
page: number
}>()
const goTo = ref<number>()
const range = (start: number, end: number) => Array.from({ length: end - start + 1 }, (_, i) => i + start)
const pages = computed(() => {
const renderPages = computed(() => {
const start = range(2, 5)
const end = range(props.pages - 4, props.pages - 1)
const end = range(pages.value - 4, pages.value - 1)
const pages = [1]
const pagesArray = [1]
if (page.value < 5) pages.push(...start)
if (page.value >= 5 && page.value <= props.pages - 4) {
pages.push(page.value - 1)
pages.push(page.value)
pages.push(page.value + 1)
if (page.value < 5) pagesArray.push(...start)
if (page.value >= 5 && page.value <= pages.value - 4) {
pagesArray.push(page.value - 1)
pagesArray.push(page.value)
pagesArray.push(page.value + 1)
}
if (page.value > props.pages - 4) pages.push(...end)
pages.push(props.pages)
if (page.value > pages.value - 4) pagesArray.push(...end)
pagesArray.push(pages.value)
return pages.filter((page, index, pages) => pages.indexOf(page) === index)
return pagesArray.filter((page, index, pages) => pages.indexOf(page) === index)
})
const pagination = ref()
......@@ -42,11 +44,8 @@ const { width } = useElementSize(pagination)
const isSmall = isMobileView(width)
const setPage = () => {
if (!isNaN(+goTo.value) && goTo.value !== '') {
page.value = Math.min(props.pages, Math.max(1, +goTo.value))
}
goTo.value = ''
if (goTo.value == null) return
page.value = Math.min(pages.value, Math.max(1, goTo.value))
}
</script>
......@@ -73,10 +72,10 @@ const setPage = () => {
</li>
<template v-if="!isSmall">
<template v-for="(i, index) in pages" :key="i">
<template v-for="(i, index) in renderPages" :key="i">
<li>
<fw-button
v-if="i <= props.pages && i > 0 && props.pages > 3"
v-if="i <= pages && i > 0 && pages > 3"
@click="page = i"
secondary
:aria-label="page !== i ? t('vui.aria.pagination.gotoPage', i) : t('vui.aria.pagination.currentPage', page)"
......@@ -85,7 +84,7 @@ const setPage = () => {
{{ i }}
</fw-button>
</li>
<li v-if="i + 1 < pages[index + 1]"></li>
<li v-if="i + 1 < renderPages[index + 1]"></li>
</template>
</template>
<template v-else>
......@@ -99,7 +98,7 @@ const setPage = () => {
1
</fw-button>
</li>
<li v-if="page === 1 || page === props.pages"></li>
<li v-if="page === 1 || page === pages"></li>
<li v-else>
<fw-button
secondary
......@@ -111,12 +110,12 @@ const setPage = () => {
</li>
<li>
<fw-button
@click="page = props.pages"
@click="page = pages"
secondary
:aria-label="page !== props.pages ? t('vui.aria.pagination.gotoPage', page) : t('vui.aria.pagination.currentPage', page)"
:outline="page !== props.pages"
:aria-label="page !== pages ? t('vui.aria.pagination.gotoPage', page) : t('vui.aria.pagination.currentPage', page)"
:outline="page !== pages"
>
{{ props.pages }}
{{ pages }}
</fw-button>
</li>
</template>
......@@ -124,7 +123,7 @@ const setPage = () => {
<li>
<fw-button
@click="page += 1"
:disabled="page >= props.pages"
:disabled="page >= pages"
:aria-label="t('vui.aria.pagination.gotoNext')"
secondary
outline
......@@ -140,7 +139,8 @@ const setPage = () => {
<fw-input
:placeholder="page.toString()"
@keyup.enter="setPage"
v-model="goTo"
@keydown="preventNonNumeric"
v-model.number="goTo"
/>
</div>
</nav>
......
......@@ -2,10 +2,7 @@
import { type ColorProps, useColor } from '~/composables/useColor'
import { type PastelProps, usePastel } from '~/composables/usePastels'
interface Props extends ColorProps, PastelProps {
}
const props = defineProps<Props>()
const props = defineProps<ColorProps & PastelProps>()
const type = useColor(props, 'is-secondary')
const color = usePastel(props, '')
......
<script setup lang="ts">
import { defineProps } from 'unplugin-vue-macros/macros' assert { type: 'macro' }
import { computed, ref, inject, provide, shallowReactive, watch, onScopeDispose } from 'vue'
import { whenever, useElementBounding, onClickOutside } from '@vueuse/core'
import { POPOVER_INJECTION_KEY, POPOVER_CONTEXT_INJECTION_KEY } from '~/injection-keys'
import { isMobileView, useScreenSize } from '~/composables/useScreenSize'
interface Props {
open?: boolean
positioning?: 'horizontal' | 'vertical'
}
const props = defineProps<Props>().withDefaults({
positioning: 'vertical'
})
const { open } = defineModels<{
open: boolean
}>()
const open = defineModel('open', { default: false })
const positioning = defineProp<'horizontal' | 'vertical'>('positioning', { default: 'vertical' })
// Template refs
const popover = ref()
......@@ -48,7 +36,7 @@ whenever(open, update, { immediate: true })
const { width: screenWidth, height: screenHeight } = useScreenSize()
const position = computed(() => {
if (props.positioning === 'vertical' || isMobile.value) {
if (positioning.value === 'vertical' || isMobile.value) {
let offsetTop = top.value + height.value
if (offsetTop + popoverHeight.value > screenHeight.value) {
offsetTop -= popoverHeight.value + height.value
......
<script setup lang="ts">
const { modelValue: value } = defineModels<{
modelValue: boolean
}>()
const value = defineModel<boolean>('modelValue', { required: true })
</script>
<template>
......
......@@ -2,14 +2,10 @@
import { inject, ref } from 'vue'
import { POPOVER_CONTEXT_INJECTION_KEY, type PopoverContext } from '~/injection-keys'
interface Props {
vuiParentPopoverContext?: PopoverContext
}
const setId = defineEmit<[value: number]>('internal:id')
const props = defineProps<Props>()
const { items, hoveredItem } = props.vuiParentPopoverContext ?? inject(POPOVER_CONTEXT_INJECTION_KEY, {
const parentPopoverContext = defineProp<PopoverContext>('vuiParentPopoverContext')
const { items, hoveredItem } = parentPopoverContext.value ?? inject(POPOVER_CONTEXT_INJECTION_KEY, {
items: ref(0),
hoveredItem: ref(-2)
})
......
<script setup lang="ts">
import { computed } from 'vue'
interface Props {
choices: string[]
}
const props = defineProps<Props>()
const choices = defineProp<string[]>('choices', {
required: true,
})
const choices = computed(() => new Set(props.choices))
const filteredChoices = computed(() => new Set(choices.value))
const { modelValue: value } = defineModels<{
modelValue: string
}>()
const value = defineModel<string>('modelValue', { required: true })
// NOTE: Due to the usage of a ref inside a Proxy, this is reactive.
const choiceValues = new Proxy<Record<string, boolean>>(Object.create(null), {
......@@ -29,7 +25,7 @@ const choiceValues = new Proxy<Record<string, boolean>>(Object.create(null), {
</script>
<template>
<fw-popover-checkbox v-for="choice of choices" :key="choice" v-model="choiceValues[choice]">
<fw-popover-checkbox v-for="choice of filteredChoices" :key="choice" v-model="choiceValues[choice]">
{{ choice }}
</fw-popover-checkbox>
</template>
......@@ -3,19 +3,15 @@ import { TABS_INJECTION_KEY } from '~/injection-keys'
import { whenever } from '@vueuse/core'
import { inject, ref } from 'vue'
interface Props {
title: string
}
const props = defineProps<Props>()
const title = defineProp<string>('title', { required: true })
const { currentTab, tabs } = inject(TABS_INJECTION_KEY, {
currentTab: ref(props.title),
currentTab: ref(title.value),
tabs: []
})
whenever(() => !tabs.includes(props.title), () => {
tabs.push(props.title)
whenever(() => !tabs.includes(title.value), () => {
tabs.push(title.value)
}, { immediate: true })
</script>
......
<script setup lang="ts">
import { defineProps } from 'unplugin-vue-macros/macros' assert { type: 'macro' }
import { useTextareaAutosize, computedWithControl, useManualRefHistory, watchDebounced } from '@vueuse/core'
import { FwButton } from '~/components'
import { nextTick, computed, ref, type ComputedRef } from 'vue'
interface Props {
max?: number
placeholder?: string
}
const max = defineProp<number>('max', {
default: Infinity
})
defineProps<Props>().withDefaults({
max: Infinity,
placeholder: ''
const placeholder = defineProp<string>('placeholder', {
default: ''
})
const { modelValue: value } = defineModels<{
......
<script setup lang="ts">
import { defineProps } from 'unplugin-vue-macros/macros' assert { type: 'macro' }
import { computed, ref, watchEffect } from 'vue'
import { slugify } from 'transliteration'
import { useScroll } from '@vueuse/core';
interface Props {
heading?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
}
const props = defineProps<Props>().withDefaults({
heading: 'h1'
const heading = defineProp<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'>('heading', {
default: 'h1'
})
const toc = ref()
const headings = computed(() => toc.value?.querySelectorAll(props.heading) ?? [])
const headings = computed(() => toc.value?.querySelectorAll(heading.value) ?? [])
watchEffect(() => {
for (const heading of headings.value) {
heading.id = slugify(heading.textContent)
......
<script setup lang="ts">
import { defineProps } from 'unplugin-vue-macros/macros' assert { type: 'macro' }
import DOMPurify from 'dompurify'
import { computed, h } from 'vue'
interface Props {
tag?: string
html: string
}
const props = defineProps<Props>().withDefaults({
tag: 'div'
})
const as = defineProp<string>('as', { default: 'div' })
const rawHtml = defineProp<string>('html', { required: true })
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
// set all elements owning target to target=_blank
......@@ -20,10 +12,6 @@ DOMPurify.addHook('afterSanitizeAttributes', (node) => {
}
})
const html = computed(() => DOMPurify.sanitize(props.html))
const root = () => h(props.tag, { innerHTML: html.value })
const html = computed(() => DOMPurify.sanitize(rawHtml.value))
defineRender(() => h(as.value, { innerHTML: html.value }))
</script>
<template>
<root />
</template>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment