Skip to content

Commit b05fa88

Browse files
committed
Loading indicator
Doesn't depend on Vue's $loading indicator (which is a guess) Can handle any number of independent API requests Gives feedback when it's finished, if the parent wants to listen Related to Issue #47
1 parent 21a2406 commit b05fa88

File tree

7 files changed

+276
-130
lines changed

7 files changed

+276
-130
lines changed

components/footer/Patrons.vue

-1
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,3 @@ $logo-height: 40px;
5353
}
5454
}
5555
</i18n>
56-

components/homepage/Marathons.vue

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
</template>
3838
</template>
3939
</div>
40+
<div class="is-centered">
41+
<WidgetLoading :while="[ homepageMarathons ]" />
42+
</div>
4043
</div>
4144
</template>
4245

components/marathon/Description.vue

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
<template>
2-
<ElementMarkdown v-if="description" :markdown="description" />
2+
<div>
3+
<ElementMarkdown v-if="description" :markdown="description" />
4+
<div class="is-centered">
5+
<WidgetLoading :while="[ marathon ]" />
6+
</div>
7+
</div>
38
</template>
49

510
<script lang="ts">
611
import Vue from 'vue';
712
import { mapActions } from 'vuex';
8-
import { MarathonState } from '~/types/api/marathon';
13+
import { FullMarathon, MarathonState } from '~/types/api/marathon';
914
1015
export default Vue.extend({
1116
props: {
@@ -20,8 +25,11 @@ export default Vue.extend({
2025
]);
2126
},
2227
computed: {
28+
marathon(): FullMarathon|undefined {
29+
return (this.$store.state.api.marathon as MarathonState).marathons[this.marathonId];
30+
},
2331
description(): string|undefined {
24-
return (this.$store.state.api.marathon as MarathonState).marathons[this.marathonId]?.description;
32+
return this.marathon?.description;
2533
},
2634
},
2735
methods: {

components/marathon/schedule/Current.vue

+38-27
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,41 @@
11
<template>
2-
<div v-if="ticker" class="message" :class="messageClass">
3-
<div class="message-header">
4-
<ElementLink :to="linkedRun">
5-
{{ $t(messageHeaderTitle, messageHeaderArgs) }}
6-
</ElementLink>
2+
<div v-show="isLoading || ticker">
3+
<div v-if="ticker" class="message" :class="messageClass">
4+
<div class="message-header">
5+
<ElementLink :to="linkedRun">
6+
{{ $t(messageHeaderTitle, messageHeaderArgs) }}
7+
</ElementLink>
8+
</div>
9+
<div class="message-body">
10+
<p class="run-info">
11+
<span v-if="ticker.setupBlock">
12+
{{ (ticker.setupBlockText || $t('marathon.schedule.setupBlock')) }}
13+
</span>
14+
<span v-if="ticker.gameName">
15+
{{ ticker.gameName }}
16+
</span>
17+
<span v-if="ticker.categoryName">
18+
{{ ticker.categoryName }}
19+
</span>
20+
<span v-if="ticker.console">
21+
{{ ticker.console }}
22+
</span>
23+
</p>
24+
<p class="runner-info">
25+
<span v-for="runner in ticker.runners" :key="runner.id">{{ runner.username }}</span>
26+
</p>
27+
</div>
728
</div>
8-
9-
<div class="message-body">
10-
<p class="run-info">
11-
<span v-if="ticker.setupBlock">
12-
{{ (ticker.setupBlockText || $t('marathon.schedule.setupBlock')) }}
13-
</span>
14-
<span v-if="ticker.gameName">
15-
{{ ticker.gameName }}
16-
</span>
17-
<span v-if="ticker.categoryName">
18-
{{ ticker.categoryName }}
19-
</span>
20-
<span v-if="ticker.console">
21-
{{ ticker.console }}
22-
</span>
23-
</p>
24-
<p class="runner-info">
25-
<span v-for="runner in ticker.runners" :key="runner.id">{{ runner.username }}</span>
26-
</p>
29+
<div class="is-centered">
30+
<WidgetLoading :while="[ tickers ]" @done="isLoading = false" />
2731
</div>
2832
</div>
2933
</template>
3034

3135
<script lang="ts">
3236
import Vue from 'vue';
3337
import { mapActions } from 'vuex';
34-
import { ScheduleLine, ScheduleState } from '~/types/api/schedule';
38+
import { ScheduleLine, ScheduleState, ScheduleTicker } from '~/types/api/schedule';
3539
3640
export default Vue.extend({
3741
props: {
@@ -44,6 +48,11 @@ export default Vue.extend({
4448
default: '',
4549
},
4650
},
51+
data() {
52+
return {
53+
isLoading: true,
54+
};
55+
},
4756
async fetch(): Promise<void> {
4857
await Promise.allSettled([
4958
this.getScheduleTicker(this.marathonId),
@@ -62,9 +71,11 @@ export default Vue.extend({
6271
messageHeaderArgs(): { duration?: string } {
6372
return this.isNext ? { duration: this.$temporal.distance.format(this.ticker?.date ?? new Date()) } : { };
6473
},
74+
tickers(): ScheduleTicker|undefined {
75+
return (this.$store.state.api.schedule as ScheduleState).tickers[this.marathonId];
76+
},
6577
ticker(): ScheduleLine|undefined {
66-
const tickers = (this.$store.state.api.schedule as ScheduleState).tickers[this.marathonId];
67-
return this.isNext ? tickers?.next : tickers?.current;
78+
return this.isNext ? this.tickers?.next : this.tickers?.current;
6879
},
6980
},
7081
methods: {

components/marathon/schedule/List.vue

+90-86
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,100 @@
11
<template>
2-
<div class="schedule-container">
3-
<!-- Ad -->
4-
<AdsByGoogle ad-slot="5905320802" ad-format="" class="is-advertisement" />
5-
<!-- Header -->
6-
<span class="notification is-header expandable" />
7-
<span class="notification is-header time">
8-
{{ $t('marathon.schedule.table.time') }}
9-
</span>
10-
<span class="notification is-header runners">
11-
{{ $t('marathon.schedule.table.runner') }}
12-
</span>
13-
<span class="notification is-header game">
14-
{{ $t('marathon.schedule.table.game') }}
15-
</span>
16-
<span class="notification is-header category">
17-
{{ $t('marathon.schedule.table.category') }}
18-
</span>
19-
<span class="notification is-header type">
20-
{{ $t('marathon.schedule.table.type') }}
21-
</span>
22-
<span class="notification is-header console">
23-
{{ $t('marathon.schedule.table.console') }}
24-
</span>
25-
<span class="notification is-header estimate">
26-
{{ $t('marathon.schedule.table.estimate') }}
27-
</span>
28-
<span class="notification is-header setup">
29-
{{ $t('marathon.schedule.table.setup') }}
30-
</span>
31-
<!-- Main Schedule Loop -->
32-
<template v-if="runs">
33-
<template v-for="(run, index) in runs">
34-
<!-- Ad -->
35-
<ClientOnly :key="'wrapper-advertisement' + index">
36-
<AdsByGoogle v-if="advertisementIndices.includes(index)" :key="'advertisement' + index" ad-slot="5905320802" ad-format="" class="is-advertisement" />
37-
<div v-show="shouldShowDay(index) && index !== 0" :key="'not-advertisement' + index" class="is-spacer" />
38-
</ClientOnly>
39-
40-
<div v-show="shouldShowDay(index)" :key="'day' + index" class="day notification is-info">
41-
{{ $d(new Date(run.date), 'longDate') }}
42-
</div>
43-
44-
<span :id="getId(run)" :key="'expandable' + index" class="notification is-expandable expandable" :class="getRowParity(index, run)" @click="expand(run)">
45-
<FontAwesomeIcon :icon="[ 'fas', expanded.has(run.id) ? 'caret-down' : 'caret-right' ]" />
46-
</span>
47-
<span :id="'run-' + run.id" :key="'time' + index" class="notification is-expandable time" :class="getRowParity(index, run)" @click="expand(run)">
48-
{{ $d(new Date(run.date), 'shortTime') }}
49-
</span>
50-
51-
<span v-if="run.setupBlock" :key="'setupText' + index" class="notification is-expandable setup-text" :class="getRowParity(index, run)" @click="expand(run)">
52-
{{ (run.setupBlockText || $t('marathon.schedule.setupBlock')) }}
53-
</span>
54-
<template v-else>
55-
<span :key="'runners' + index" class="notification is-expandable runners" :class="getRowParity(index, run)" @click="expand(run)">
56-
<p v-for="runner in run.runners" :key="'runners' + index + 'runner' + runner.id">
57-
{{ runner.username }}
58-
</p>
2+
<div>
3+
<div class="schedule-container">
4+
<!-- Ad -->
5+
<AdsByGoogle ad-slot="5905320802" ad-format="" class="is-advertisement" />
6+
<!-- Header -->
7+
<span class="notification is-header expandable" />
8+
<span class="notification is-header time">
9+
{{ $t('marathon.schedule.table.time') }}
10+
</span>
11+
<span class="notification is-header runners">
12+
{{ $t('marathon.schedule.table.runner') }}
13+
</span>
14+
<span class="notification is-header game">
15+
{{ $t('marathon.schedule.table.game') }}
16+
</span>
17+
<span class="notification is-header category">
18+
{{ $t('marathon.schedule.table.category') }}
19+
</span>
20+
<span class="notification is-header type">
21+
{{ $t('marathon.schedule.table.type') }}
22+
</span>
23+
<span class="notification is-header console">
24+
{{ $t('marathon.schedule.table.console') }}
25+
</span>
26+
<span class="notification is-header estimate">
27+
{{ $t('marathon.schedule.table.estimate') }}
28+
</span>
29+
<span class="notification is-header setup">
30+
{{ $t('marathon.schedule.table.setup') }}
31+
</span>
32+
<!-- Main Schedule Loop -->
33+
<template v-if="runs">
34+
<template v-for="(run, index) in runs">
35+
<!-- Ad -->
36+
<ClientOnly :key="'wrapper-advertisement' + index">
37+
<AdsByGoogle v-if="advertisementIndices.includes(index)" :key="'advertisement' + index" ad-slot="5905320802" ad-format="" class="is-advertisement" />
38+
<div v-show="shouldShowDay(index) && index !== 0" :key="'not-advertisement' + index" class="is-spacer" />
39+
</ClientOnly>
40+
<div v-show="shouldShowDay(index)" :key="'day' + index" class="day notification is-info">
41+
{{ $d(new Date(run.date), 'longDate') }}
42+
</div>
43+
<span :id="getId(run)" :key="'expandable' + index" class="notification is-expandable expandable" :class="getRowParity(index, run)" @click="expand(run)">
44+
<FontAwesomeIcon :icon="[ 'fas', expanded.has(run.id) ? 'caret-down' : 'caret-right' ]" />
5945
</span>
60-
<span :key="'game' + index" class="notification is-expandable game" :class="getRowParity(index, run)" @click="expand(run)">
61-
{{ run.gameName }}
46+
<span :id="'run-' + run.id" :key="'time' + index" class="notification is-expandable time" :class="getRowParity(index, run)" @click="expand(run)">
47+
{{ $d(new Date(run.date), 'shortTime') }}
6248
</span>
63-
</template>
64-
65-
<span :key="'category' + index" class="notification is-expandable category" :class="getRowParity(index, run)" @click="expand(run)">
66-
{{ run.categoryName }}
67-
</span>
68-
<span :key="'type' + index" class="notification is-expandable type" :class="getRowParity(index, run)" @click="expand(run)">
69-
{{ $t(`marathon.schedule.type.${run.type}`) }}
70-
</span>
71-
<span :key="'console' + index" class="notification is-expandable console" :class="getRowParity(index, run)" @click="expand(run)">
72-
<span>
73-
{{ run.console }}
49+
<span v-if="run.setupBlock" :key="'setupText' + index" class="notification is-expandable setup-text" :class="getRowParity(index, run)" @click="expand(run)">
50+
{{ (run.setupBlockText || $t('marathon.schedule.setupBlock')) }}
51+
</span>
52+
<template v-else>
53+
<span :key="'runners' + index" class="notification is-expandable runners" :class="getRowParity(index, run)" @click="expand(run)">
54+
<p v-for="runner in run.runners" :key="'runners' + index + 'runner' + runner.id">
55+
{{ runner.username }}
56+
</p>
57+
</span>
58+
<span :key="'game' + index" class="notification is-expandable game" :class="getRowParity(index, run)" @click="expand(run)">
59+
{{ run.gameName }}
60+
</span>
61+
</template>
62+
<span :key="'category' + index" class="notification is-expandable category" :class="getRowParity(index, run)" @click="expand(run)">
63+
{{ run.categoryName }}
7464
</span>
75-
<sup v-if="run.emulated">
76-
{{ $t('global.emu') }}
77-
</sup>
78-
</span>
79-
<span :key="'estimate' + index" class="notification is-expandable estimate" :class="getRowParity(index, run)" @click="expand(run)">
80-
<ElementTemporalDuration :duration="run.estimate" />
81-
</span>
82-
<span :key="'setup' + index" class="notification is-expandable setup" :class="getRowParity(index, run)" @click="expand(run)">
83-
<ElementTemporalDuration :duration="run.setupTime" />
84-
</span>
85-
<div v-if="expanded.has(run.id)" :key="'expanded' + index" class="expanded-run">
86-
<MarathonScheduleRun :run="run" :class="getRowParity(index, run)" />
87-
</div>
65+
<span :key="'type' + index" class="notification is-expandable type" :class="getRowParity(index, run)" @click="expand(run)">
66+
{{ $t(`marathon.schedule.type.${run.type}`) }}
67+
</span>
68+
<span :key="'console' + index" class="notification is-expandable console" :class="getRowParity(index, run)" @click="expand(run)">
69+
<span>
70+
{{ run.console }}
71+
</span>
72+
<sup v-if="run.emulated">
73+
{{ $t('global.emu') }}
74+
</sup>
75+
</span>
76+
<span :key="'estimate' + index" class="notification is-expandable estimate" :class="getRowParity(index, run)" @click="expand(run)">
77+
<ElementTemporalDuration :duration="run.estimate" />
78+
</span>
79+
<span :key="'setup' + index" class="notification is-expandable setup" :class="getRowParity(index, run)" @click="expand(run)">
80+
<ElementTemporalDuration :duration="run.setupTime" />
81+
</span>
82+
<div v-if="expanded.has(run.id)" :key="'expanded' + index" class="expanded-run">
83+
<MarathonScheduleRun :run="run" :class="getRowParity(index, run)" />
84+
</div>
85+
</template>
8886
</template>
89-
</template>
87+
</div>
88+
<div class="is-centered">
89+
<WidgetLoading :while="[ schedule ]" />
90+
</div>
9091
</div>
9192
</template>
9293

9394
<script lang="ts">
9495
import Vue from 'vue';
9596
import { mapActions } from 'vuex';
96-
import { ScheduleLine, ScheduleState, ScheduleTicker } from '~/types/api/schedule';
97+
import { Schedule, ScheduleLine, ScheduleState, ScheduleTicker } from '~/types/api/schedule';
9798
9899
export default Vue.extend({
99100
props: {
@@ -119,8 +120,11 @@ export default Vue.extend({
119120
]);
120121
},
121122
computed: {
123+
schedule(): Schedule|undefined {
124+
return (this.$store.state.api.schedule as ScheduleState).schedules[this.marathonId];
125+
},
122126
runs(): Array<ScheduleLine>|undefined {
123-
return (this.$store.state.api.schedule as ScheduleState).schedules[this.marathonId]?.lines;
127+
return this.schedule?.lines;
124128
},
125129
tickers(): ScheduleTicker|undefined {
126130
return (this.$store.state.api.schedule as ScheduleState).tickers[this.marathonId];

components/patron/List.vue

+21-13
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
<template>
2-
<div v-if="patrons" class="field is-grouped is-grouped-multiline">
3-
<template v-if="patrons.length">
4-
<div v-for="patron in patrons" :key="patron.id" class="tags has-addons">
5-
<div class="avatar tag is-primary">
6-
<img :src="patron.image_url">
2+
<div>
3+
<div v-if="patrons" class="field is-grouped is-grouped-multiline">
4+
<template v-if="patrons.length">
5+
<div v-for="patron in patrons" :key="patron.id" class="tags has-addons">
6+
<div class="avatar tag is-primary">
7+
<img :src="patron.image_url">
8+
</div>
9+
<a :href="`https://www.patreon.com/user?u=${patron.id}`" target="_blank" class="tag is-primary is-large">
10+
{{ patron.full_name }}
11+
</a>
712
</div>
8-
<a :href="`https://www.patreon.com/user?u=${patron.id}`" target="_blank" class="tag is-primary is-large">
9-
{{ patron.full_name }}
10-
</a>
11-
</div>
12-
</template>
13-
<span v-else v-html="$t('patrons.noPatrons')" />
13+
</template>
14+
<span v-else v-html="$t('patrons.noPatrons')" />
15+
</div>
16+
<div class="is-centered">
17+
<WidgetLoading :while="[ patronsWhole ]" />
18+
</div>
1419
</div>
1520
</template>
1621

1722
<script lang="ts">
1823
import Vue from 'vue';
1924
import { mapActions } from 'vuex';
20-
import { PatreonState, Patron } from '~/types/api/patreon';
25+
import { PatreonState, Patron, Patrons } from '~/types/api/patreon';
2126
2227
export default Vue.extend({
2328
async fetch(): Promise<void> {
@@ -26,8 +31,11 @@ export default Vue.extend({
2631
]);
2732
},
2833
computed: {
34+
patronsWhole(): Patrons|undefined {
35+
return (this.$store.state.api.patreon as PatreonState).patrons;
36+
},
2937
patrons(): Array<Patron>|undefined {
30-
return (this.$store.state.api.patreon as PatreonState).patrons?.patrons;
38+
return this.patronsWhole?.patrons;
3139
},
3240
},
3341
methods: {

0 commit comments

Comments
 (0)