| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- <template lang="pug">
- .page--list.f-col.between
- article.f-grow
-
- header.center.t-up
- .title.f-row
- h3 {{ type }}s
- span(v-if="sortBy")
- h3 {{ sortBy.replace('-', ' ') }}
-
- h3(v-if="!loaded") loading...
- .content(
- v-else-if="allPagesLoaded && type && allPages[type]"
- v-html="allPages[type].content"
- )
-
- ul.posts.f-col(v-if="posts && loaded" :class="{ 'is-grid': grid }")
- template(v-for="(post, i) in posts" :key="post.slug")
- li.post.shadow(v-if="!post.inbetween" )
- card(:content="post" :type="type" :wide="isWide")
- li.post.shadow.inbetween.t-up.f-row.w-max(v-else-if="post.inbetween" :id="post.slug")
- p {{ post.slug }}
-
- //- Important: Do NOT remove this! Required for intersection observer
- footer
- p(v-if="loadingFetched") loading more {{ type }}...
-
- sidebar(v-if="sidebar" :type="`${type}`" layout="list")
- </template>
-
- <script>
- import featuredImage from '@/components/featured-image'
- import card from '@/components/card'
- import sidebar from '@/components/sidebars/sidebar'
- import { postTypeGetters, scrollTop, heroUtils } from './mixin-post-types'
- import { postTypes, sortTypes, convertTitleCase } from '@/utils/helpers'
-
- const TIMEOUT = 1
- const INTERSECT_SELECTOR = '.page--list > article footer'
- const wideTypes = ['event', 'exhibition', 'guide', 'post', 'publication']
- const gridTypes = ['episode', 'artist', 'object', 'short']
- const sansSidebarTypes = ['episode']
-
- export default {
- components: { sidebar, featuredImage, card },
- mixins: [postTypeGetters, scrollTop, heroUtils],
- data() {
- return {
- page: 0,
- perPage: 21,
- keepFetching: true,
- loadingFetched: false,
- observer: null
- }
- },
- computed: {
- type() {
- return postTypes.includes(this.$route.params.type) ? this.$route.params.type : 'post'
- },
- sortBy() {
- return this.$route.params.sortBy
- },
- grid() {
- return gridTypes.includes(this.type)
- },
- isWide() {
- return wideTypes.includes(this.type)
- },
- sidebar() {
- return !sansSidebarTypes.includes(this.type)
- },
- pType() {
- if(!this.type) return
- return `${convertTitleCase(this.type)}s`
- },
- loaded() {
- if (!this.pType) return
- return this[`all${this.pType}Loaded`]
- },
- posts() {
- if (!this.pType) return
- return this[`all${this.pType}`]
- },
- shouldLoadAllAtOnce() {
- return this.type == 'episode' ||
- this.type == 'artist' && this.sortBy == sortTypes.material ||
- this.type == 'artist' && this.sortBy == sortTypes.episode
- }
- },
- methods: {
- async loadMorePosts(shouldClear) {
- if(!this.keepFetching) return console.warn('Nothing left to fetch...')
-
- const getPosts = async (params, dispatchType) => {
- if(!this.type) throw `post type: ${this.type} not found...`
- console.log(`Getting more ${this.type} posts`)
- // We always grab all pages on hero init so no need to do it here
- return this.pType && this.type != 'page' ? await this.$store.dispatch(
- `get${dispatchType}${this.pType}`,
- { sortType: this.sortBy, params }
- ) : []
- }
-
- if(shouldClear) {
- this.$store.commit(`CLEAR_${this.pType.toUpperCase()}`)
-
- // Clear any state needed to track title inbetweens
- const hasInbetweens = ['artist']
- if(hasInbetweens.includes(this.type)) {
- this.$store.commit(`CLEAR_${this.pType.toUpperCase()}_SEEN`)
- }
- }
-
- try {
- this.loadingFetched = true
- this.page++
- const res = await getPosts(
- {
- limit: this.shouldLoadAllAtOnce ? -1 : this.perPage,
- page: this.page
- },
- this.shouldLoadAllAtOnce ? 'All' : 'More'
- )
- this.loadingFetched = false
-
- // Stop trying to load more posts
- if(res && !res.length || this.shouldLoadAllAtOnce) {
- this.keepFetching = false
- if(!res.length) console.warn(`Empty response for ${this.type}:`, res.length)
- if(this.shouldLoadAllAtOnce) console.warn(`Fetched all responses for ${this.type}:`, res.length)
- }
- } catch (err) { console.error(err) }
- },
- async getPage(type) {
- await this._getAllIfNotLoaded('page', this.$store)
- if(!this.allPages) throw 'no pages in state'
- const page = this.allPages.filter(page => page.slug == `${type}s`)[0]
- if(!page) throw `No page: ${type} found. Cannot set hero.`
- return page
- },
- // _setHeroInfo(post) {} from mixin
- // _clearHero(store) {} from mixin
- async checkAndSetHero(type) {
- this._clearHero(this.$store)
- try {
- const page = await this.getPage(type)
- // We always set a hero no matter what
- // Because the hero component will deal
- // with how to render based on hero.url
- this.$store.commit('SET_HERO', this._setHeroInfo(page))
- } catch (err) { console.error(err) }
- },
- setIntersectionLoader() {
- // KeepFetching is UNSET for certain post types and sort types in `loadMorePosts()`
- if(!this.keepFetching) return console.warn('Cannot setup intersection handler keepFetching is set false')
-
- // Always unset before setting the intersection loader
- this.unsetIntersectionLoader()
-
- // console.warn('Setting interesection loader...')
- this.observer = new IntersectionObserver(entries => {
- entries.forEach(entry => {
- if (!entry.isIntersecting || this.loadingFetched) return
- setTimeout(this.loadMorePosts, TIMEOUT)
- })
- }, { threshold: 0.80 })
- this.observer.observe(document.querySelector(INTERSECT_SELECTOR))
- },
- unsetIntersectionLoader() {
- const footerEl = document.querySelector(INTERSECT_SELECTOR)
- try {
- if(!footerEl) throw `Cannot unset intersection handler missing el: ${footerEl}`
- if(!this.observer) return
- this.observer.unobserve(footerEl)
- this.observer.disconnect()
- } catch (error) { console.error(error) }
- },
- initPostList() {
- this.page = 0
- this.keepFetching = true
-
- this.checkAndSetHero(this.type)
-
- // Clear any preloaded posts (from home, etc.)
- this.loadMorePosts(true)
- this.setIntersectionLoader()
- }
- },
- watch: {
- // This only fires navigating from a list page, to another list page
- type(newType) {
- if(!postTypes.includes(newType)) return console.warn('Type not valid...')
-
- // Ignore types with presorts so the sortBy watcher can handle them
- const ignore = ['event', 'exhibition', 'artist']
- if(ignore.includes(newType)) return
- this.initPostList()
- },
- sortBy(newSort) {
- if(!Object.values(sortTypes).includes(newSort)) return
- this.initPostList()
- }
- },
- mounted() {
- // This only fires navigating from a non-list page > list page
- this.initPostList()
- },
- beforeUnmount() {
- this.unsetIntersectionLoader()
- }
- }
- </script>
-
- <style lang="postcss">
- // prettier-ignore
- @import '../sss/variables.sss'
- @import '../sss/theme.sss'
- .page--list
- /* Puts the aside bar on top */
- flex-direction: column-reverse !important
- article
- > header
- padding: 1em
- > h1
- margin: 0
- > .content
- padding: 0
- width: 100%
- .title.f-row
- /* flex-direction: column */
- justify-content: flex-start
-
- > footer
- padding: $ms-0
-
- /* posts not grid list */
- .posts
- list-style: none
- grid-gap: $ms--2
- .post
- width: 100%
- &.inbetween
- grid-column: span 2
- background: white
- padding: 0.3em 0
- font-size: 1.3em
- > p
- margin: 0
-
- /* posts in grid list */
- .posts.is-grid
- width: 100%
- min-width: 185px
- display: grid
- grid-template-columns: repeat(2, 1fr)
- align-items: start
- /* This is important for how the grid lines up to the page */
- justify-content: right
- .post
- min-width: 177px
- img
- width: 100%
-
- @media (min-width: $medium)
- .page--list
- &.f-col
- flex-direction: row !important
- > article
- margin: 0 $ms--2 0 0
- header
- .title.f-row
- flex-direction: row
- justify-content: center
-
- .posts.is-grid
- grid-template-columns: repeat(3, 1fr)
- .post.inbetween
- grid-column: span 3
- </style>
|