| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- <template lang="pug">
- .page--single.f-col.between
- gallery(v-if="activeGalleryIndex >= 0" :activeImageIndex="activeImageIndex" :images="imagesInGallery" @close="closeGallery")
-
- article.w-max(v-if="!singlePost || loading")
- header
- p loading...
- article(v-else).w-max.f-grow.shadow
- header
- //- breadcrumb links at top of page, needs link routing
- breadcrumb(:type="type" :post="singlePost")
-
- h1.t-b {{ singlePost.title }}
- //- p(v-if="singlePost.categories") categories: {{ singlePost.categories }}
- //- p(v-if="singlePost.type") type: {{ singlePost.type }}
- //- p(v-if="singlePost.subtypes") subtypes: {{ singlePost.subtypes }}
-
- .date-info(v-if="['exhibition', 'event'].includes(type)")
- p start: {{ dateFrom(singlePost.start, type == 'event') }}
- p end: {{ dateFrom(singlePost.end, type == 'event') }}
-
- //- WP main content
- section.content(v-html="singlePost.content")
-
- //- related artists section for episodes
- section(v-if="type === 'episode' && post" :post="post")
- h2.t-up featured in this episode
- ul
- li.f-row.between(v-for="artist in p2pPostsByType['artist']")
- card(:content="artist" type="artist" :wide="true" :hide-type="true")
-
- credits(v-if="type === 'episode' && singlePost" :post="singlePost")
-
- //- end of article icon
- footer.f-col
- img(src="../star.svg")
-
- sidebar(v-if="sidebar" :type="`${type}`" layout="single" :related="p2pPostsByType")
- </template>
-
- <script>
- import { mapGetters, mapState } from 'vuex'
- import card from '@/components/card.vue'
- import sidebar from '@/components/sidebars/sidebar'
- import gallery from '@/components/gallery/'
- import credits from '@/components/credits'
- import breadcrumb from '@/components/breadcrumb'
-
- import { postTypeGetters, scrollTop, heroUtils } from './mixin-post-types'
-
- import { sortTypes, convertTitleCase, dePluralize, typeFromRoute } from '@/utils/helpers'
-
- const TIMEOUT = 1
-
- export default {
- components: { sidebar, gallery, credits, card, breadcrumb },
- props: {
- sidebar: { type: Boolean },
- id: { type: Number },
- },
- mixins: [postTypeGetters, scrollTop, heroUtils],
- data() {
- return {
- // Gallery control
- activeGalleryIndex: -1,
- activeImageID: -1,
- loading: true
- }
- },
- computed: {
- type() {
- // Checks for type and fixes Episodes route edge case
- return typeFromRoute(this.$route)
- },
- /**
- * We get the actual post data using the slug
- * Careful with name collisions with vuex helpers
- */
- singlePost() {
- if (!this[this.type]) return
- const type = dePluralize(this.type)
-
- // State not a getter!
- const singleOfTypeFromState =
- this[this.type][`single${convertTitleCase(type)}`]
-
- if (!singleOfTypeFromState) return
-
- return singleOfTypeFromState
- },
-
- idsForGallery() {
- if (!this.singlePost || this.activeGalleryIndex < 0) return []
- return this.singlePost.galleries[this.activeGalleryIndex].ids
- },
- /**
- * We need a convenient way to get all the images
- * broken down by gallery. We use the active gallery
- * image IDs to create a map. We match the ID to the
- * image size and url information returned by singlePost.attached
- */
- imagesInGallery() {
- if (!this.activeGalleryIndex < 0) return {}
-
- return this.idsForGallery.reduce((imageMap, id) => {
- imageMap[id] = this.singlePost.attached[parseInt(id)]
- return imageMap
- }, {})
- },
- activeImageIndex() {
- return Object.keys(this.imagesInGallery).indexOf(
- this.activeImageID.toString(),
- )
- },
- p2pPostsByType() {
- return this.singlePost
- ? Object.values(this.singlePost.relatedto).reduce(
- (byType, relatedPost) => {
- if (!byType[relatedPost.type])
- byType[relatedPost.type] = []
- byType[relatedPost.type].push(relatedPost)
- return byType
- },
- {},
- )
- : {}
- },
- },
- methods: {
- /**
- * We set the active gallery to the index.
- * Everything kicks off when activeGallery
- * is set. We also need to set the activeImageID
- * to the image clicked
- * @param {string} imageInfo
- */
- openGallery(imageInfo) {
- const byIndex = this.singlePost.galleries.reduce(
- (byIndex, gallery, index) => {
- byIndex[index] = gallery.ids
- return byIndex
- },
- {},
- )
- let matchingIndex = 0
- Object.keys(byIndex).forEach(galleryIndex => {
- if (
- byIndex[galleryIndex].includes(
- parseInt(imageInfo.dataset.id),
- )
- )
- matchingIndex = galleryIndex
- })
- this.activeGalleryIndex = matchingIndex
- this.activeImageID = imageInfo.dataset.id
- ? parseInt(imageInfo.dataset.id)
- : parseInt(imageInfo.className.split('-').pop())
- },
- closeGallery() {
- this.activeGalleryIndex = this.activeImageID = -1
- },
- // _setHeroInfo(post) {} from mixin
- // _clearHero(store) {} from mixin
- /**
- * Everytime the posts object changes
- * we use this to set a new HERO
- * in vuex
- * @param {object} posts
- */
- checkAndSetHero(post) {
- this._clearHero(this.$store)
- if (!post) return console.warn(`no post found`)
- this.$store.commit('SET_HERO', this._setHeroInfo(post))
- },
-
- /**
- * Date Object from unix strings from db
- */
- dateFrom(unix, includeTime) {
- const d = new Date(parseInt(unix) * 1000)
- return includeTime ? d.toLocaleString('en-US', { timeZone: 'UTC' }) : d.toLocaleDateString('en-US')
- },
-
- async loadPostData() {
- /**
- * Conditionally load based on post type
- * which is derived from the route
- */
- // modules are NOT plural because module key
- if (!this.$store.state[this.type]) return
- let allPostsOfType = this.$store.state[this.type].all
-
- /**
- * Load posts if they're not already in state
- */
- // Find the single post from api if it's not already in state
- // Then add it to our list
- let singlePostData = allPostsOfType.filter(
- post => post.slug == this.$route.params.slug,
- )[0]
-
- // Look if it exists before you try and load everything!
- if(!singlePostData) {
- const res = await this.$store.dispatch(`getAll${convertTitleCase(this.type)}s`, { sortType: null, params: null})
- allPostsOfType = res
- singlePostData = allPostsOfType.filter(
- post => post.slug == this.$route.params.slug,
- )[0]
- }
-
- if (!singlePostData) return console.error('could not get single post data...')
-
- // NOT plural
- this.checkAndSetHero(singlePostData)
- await this.$store.dispatch(
- `getSingle${convertTitleCase(this.type)}`,
- singlePostData.id,
- )
- this.loading = false
- },
- },
- watch: {
- $route(to, from) {
- // Only load post data when
- // navigating TO a single page
- const path = to.fullPath.split('/').filter(p => p)
- const hasSort = path.filter(fragment => Object.values(sortTypes).includes(fragment))
- console.log('hasSort :', hasSort)
- console.log('path :', path)
- if (path.length < 1 && hasSort.length < 1) {
- this._clearHero(this.$store)
- this.loadPostData()
- }
- },
- },
- created() {
- this.loadPostData()
- },
- }
- </script>
-
- <style lang="postcss">
- // prettier-ignore
- @import '../sss/variables.sss'
- @import '../sss/theme.sss'
- .page--single
- article
- background-color: white
- padding: $ms-0
- h1
- color: $cia_black
- /* font-weight: 800 */
- /* padding: $ms--3 0 */
- > ul
- /* grid-gap: $ms-0 */
- list-style: none
- /* change to a 1/3 width of the article*/
- img.feature
- width: 20em
- li
- /* wp-block-embed youtube link */
- .wp-block-embed, .is-type-video
- position: relative
- width: 100%
- padding-bottom: 56.25%
- &__wrapper
- display: contents
- /* TBD if kept */
- figcaption
- position: absolute
- top: 39%
- margin: 0 3em 0
- color: aqua
- backdrop-filter: brightness(0.5)
-
- /* iframe container 16:9 */
- .iframe-container
- position: relative
- width: 100%
- padding-bottom: 56.25%
-
- /* iframe container portrait */
- .iframe-container-v
- position: relative
- width: 100%
- height: 100%
- padding-bottom: 125%
-
- iframe
- position: absolute
- top: 0px
- left: 0px
- width: 100%
- height: 100%
- /* separator styles */
- * hr
- margin: $ms-2 auto
- &.is-style
- &-default
- height: 1px
- width: 15vw
- &-wide
- height: 3px
- width: 50vw
- &-dots::before
- outline-style: none
- font-weight: bolder
- letter-spacing: $ms-8
- padding-left: $ms-8
- /* margin to indent marker */
- li
- margin: 0 0 $ms--2 $ms-4
-
- breadcrumb
- h5
- /* color: yellow */
- color: $cia_red
- /* font-weight: 400 */
- /* padding: $ms--6 0 */
-
- //- end of article icon
- footer
- padding: $ms-6 0
- img
- height: $ms-3
- width: $ms-3
-
-
- @media (min-width: $medium)
- .page--single.f-col
- flex-direction: row
- </style>
|