Skip to content

Commit b8d9726

Browse files
committed
Auto merge of #2457 - Turbo87:index-loading, r=locks
index: Replace loading spinner with list item placeholders #1937 changed the data loading code of the `index` route to load the summary data in the `model()` hook again. That is fine when using fastboot, but without fastboot it is currently resulting in an ugly loading spinner page before the real page content gets rendered. This PR replaces the loading page with rendering the real page directly and showing placeholders until the real data is available. The new approach is still compatible with fastboot due to the use of `deferReadiness()`. ![loading](https://user-images.githubusercontent.com/141300/80317190-98050780-8802-11ea-8596-8d9f9ee05545.gif) r? @locks
2 parents 53ff1b6 + 10c5e9b commit b8d9726

File tree

14 files changed

+228
-101
lines changed

14 files changed

+228
-101
lines changed

app/components/category-list.hbs

Lines changed: 0 additions & 10 deletions
This file was deleted.

app/components/crate-list-name-only.hbs

Lines changed: 0 additions & 10 deletions
This file was deleted.

app/components/crate-list-newest.hbs

Lines changed: 0 additions & 11 deletions
This file was deleted.

app/components/front-page-list.hbs

Lines changed: 0 additions & 5 deletions
This file was deleted.

app/components/front-page-list.module.css

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
<li>
2-
<LinkTo @route={{@route}} @model={{@model}} local-class="link" ...attributes>
3-
<div local-class="left">
4-
<div local-class="title">{{@title}}</div>
5-
{{#if @subtitle}}<div local-class="subtitle">{{@subtitle}}</div>{{/if}}
6-
</div>
7-
{{svg-jar "chevron-right" local-class="right"}}
8-
</LinkTo>
9-
</li>
1+
<LinkTo @route={{@route}} @model={{@model}} local-class="link" ...attributes>
2+
<div local-class="left">
3+
<div local-class="title">{{@title}}</div>
4+
{{#if @subtitle}}<div local-class="subtitle">{{@subtitle}}</div>{{/if}}
5+
</div>
6+
{{svg-jar "chevron-right" local-class="right"}}
7+
</LinkTo>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div local-class="link" ...attributes>
2+
<div local-class="left">
3+
<div local-class="title"></div>
4+
{{#if @withSubtitle}}<div local-class="subtitle"></div>{{/if}}
5+
</div>
6+
{{svg-jar "chevron-right" local-class="right"}}
7+
</div>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.link {
2+
display: flex;
3+
align-items: center;
4+
width: 100%;
5+
min-height: 60px;
6+
margin: 8px 0;
7+
padding: 0 10px;
8+
background-color: var(--main-bg-dark);
9+
}
10+
11+
.left {
12+
flex-grow: 1;
13+
width: 0;
14+
}
15+
16+
.title {
17+
height: 16px;
18+
width: 150px;
19+
border-radius: 8px;
20+
background: rgb(118, 131, 138);
21+
opacity: 0.25;
22+
}
23+
24+
.subtitle {
25+
height: 13px;
26+
width: 90px;
27+
margin-top: 4px;
28+
border-radius: 6.5px;
29+
background: rgb(118, 131, 138);
30+
opacity: 0.2;
31+
}
32+
33+
.right {
34+
flex-shrink: 0;
35+
height: 16px;
36+
width: auto;
37+
margin-left: 10px;
38+
color: rgb(118, 131, 138);
39+
}

app/components/keyword-list.hbs

Lines changed: 0 additions & 10 deletions
This file was deleted.

app/controllers/index.js

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
11
import Controller from '@ember/controller';
2+
import { computed } from '@ember/object';
3+
import { readOnly } from '@ember/object/computed';
4+
import { inject as service } from '@ember/service';
5+
6+
import { task } from 'ember-concurrency';
27

38
export default Controller.extend({
4-
hasData: true,
9+
fetcher: service(),
10+
11+
model: readOnly('dataTask.lastSuccessful.value'),
12+
13+
hasData: computed('dataTask.{lastSuccessful,isRunning}', function () {
14+
return this.get('dataTask.lastSuccessful') || !this.get('dataTask.isRunning');
15+
}),
16+
17+
dataTask: task(function* () {
18+
let data = yield this.fetcher.ajax('/api/v1/summary');
19+
20+
addCrates(this.store, data.new_crates);
21+
addCrates(this.store, data.most_downloaded);
22+
addCrates(this.store, data.just_updated);
23+
addCrates(this.store, data.most_recently_downloaded);
24+
25+
return data;
26+
}).drop(),
527
});
28+
29+
function addCrates(store, crates) {
30+
for (let i = 0; i < crates.length; i++) {
31+
crates[i] = store.push(store.normalize('crate', crates[i]));
32+
}
33+
}

app/helpers/placeholders.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { helper } from '@ember/component/helper';
2+
3+
export default helper(([count]) => new Array(count));

app/routes/index.js

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Route from '@ember/routing/route';
22
import { inject as service } from '@ember/service';
33

44
export default Route.extend({
5-
fetcher: service(),
5+
fastboot: service(),
66

77
headTags() {
88
return [
@@ -16,26 +16,12 @@ export default Route.extend({
1616
];
1717
},
1818

19-
setupController(controller, model) {
20-
this._super(controller, model);
19+
setupController(controller) {
2120
this.controllerFor('application').set('searchQuery', null);
22-
},
23-
24-
model() {
25-
return this.fetcher.ajax('/api/v1/summary');
26-
},
2721

28-
// eslint-disable-next-line no-unused-vars
29-
afterModel(model, transition) {
30-
addCrates(this.store, model.new_crates);
31-
addCrates(this.store, model.most_downloaded);
32-
addCrates(this.store, model.just_updated);
33-
addCrates(this.store, model.most_recently_downloaded);
22+
let promise = controller.dataTask.perform();
23+
if (this.fastboot.isFastBoot) {
24+
this.fastboot.deferRendering(promise);
25+
}
3426
},
3527
});
36-
37-
function addCrates(store, crates) {
38-
for (let i = 0; i < crates.length; i++) {
39-
crates[i] = store.push(store.normalize('crate', crates[i]));
40-
}
41-
}

app/styles/index.module.css

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,9 @@
8686
font-size: 105%;
8787
line-height: 20px;
8888
}
89-
90-
> section[aria-busy="true"] > h2::after {
91-
content: '';
92-
background-image: url('/assets/ajax-loader.gif');
93-
display: inline-block;
94-
height: 16px;
95-
width: 16px;
96-
}
9789
}
9890

91+
.list {
92+
list-style: none;
93+
padding: 0;
94+
}

app/templates/index.hbs

Lines changed: 133 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,149 @@
3636
</div>
3737

3838
<div local-class='lists'>
39-
<section data-test-new-crates aria-busy="{{this.dataTask.isRunning}}">
39+
<section data-test-new-crates>
4040
<h2>New Crates</h2>
41-
<CrateListNewest @crates={{this.model.new_crates}} />
41+
<ol local-class="list" aria-busy="{{this.dataTask.isRunning}}">
42+
{{#if this.dataTask.isRunning}}
43+
{{#each (placeholders 10)}}
44+
<li>
45+
<FrontPageList::Item::Placeholder @withSubtitle={{true}} />
46+
</li>
47+
{{/each}}
48+
{{else}}
49+
{{#each this.model.new_crates as |crate index|}}
50+
<li>
51+
<FrontPageList::Item
52+
@route="crate"
53+
@model={{crate.id}}
54+
@title={{crate.name}}
55+
@subtitle="v{{crate.newest_version}}"
56+
data-test-crate-link={{index}}
57+
/>
58+
</li>
59+
{{/each}}
60+
{{/if}}
61+
</ol>
4262
</section>
43-
<section data-test-most-downloaded aria-busy="{{this.dataTask.isRunning}}">
63+
64+
<section data-test-most-downloaded>
4465
<h2>Most Downloaded</h2>
45-
<CrateListNameOnly @crates={{this.model.most_downloaded}} />
66+
<ol local-class="list" aria-busy="{{this.dataTask.isRunning}}">
67+
{{#if this.dataTask.isRunning}}
68+
{{#each (placeholders 10)}}
69+
<li>
70+
<FrontPageList::Item::Placeholder />
71+
</li>
72+
{{/each}}
73+
{{else}}
74+
{{#each this.model.most_downloaded as |crate index|}}
75+
<li>
76+
<FrontPageList::Item
77+
@route="crate"
78+
@model={{crate.id}}
79+
@title={{crate.name}}
80+
data-test-crate-link={{index}}
81+
/>
82+
</li>
83+
{{/each}}
84+
{{/if}}
85+
</ol>
4686
</section>
47-
<section data-test-just-updated aria-busy="{{this.dataTask.isRunning}}">
87+
88+
<section data-test-just-updated>
4889
<h2>Just Updated</h2>
49-
<CrateListNewest @crates={{this.model.just_updated}} />
90+
<ol local-class="list" aria-busy="{{this.dataTask.isRunning}}">
91+
{{#if this.dataTask.isRunning}}
92+
{{#each (placeholders 10)}}
93+
<li>
94+
<FrontPageList::Item::Placeholder @withSubtitle={{true}} />
95+
</li>
96+
{{/each}}
97+
{{else}}
98+
{{#each this.model.just_updated as |crate index|}}
99+
<li>
100+
<FrontPageList::Item
101+
@route="crate"
102+
@model={{crate.id}}
103+
@title={{crate.name}}
104+
@subtitle="v{{crate.newest_version}}"
105+
data-test-crate-link={{index}}
106+
/>
107+
</li>
108+
{{/each}}
109+
{{/if}}
110+
</ol>
50111
</section>
51-
<section data-test-most-recently-downloaded aria-busy="{{this.dataTask.isRunning}}">
112+
113+
<section data-test-most-recently-downloaded>
52114
<h2>Most Recent Downloads</h2>
53-
<CrateListNameOnly @crates={{this.model.most_recently_downloaded}} />
115+
<ol local-class="list" aria-busy="{{this.dataTask.isRunning}}">
116+
{{#if this.dataTask.isRunning}}
117+
{{#each (placeholders 10)}}
118+
<li>
119+
<FrontPageList::Item::Placeholder />
120+
</li>
121+
{{/each}}
122+
{{else}}
123+
{{#each this.model.most_recently_downloaded as |crate index|}}
124+
<li>
125+
<FrontPageList::Item
126+
@route="crate"
127+
@model={{crate.id}}
128+
@title={{crate.name}}
129+
data-test-crate-link={{index}}
130+
/>
131+
</li>
132+
{{/each}}
133+
{{/if}}
134+
</ol>
54135
</section>
55-
<section data-test-keywords aria-busy="{{this.dataTask.isRunning}}">
136+
137+
<section data-test-keywords>
56138
<h2>Popular Keywords <LinkTo @route="keywords">(see all)</LinkTo></h2>
57-
<KeywordList @keywords={{this.model.popular_keywords}} />
139+
<ul local-class="list" aria-busy="{{this.dataTask.isRunning}}">
140+
{{#if this.dataTask.isRunning}}
141+
{{#each (placeholders 10)}}
142+
<li>
143+
<FrontPageList::Item::Placeholder @withSubtitle={{true}} />
144+
</li>
145+
{{/each}}
146+
{{else}}
147+
{{#each this.model.popular_keywords as |keyword|}}
148+
<li>
149+
<FrontPageList::Item
150+
@route="keyword"
151+
@model={{keyword}}
152+
@title={{keyword.id}}
153+
@subtitle="{{format-num keyword.crates_cnt}} crates"
154+
/>
155+
</li>
156+
{{/each}}
157+
{{/if}}
158+
</ul>
58159
</section>
59-
<section data-test-categories aria-busy="{{this.dataTask.isRunning}}">
160+
161+
<section data-test-categories>
60162
<h2>Popular Categories <LinkTo @route="categories">(see all)</LinkTo></h2>
61-
<CategoryList @categories={{this.model.popular_categories}} />
163+
<ul local-class="list" aria-busy="{{this.dataTask.isRunning}}">
164+
{{#if this.dataTask.isRunning}}
165+
{{#each (placeholders 10)}}
166+
<li>
167+
<FrontPageList::Item::Placeholder @withSubtitle={{true}} />
168+
</li>
169+
{{/each}}
170+
{{else}}
171+
{{#each this.model.popular_categories as |category|}}
172+
<li>
173+
<FrontPageList::Item
174+
@route="category"
175+
@model={{category.slug}}
176+
@title={{category.category}}
177+
@subtitle="{{format-num category.crates_cnt}} crates"
178+
/>
179+
</li>
180+
{{/each}}
181+
{{/if}}
182+
</ul>
62183
</section>
63184
</div>

0 commit comments

Comments
 (0)