Remove unused Modal, Collapsible components and events page
This commit is contained in:
parent
e5e4dca8e4
commit
44c3397d85
|
|
@ -1,313 +0,0 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'CollapseTransition',
|
||||
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'collapse',
|
||||
},
|
||||
dimension: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'height',
|
||||
validator: (value) => {
|
||||
return ['height', 'width'].includes(value)
|
||||
},
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 300,
|
||||
},
|
||||
easing: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'ease-in-out',
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['before-appear', 'appear', 'after-appear', 'appear-cancelled', 'before-enter', 'enter', 'after-enter', 'enter-cancelled', 'before-leave', 'leave', 'after-leave', 'leave-cancelled'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
cachedStyles: null,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
transition() {
|
||||
const transitions = []
|
||||
|
||||
Object.keys(this.cachedStyles).forEach((key) => {
|
||||
transitions.push(
|
||||
`${this.convertToCssProperty(key)} ${this.duration}ms ${this.easing}`,
|
||||
)
|
||||
})
|
||||
|
||||
return transitions.join(', ')
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
dimension() {
|
||||
this.clearCachedDimensions()
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
beforeAppear(el) {
|
||||
// Emit the event to the parent
|
||||
this.$emit('before-appear', el)
|
||||
},
|
||||
|
||||
appear(el) {
|
||||
// Emit the event to the parent
|
||||
this.$emit('appear', el)
|
||||
},
|
||||
|
||||
afterAppear(el) {
|
||||
// Emit the event to the parent
|
||||
this.$emit('after-appear', el)
|
||||
},
|
||||
|
||||
appearCancelled(el) {
|
||||
// Emit the event to the parent
|
||||
this.$emit('appear-cancelled', el)
|
||||
},
|
||||
|
||||
beforeEnter(el) {
|
||||
// Emit the event to the parent
|
||||
this.$emit('before-enter', el)
|
||||
},
|
||||
|
||||
enter(el, done) {
|
||||
// Because width and height may be 'auto',
|
||||
// first detect and cache the dimensions
|
||||
this.detectAndCacheDimensions(el)
|
||||
|
||||
// The order of applying styles is important:
|
||||
// - 1. Set styles for state before transition
|
||||
// - 2. Force repaint
|
||||
// - 3. Add transition style
|
||||
// - 4. Set styles for state after transition
|
||||
// If the order is not right and you open any 2nd level submenu
|
||||
// for the first time, the transition will not work.
|
||||
this.setClosedDimensions(el)
|
||||
this.hideOverflow(el)
|
||||
this.forceRepaint(el)
|
||||
this.setTransition(el)
|
||||
this.setOpenedDimensions(el)
|
||||
|
||||
// Emit the event to the parent
|
||||
this.$emit('enter', el, done)
|
||||
|
||||
// Call done() when the transition ends
|
||||
// to trigger the @after-enter event.
|
||||
setTimeout(done, this.duration)
|
||||
},
|
||||
|
||||
afterEnter(el) {
|
||||
// Clean up inline styles
|
||||
this.unsetOverflow(el)
|
||||
this.unsetTransition(el)
|
||||
this.unsetDimensions(el)
|
||||
this.clearCachedDimensions()
|
||||
|
||||
// Emit the event to the parent
|
||||
this.$emit('after-enter', el)
|
||||
},
|
||||
|
||||
enterCancelled(el) {
|
||||
// Emit the event to the parent
|
||||
this.$emit('enter-cancelled', el)
|
||||
},
|
||||
|
||||
beforeLeave(el) {
|
||||
// Emit the event to the parent
|
||||
this.$emit('before-leave', el)
|
||||
},
|
||||
|
||||
leave(el, done) {
|
||||
// For some reason, @leave triggered when starting
|
||||
// from open state on page load. So for safety,
|
||||
// check if the dimensions have been cached.
|
||||
this.detectAndCacheDimensions(el)
|
||||
|
||||
// The order of applying styles is less important
|
||||
// than in the enter phase, as long as we repaint
|
||||
// before setting the closed dimensions.
|
||||
// But it is probably best to use the same
|
||||
// order as the enter phase.
|
||||
this.setOpenedDimensions(el)
|
||||
this.hideOverflow(el)
|
||||
this.forceRepaint(el)
|
||||
this.setTransition(el)
|
||||
this.setClosedDimensions(el)
|
||||
|
||||
// Emit the event to the parent
|
||||
this.$emit('leave', el, done)
|
||||
|
||||
// Call done() when the transition ends
|
||||
// to trigger the @after-leave event.
|
||||
// This will also cause v-show
|
||||
// to reapply 'display: none'.
|
||||
setTimeout(done, this.duration)
|
||||
},
|
||||
|
||||
afterLeave(el) {
|
||||
// Clean up inline styles
|
||||
this.unsetOverflow(el)
|
||||
this.unsetTransition(el)
|
||||
this.unsetDimensions(el)
|
||||
this.clearCachedDimensions()
|
||||
|
||||
// Emit the event to the parent
|
||||
this.$emit('after-leave', el)
|
||||
},
|
||||
|
||||
leaveCancelled(el) {
|
||||
// Emit the event to the parent
|
||||
this.$emit('leave-cancelled', el)
|
||||
},
|
||||
|
||||
detectAndCacheDimensions(el) {
|
||||
// Cache actual dimensions
|
||||
// only once to void invalid values when
|
||||
// triggering during a transition
|
||||
if (this.cachedStyles)
|
||||
return
|
||||
|
||||
const visibility = el.style.visibility
|
||||
const display = el.style.display
|
||||
|
||||
// Trick to get the width and
|
||||
// height of a hidden element
|
||||
el.style.visibility = 'hidden'
|
||||
el.style.display = ''
|
||||
|
||||
this.cachedStyles = this.detectRelevantDimensions(el)
|
||||
|
||||
// Restore any original styling
|
||||
el.style.visibility = visibility
|
||||
el.style.display = display
|
||||
},
|
||||
|
||||
clearCachedDimensions() {
|
||||
this.cachedStyles = null
|
||||
},
|
||||
|
||||
detectRelevantDimensions(el) {
|
||||
// These properties will be transitioned
|
||||
if (this.dimension === 'height') {
|
||||
return {
|
||||
height: `${el.offsetHeight}px`,
|
||||
paddingTop:
|
||||
el.style.paddingTop || this.getCssValue(el, 'padding-top'),
|
||||
paddingBottom:
|
||||
el.style.paddingBottom || this.getCssValue(el, 'padding-bottom'),
|
||||
}
|
||||
}
|
||||
|
||||
if (this.dimension === 'width') {
|
||||
return {
|
||||
width: `${el.offsetWidth}px`,
|
||||
paddingLeft:
|
||||
el.style.paddingLeft || this.getCssValue(el, 'padding-left'),
|
||||
paddingRight:
|
||||
el.style.paddingRight || this.getCssValue(el, 'padding-right'),
|
||||
}
|
||||
}
|
||||
|
||||
return {}
|
||||
},
|
||||
|
||||
setTransition(el) {
|
||||
el.style.transition = this.transition
|
||||
},
|
||||
|
||||
unsetTransition(el) {
|
||||
el.style.transition = ''
|
||||
},
|
||||
|
||||
hideOverflow(el) {
|
||||
el.style.overflow = 'hidden'
|
||||
},
|
||||
|
||||
unsetOverflow(el) {
|
||||
el.style.overflow = ''
|
||||
},
|
||||
|
||||
setClosedDimensions(el) {
|
||||
Object.keys(this.cachedStyles).forEach((key) => {
|
||||
el.style[key] = '0'
|
||||
})
|
||||
},
|
||||
|
||||
setOpenedDimensions(el) {
|
||||
Object.keys(this.cachedStyles).forEach((key) => {
|
||||
el.style[key] = this.cachedStyles[key]
|
||||
})
|
||||
},
|
||||
|
||||
unsetDimensions(el) {
|
||||
Object.keys(this.cachedStyles).forEach((key) => {
|
||||
el.style[key] = ''
|
||||
})
|
||||
},
|
||||
|
||||
forceRepaint(el) {
|
||||
// Force repaint to make sure the animation is triggered correctly.
|
||||
// Thanks: https://markus.oberlehner.net/blog/transition-to-height-auto-with-vue/
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
getComputedStyle(el)[this.dimension]
|
||||
},
|
||||
|
||||
getCssValue(el, style) {
|
||||
return getComputedStyle(el, null).getPropertyValue(style)
|
||||
},
|
||||
|
||||
convertToCssProperty(style) {
|
||||
// Example: convert 'paddingTop' to 'padding-top'
|
||||
// Thanks: https://gist.github.com/tan-yuki/3450323
|
||||
const upperChars = style.match(/([A-Z])/g)
|
||||
|
||||
if (!upperChars)
|
||||
return style
|
||||
|
||||
for (let i = 0, n = upperChars.length; i < n; i++) {
|
||||
style = style.replace(
|
||||
new RegExp(upperChars[i]),
|
||||
`-${upperChars[i].toLowerCase()}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (style.slice(0, 1) === '-')
|
||||
style = style.slice(1)
|
||||
|
||||
return style
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition
|
||||
:name="name"
|
||||
@before-appear="beforeAppear"
|
||||
@appear="appear"
|
||||
@after-appear="afterAppear"
|
||||
@appear-cancelled="appearCancelled"
|
||||
@before-enter="beforeEnter"
|
||||
@enter="enter"
|
||||
@after-enter="afterEnter"
|
||||
@enter-cancelled="enterCancelled"
|
||||
@before-leave="beforeLeave"
|
||||
@leave="leave"
|
||||
@after-leave="afterLeave"
|
||||
@leave-cancelled="leaveCancelled"
|
||||
>
|
||||
<slot />
|
||||
</transition>
|
||||
</template>
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import type { Story } from '@storybook/vue3'
|
||||
import Collapsible from './Collapsible.vue'
|
||||
|
||||
export default {
|
||||
title: 'Components/Collapsible',
|
||||
component: Collapsible,
|
||||
args: {
|
||||
modelValue: false,
|
||||
title: 'Item',
|
||||
content: 'lorem ipsum dolor sit amet',
|
||||
},
|
||||
}
|
||||
|
||||
const Template: Story = (args, { argTypes }) => ({
|
||||
components: { Collapsible },
|
||||
setup() {
|
||||
return { args, argTypes }
|
||||
},
|
||||
template: `
|
||||
<Collapsible v-bind="args"/>
|
||||
`,
|
||||
})
|
||||
|
||||
export const Default = Template.bind({})
|
||||
Default.args = {}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
||||
import { ref, toRefs, watch } from 'vue'
|
||||
import CollapseTransition from './CollapseTransition.vue'
|
||||
import Modal from '../Modal/Modal.vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: boolean
|
||||
title: string
|
||||
content?: string
|
||||
classes?: {
|
||||
wrapper?: string
|
||||
button?: string
|
||||
title?: string
|
||||
panel?: string
|
||||
}
|
||||
}>(),
|
||||
{
|
||||
modelValue: false,
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'change',
|
||||
'toggle',
|
||||
'open',
|
||||
'close',
|
||||
])
|
||||
|
||||
const { modelValue } = toRefs(props)
|
||||
const isOpen = ref(modelValue.value)
|
||||
|
||||
watch(modelValue, (val) => {
|
||||
isOpen.value = val
|
||||
})
|
||||
|
||||
watch(isOpen, (val) => {
|
||||
emit('update:modelValue', val)
|
||||
emit('change', val)
|
||||
|
||||
if (val)
|
||||
emit('open')
|
||||
else
|
||||
emit('close')
|
||||
})
|
||||
|
||||
const toggle = () => {
|
||||
emit('toggle')
|
||||
isOpen.value = !isOpen.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Disclosure v-slot="{ open }" as="div">
|
||||
<DisclosureButton
|
||||
class="
|
||||
flex
|
||||
items-center
|
||||
justify-between
|
||||
w-full
|
||||
text-left
|
||||
rounded-lg
|
||||
focus:outline-none
|
||||
focus-visible:ring
|
||||
focus-visible:ring-blue-50
|
||||
focus-visible:ring-opacity-75
|
||||
"
|
||||
:class="classes?.button"
|
||||
type="button"
|
||||
@click="toggle"
|
||||
>
|
||||
<div class="inline-flex w-full">
|
||||
<Icon
|
||||
name="heroicons:chevron-down"
|
||||
:class="isOpen ? 'transform rotate-180' : ''"
|
||||
class="w-5 h-5 text-black"
|
||||
/>
|
||||
<slot name="title"></slot>
|
||||
</div>
|
||||
</DisclosureButton>
|
||||
<CollapseTransition>
|
||||
<div v-show="isOpen">
|
||||
<DisclosurePanel static class="pb-2 text-15" :class="classes?.panel">
|
||||
<slot name="content"></slot>
|
||||
</DisclosurePanel>
|
||||
</div>
|
||||
</CollapseTransition>
|
||||
</Disclosure>
|
||||
</template>
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import type { Story } from '@storybook/vue3'
|
||||
import CollapsibleGroup from './CollapsibleGroup.vue'
|
||||
|
||||
const genItems = (length = 5): any[] =>
|
||||
Array.from({ length }, (_, v) => ({
|
||||
title: `Item ${v + 1}`,
|
||||
content: `lorem ipsum ${v + 1}`,
|
||||
}))
|
||||
|
||||
const items = genItems(5)
|
||||
|
||||
export default {
|
||||
title: 'Components/CollapsibleGroup',
|
||||
component: CollapsibleGroup,
|
||||
args: {
|
||||
modelValue: false,
|
||||
accordion: false,
|
||||
items,
|
||||
},
|
||||
}
|
||||
|
||||
const Template: Story = (args, { argTypes }) => ({
|
||||
components: { CollapsibleGroup },
|
||||
setup() {
|
||||
return { args, argTypes }
|
||||
},
|
||||
template: `
|
||||
<CollapsibleGroup v-bind="args"/>
|
||||
`,
|
||||
})
|
||||
|
||||
export const Default = Template.bind({})
|
||||
Default.args = {}
|
||||
|
||||
export const Accordion = Template.bind({})
|
||||
Accordion.args = {
|
||||
accordion: true,
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
<script lang="ts" setup>
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue'
|
||||
import { ref, toRefs, watch } from 'vue'
|
||||
import Collapsible from './Collapsible.vue'
|
||||
|
||||
interface CollapsibleItem {
|
||||
title: string
|
||||
content: string
|
||||
isOpen?: boolean
|
||||
}
|
||||
|
||||
const props
|
||||
= defineProps<{
|
||||
items?: CollapsibleItem[]
|
||||
classes?: {
|
||||
wrapper?: string
|
||||
button?: string
|
||||
title?: string
|
||||
panel?: string
|
||||
}
|
||||
accordion?: boolean
|
||||
}>()
|
||||
|
||||
const { items } = toRefs(props)
|
||||
|
||||
const children = ref(props.items)
|
||||
|
||||
watch(items, (val) => {
|
||||
children.value = val
|
||||
})
|
||||
|
||||
const onToggle = (item: CollapsibleItem) => {
|
||||
if (props.accordion) {
|
||||
children.value.forEach((child) => {
|
||||
child.isOpen = false
|
||||
})
|
||||
item.isOpen = true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full p-2" :class="classes?.wrapper">
|
||||
<slot>
|
||||
<Collapsible
|
||||
v-for="(item, idx) in children"
|
||||
:key="idx"
|
||||
v-bind="item"
|
||||
v-model="item.isOpen"
|
||||
@toggle="onToggle(item)"
|
||||
/>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<template>
|
||||
<div class="fixed inset-0 bg-black/50 z-15 transition duration-300" />
|
||||
</template>
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
Dialog,
|
||||
DialogPanel,
|
||||
TransitionChild,
|
||||
TransitionRoot,
|
||||
} from '@headlessui/vue'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
modelValue?: boolean
|
||||
persistent?: boolean
|
||||
fullscreen?: boolean
|
||||
maxHeight?: string
|
||||
}>(),
|
||||
{
|
||||
modelValue: false,
|
||||
persistent: false,
|
||||
fullscreen: false,
|
||||
maxHeight: '85vh',
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void
|
||||
}>()
|
||||
|
||||
const { modelValue } = toRefs(props)
|
||||
|
||||
const isOpen = ref(modelValue.value)
|
||||
|
||||
watch(modelValue, (value) => {
|
||||
isOpen.value = value
|
||||
})
|
||||
|
||||
function closeModal() {
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
isOpen.value = true
|
||||
}
|
||||
|
||||
function onModalClose() {
|
||||
if (!props.persistent)
|
||||
closeModal()
|
||||
}
|
||||
|
||||
watch(isOpen, (value) => {
|
||||
emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
const api = {
|
||||
isOpen,
|
||||
open: openModal,
|
||||
close: closeModal,
|
||||
}
|
||||
|
||||
provide('modal', api)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<slot name="activator" :open="openModal" :on="{ click: openModal }" />
|
||||
|
||||
<TransitionRoot appear :show="isOpen" as="template">
|
||||
<Dialog as="div" class="relative z-20" @close="onModalClose">
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="duration-300 ease-out"
|
||||
enter-from="opacity-0"
|
||||
enter-to="opacity-100"
|
||||
leave="duration-200 ease-in"
|
||||
leave-from="opacity-100"
|
||||
leave-to="opacity-0"
|
||||
>
|
||||
<div class="fixed inset-0 bg-black bg-opacity-25" />
|
||||
</TransitionChild>
|
||||
|
||||
<div class="fixed inset-0 overflow-y-auto">
|
||||
<div
|
||||
class="flex min-h-full items-center justify-center text-center"
|
||||
:class="{
|
||||
'p-4': !fullscreen,
|
||||
}"
|
||||
>
|
||||
<TransitionChild
|
||||
as="template"
|
||||
enter="duration-300 ease-out"
|
||||
enter-from="opacity-0 scale-95"
|
||||
enter-to="opacity-100 scale-100"
|
||||
leave="duration-200 ease-in"
|
||||
leave-from="opacity-100 scale-100"
|
||||
leave-to="opacity-0 scale-95"
|
||||
>
|
||||
<DialogPanel
|
||||
class="w-full transform overflow-hidden bg-white text-left align-middle shadow-xl transition-all py-4"
|
||||
:class="{
|
||||
'h-screen': fullscreen,
|
||||
'max-w-[min(85vw,1200px)] rounded-lg': !fullscreen,
|
||||
}"
|
||||
:style="!fullscreen ? { maxHeight } : {}"
|
||||
>
|
||||
<slot />
|
||||
</DialogPanel>
|
||||
</TransitionChild>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</TransitionRoot>
|
||||
|
||||
</template>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { DialogDescription } from '@headlessui/vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogDescription class="px-4 py-3 text-sm text-gray-800">
|
||||
<slot />
|
||||
</DialogDescription>
|
||||
</template>
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
// import { ref } from 'vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="px-4 py-3">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { DialogTitle } from '@headlessui/vue'
|
||||
|
||||
interface Props {
|
||||
dismissable?: boolean
|
||||
titleClass?: string
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const api = inject('modal')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTitle
|
||||
as="div"
|
||||
class="flex gap-2 justify-between items-center px-4 pt-3"
|
||||
>
|
||||
<h3
|
||||
class="text-lg font-medium leading-6 text-gray-900"
|
||||
:class="titleClass"
|
||||
>
|
||||
<slot />
|
||||
</h3>
|
||||
<slot v-if="dismissable" name="dismissable">
|
||||
<button
|
||||
class="text-2xl text-gray-500 appearance-none px-2 -mr-2"
|
||||
@click="api.close"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</slot>
|
||||
</DialogTitle>
|
||||
</template>
|
||||
111
pages/events.vue
111
pages/events.vue
|
|
@ -1,111 +0,0 @@
|
|||
<template>
|
||||
<div class="bg-zinc-100 rounded-lg m-5 grid grid-cols-2 gap-10 divide-x divide-solid divide-black py-4 mb-10">
|
||||
|
||||
<div class="px-5">
|
||||
<p class="text-lg">performances</p>
|
||||
|
||||
<div v-for="(item, index) in events">
|
||||
<Collapsible title='placeholder' :modelValue='index <= 10' class="leading-tight py-2 ml-3 text-sm">
|
||||
<template v-slot:title>
|
||||
<div class="gap-1 w-[95%] px-2">
|
||||
<div>
|
||||
{{ item.formatted_date }}: {{item.venue.city}}, {{item.venue.state}}
|
||||
<div class="ml-4 text-[#7F7F7F]">
|
||||
{{ item.venue.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:content>
|
||||
<div v-for="performance in item.program">
|
||||
<div class="italic text-sm ml-16 pt-1">{{performance.work}}</div>
|
||||
<div v-if="performance.ensemble" class="ml-20">
|
||||
{{ performance.ensemble }}
|
||||
</div>
|
||||
<div v-for="performer in performance.performers" class="ml-20">
|
||||
{{ performer.name }}<span v-if="performer.instrument_tags?.length"> - </span>
|
||||
<span v-for="(instrument, index) in performer.instrument_tags">
|
||||
<span v-if="index !== 0">, </span>
|
||||
{{ instrument }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Collapsible>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5">
|
||||
<p class="text-lg">lectures</p>
|
||||
|
||||
<div v-for="yearGroup in lecturesByYear" :key="yearGroup.year">
|
||||
<p class="text-sm font-semibold mt-4 text-[#7F7F7F]">{{ yearGroup.year }}</p>
|
||||
<div class="leading-tight py-2 ml-3 text-sm" v-for="item in yearGroup.talks">
|
||||
<div class="gap-1">
|
||||
<div>
|
||||
{{item.location}}
|
||||
<div v-for="talk in item.talks" class="ml-4 text-[#7F7F7F]">
|
||||
{{ talk.title }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
const { data: events } = await useFetch('/api/events', {
|
||||
transform: (events) => {
|
||||
for (const event of events) {
|
||||
let date = new Date(event.start_date)
|
||||
event.formatted_date = ("0" + date.getDate()).slice(-2) + "." + ("0" + (date.getMonth() + 1)).slice(-2) + "." + date.getFullYear()
|
||||
}
|
||||
return events.sort((a,b) => new Date(b.start_date) - new Date(a.start_date))
|
||||
}
|
||||
})
|
||||
|
||||
const { data: talksData } = await useFetch('/api/talks', {
|
||||
transform: (events) => {
|
||||
for (const event of events) {
|
||||
let date = new Date(event.date)
|
||||
event.date = date
|
||||
event.formatted_date = ("0" + (date.getMonth() + 1)).slice(-2) + "." + date.getFullYear()
|
||||
if(typeof event.title === 'string' || event.title instanceof String) {event.talks = [{'title': event.title}]
|
||||
} else {
|
||||
let talks = []
|
||||
for(const talk of event.title){
|
||||
talks.push({"title": talk})
|
||||
}
|
||||
event.talks = talks
|
||||
}
|
||||
}
|
||||
return events.sort((a,b) => new Date(b.date) - new Date(a.date))
|
||||
}
|
||||
})
|
||||
|
||||
const lecturesByYear = computed(() => {
|
||||
if (!talksData.value) return []
|
||||
|
||||
const byYear = {}
|
||||
for (const talk of talksData.value) {
|
||||
const year = talk.date ? talk.date.getFullYear() : 'Unknown'
|
||||
if (!byYear[year]) byYear[year] = []
|
||||
byYear[year].push(talk)
|
||||
}
|
||||
|
||||
return Object.keys(byYear)
|
||||
.sort((a, b) => b - a)
|
||||
.map(year => {
|
||||
const talks = byYear[year].sort((a, b) => new Date(b.date) - new Date(a.date))
|
||||
return { year, talks }
|
||||
})
|
||||
})
|
||||
|
||||
useHead({
|
||||
titleTemplate: 'Michael Winter - Events - Performances and Lectures'
|
||||
})
|
||||
</script>
|
||||
|
||||
Loading…
Reference in a new issue