| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335 |
- <template lang="pug">
- .page--single.f-col.between
- gallery(v-if="activeGalleryIndex >= 0" :activeImageIndex="activeImageIndex" :images="imagesInGallery" @close="closeGallery")
-
- article.w-max.f-grow.shadow(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").related-artists
- 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(:type="`${type}`" layout="single" :related="p2pPostsByType")
- </template>
-
- <script>
- 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 { postTypes, convertTitleCase, formatDate } from '@/utils/helpers'
-
- const TIMEOUT = 1
-
- export default {
- components: { sidebar, gallery, credits, card, breadcrumb },
- mixins: [postTypeGetters, scrollTop, heroUtils],
- data() {
- return {
- // Gallery control
- activeGalleryIndex: -1,
- activeImageID: -1,
- loading: true
- }
- },
- computed: {
- type() {
- return postTypes.includes(this.$route.params.type) ? this.$route.params.type : 'post'
- },
- slug() {
- return this.$route.params.slug
- },
- /**
- * We get the actual post data using the slug
- * Careful with name collisions with vuex helpers
- */
- singlePost() {
- if (!this[this.type]) return
-
- // State not a getter!
- const singleOfTypeFromState =
- this[this.type][`single${convertTitleCase(this.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 && this.singlePost.relatedto
- ? 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 post object changes
- * we use this to set a new HERO
- * in vuex
- * @param {object} post
- */
- checkAndSetHero(post) {
- this._clearHero(this.$store)
- if (!post) throw `No post found. Cannot set hero.`
- this.$store.commit('SET_HERO', this._setHeroInfo(post))
- },
-
- /**
- * Date Object from unix strings from db
- */
- dateFrom: (unix, includeTime) => formatDate(unix, includeTime),
-
- async loadPostData() {
- this.loading = true
- /**
- * 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
- const allPostsOfTypeInStore = 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 = allPostsOfTypeInStore.filter(
- post => post.slug == this.slug,
- )[0]
-
- // Look if it exists before you try and load everything!
- if (!singlePostData) {
- console.warn('Could not find single post in store; Fetching everything...')
- const res = await this.$store.dispatch(
- `getAll${convertTitleCase(this.type)}s`,
- { sortType: null, params: null }
- )
- singlePostData = res.filter(
- post => post.slug == this.slug,
- )[0]
- }
-
- /**
- * At the point we MUST have singlePostData
- */
- try {
- this.checkAndSetHero(singlePostData)
- await this.$store.dispatch(
- `getSingle${convertTitleCase(this.type)}`,
- singlePostData.id,
- )
- } catch (err) {
- console.error(err)
- }
- this.loading = false
- },
- },
- watch: {
- slug(newSlug, oldSlug) {
- // ONLY load post data when navigating TO a single page
- // OR when navigating TO a single page from a single page
- if(newSlug && !oldSlug || newSlug && oldSlug) {
- 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
- margin: 0 0 $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%
- margin-bottom: $ms-7
- &__wrapper
- display: contents
- /* TBD if kept- edit ot test */
- figcaption
- position: absolute
- top: 100%
-
- [class^="iframe-container"]
- position: relative
- width: 100%
- /* iframe container 16:9 */
- .iframe-container
- padding-bottom: 56.25%
-
- /* iframe container portrait */
- .iframe-container-v
- 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
-
- .related-artists
- ul li
- margin: 0 0 $ms-0 0
- .card
- padding: 0 0 $ms--2
- min-height: auto
-
- 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
- > article
- margin: 0 0.65em 0 0
- &.f-col
- flex-direction: row
- </style>
|