1import LoadingSpinner from '../VueComponents/LoadingSpinner.js'; 2import ResourceCarousel from './ResourceCarousel.js'; 3import weekendMessagesEndpoints from '../utils/weekendMessagesEndpoints.js'; 4 5export default { 6 name: 'SeriesCarousel', 7 components: { 8 LoadingSpinner, 9 ResourceCarousel, 10 }, 11 props: { 12 healthyChurchDomain: { 13 type: String, 14 required: true, 15 }, 16 useLegacy: { 17 type: Boolean, 18 required: false, 19 default: false, 20 }, 21 // Controlled by the parent (single source of truth). null = nothing selected. 22 // Holds the Sardius seriesAssetId string (matches series.seriesAssetId). 23 selectedSeriesId: { 24 type: String, 25 required: false, 26 default: null, 27 }, 28 }, 29 emits: ['series-selected'], 30 data() { 31 return { 32 isLoading: true, 33 seriesList: [], 34 }; 35 }, 36 watch: { 37 // Fires whatever moved the selection â a card click, or the series dropdown on 38 // either filter surface (desktop FilterCard / FilterPanelMobile). Selections made 39 // from outside the strip are the ones that matter: the matching card is usually 40 // scrolled out of view. 41 selectedSeriesId() { 42 this.$nextTick(this.scrollSelectedIntoView); 43 }, 44 }, 45 mounted() { 46 this.fetchSeries(); 47 }, 48 methods: { 49 fetchSeries() { 50 this.isLoading = true; 51 const url = weekendMessagesEndpoints.getSeriesList(this.healthyChurchDomain, this.useLegacy); 52 window.sbDebug && console.log('[SeriesCarousel] getSeriesList:', url); 53 axios 54 .get(url) 55 .then((response) => { 56 this.seriesList = response.data?.results ?? []; 57 }) 58 .catch((error) => { 59 console.error(error); 60 }) 61 .finally(() => { 62 this.isLoading = false; 63 // A selection can predate the list (deep link / filter set while loading). 64 this.$nextTick(this.scrollSelectedIntoView); 65 }); 66 }, 67 scrollSelectedIntoView() { 68 if (this.selectedSeriesId === null) { 69 return; 70 } 71 const index = this.seriesList.findIndex((series) => series.seriesAssetId === this.selectedSeriesId); 72 if (index === -1) { 73 return; 74 } 75 this.$refs.carousel?.scrollItemIntoView(index); 76 }, 77 selectSeries(series) { 78 // Toggle: re-clicking the selected series clears the selection. The parent 79 // owns the state, so we only emit intent â the prop flows back down. 80 const isDeselecting = this.selectedSeriesId === series.seriesAssetId; 81 this.$emit('series-selected', isDeselecting ? null : series); 82 }, 83 isDimmed(series) { 84 // Dim every card except the selected one, only while something is selected. 85 return this.selectedSeriesId !== null && series.seriesAssetId !== this.selectedSeriesId; 86 }, 87 isSelected(series) { 88 return this.selectedSeriesId !== null && series.seriesAssetId === this.selectedSeriesId; 89 }, 90 }, 91 template: ` 92 <section class="series-carousel"> 93 <h3 class="series-carousel__heading">Series</h3> 94 <LoadingSpinner v-show="isLoading" /> 95 <ResourceCarousel 96 ref="carousel" 97 v-show="!isLoading" 98 :items="seriesList" 99 itemKey="id" 100 prevLabel="Previous series" 101 nextLabel="Next series" 102 > 103 <template #item="{ item }"> 104 <button 105 type="button" 106 class="series-card" 107 :class="{ 'is-dimmed': isDimmed(item), 'is-selected': isSelected(item) }" 108 :aria-label="'Filter messages by ' + item.name" 109 :aria-pressed="isSelected(item)" 110 @click="selectSeries(item)" 111 > 112 <img 113 class="series-card__image" 114 :src="item.heroImage" 115 :alt="item.name" 116 loading="lazy" 117 /> 118 </button> 119 </template> 120 </ResourceCarousel> 121 </section> 122 `, 123};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.