tangled
alpha
login
or
join now
mackuba.eu
/
skythread
14
fork
atom
Thread viewer for Bluesky
14
fork
atom
overview
issues
pulls
pipelines
extracted loading spinner to a component
mackuba.eu
3 months ago
6d57f903
7f7263ee
+43
-49
8 changed files
expand all
collapse all
unified
split
index.html
src
components
MainLoader.svelte
pages
HashtagPage.svelte
NotificationsPage.svelte
QuotesPage.svelte
ThreadPage.svelte
skythread.js
style.css
-2
index.html
···
23
23
<link href="dist/skythread.css" rel="stylesheet">
24
24
</head>
25
25
<body>
26
26
-
<div id="loader"><img src="icons/sunny.png"></div>
27
27
-
28
26
<div id="search"></div>
29
27
30
28
<div id="github">
+21
src/components/MainLoader.svelte
···
1
1
+
<div id="loader">
2
2
+
<img src="icons/sunny.png" alt="Loading...">
3
3
+
</div>
4
4
+
5
5
+
<style>
6
6
+
#loader {
7
7
+
position: fixed;
8
8
+
top: 0;
9
9
+
bottom: 0;
10
10
+
left: 0;
11
11
+
right: 0;
12
12
+
margin: auto;
13
13
+
width: 36px;
14
14
+
height: 36px;
15
15
+
}
16
16
+
17
17
+
#loader img {
18
18
+
width: 36px;
19
19
+
animation: rotation 3s infinite linear;
20
20
+
}
21
21
+
</style>
+6
-7
src/pages/HashtagPage.svelte
···
1
1
<script>
2
2
import { Post } from '../models/posts.js';
3
3
-
import { hideLoader } from '../skythread.js';
4
3
import * as paginator from '../utils/paginator.js';
4
4
+
import MainLoader from '../components/MainLoader.svelte';
5
5
import PostWrapper from '../components/posts/PostWrapper.svelte';
6
6
7
7
let { hashtag } = $props();
···
9
9
10
10
let posts = $state([]);
11
11
let firstPageLoaded = $state(false);
12
12
+
let loadingFailed = $state(false);
12
13
13
14
let isLoading = false;
14
15
let finished = false;
···
21
22
try {
22
23
let data = await api.getHashtagFeed(hashtag, cursor);
23
24
let batch = data.posts.map(j => new Post(j));
24
24
-
25
25
-
if (!firstPageLoaded) {
26
26
-
hideLoader();
27
27
-
firstPageLoaded = true;
28
28
-
}
25
25
+
firstPageLoaded = true;
29
26
30
27
posts.splice(posts.length, 0, ...batch);
31
28
···
36
33
finished = true;
37
34
}
38
35
} catch(error) {
39
39
-
hideLoader();
40
36
console.log(error);
41
37
isLoading = false;
38
38
+
loadingFailed = true;
42
39
}
43
40
});
44
41
</script>
···
61
58
{#each posts as post}
62
59
<PostWrapper {post} context="feed" />
63
60
{/each}
61
61
+
{:else if !loadingFailed}
62
62
+
<MainLoader />
64
63
{/if}
+5
-3
src/pages/NotificationsPage.svelte
···
1
1
<script>
2
2
import { Post } from '../models/posts.js';
3
3
-
import { hideLoader } from '../skythread.js';
4
3
import * as paginator from '../utils/paginator.js';
5
4
import FeedPostParent from '../components/posts/FeedPostParent.svelte';
5
5
+
import MainLoader from '../components/MainLoader.svelte';
6
6
import PostWrapper from '../components/posts/PostWrapper.svelte';
7
7
8
8
let posts = $state([]);
9
9
let firstPageLoaded = $state(false);
10
10
+
let loadingFailed = $state(false);
10
11
11
12
let isLoading = false;
12
13
let finished = false;
···
21
22
let batch = data.posts.map(x => new Post(x));
22
23
23
24
if (!firstPageLoaded && batch.length > 0) {
24
24
-
hideLoader();
25
25
firstPageLoaded = true;
26
26
}
27
27
···
36
36
next();
37
37
}
38
38
} catch(error) {
39
39
-
hideLoader();
40
39
console.log(error);
41
40
isLoading = false;
41
41
+
loadingFailed = true;
42
42
}
43
43
});
44
44
</script>
···
60
60
61
61
<PostWrapper {post} context="feed" />
62
62
{/each}
63
63
+
{:else if !loadingFailed}
64
64
+
<MainLoader />
63
65
{/if}
+5
-3
src/pages/QuotesPage.svelte
···
1
1
<script>
2
2
import { Post } from '../models/posts.js';
3
3
-
import { hideLoader } from '../skythread.js';
4
3
import * as paginator from '../utils/paginator.js';
4
4
+
import MainLoader from '../components/MainLoader.svelte';
5
5
import PostWrapper from '../components/posts/PostWrapper.svelte';
6
6
7
7
let isLoading = false;
···
13
13
let posts = $state([]);
14
14
let quoteCount = $state();
15
15
let firstPageLoaded = $derived(quoteCount !== undefined);
16
16
+
let loadingFailed = $state(false);
16
17
17
18
paginator.loadInPages(async () => {
18
19
if (isLoading || finished) { return }
···
24
25
let batch = jsons.map(j => new Post(j));
25
26
26
27
if (!firstPageLoaded) {
27
27
-
hideLoader();
28
28
quoteCount = data.quoteCount;
29
29
}
30
30
···
37
37
finished = true;
38
38
}
39
39
} catch(error) {
40
40
-
hideLoader();
41
40
console.log(error);
42
41
isLoading = false;
42
42
+
loadingFailed = true;
43
43
}
44
44
});
45
45
</script>
···
60
60
{#each posts as post}
61
61
<PostWrapper {post} context="quotes" />
62
62
{/each}
63
63
+
{:else if !loadingFailed}
64
64
+
<MainLoader />
63
65
{/if}
+5
-3
src/pages/ThreadPage.svelte
···
1
1
<script>
2
2
-
import { hideLoader } from '../skythread.js';
3
2
import { Post, parseThreadPost } from '../models/posts.js';
4
3
import { showError } from '../utils.js';
4
4
+
import MainLoader from '../components/MainLoader.svelte';
5
5
import PostWrapper from '../components/posts/PostWrapper.svelte';
6
6
import ThreadRootParent from '../components/posts/ThreadRootParent.svelte';
7
7
8
8
let { url = null, author = null, rkey = null } = $props();
9
9
let post = $state();
10
10
+
let loadingFailed = $state(false);
10
11
11
12
let response;
12
13
···
39
40
}
40
41
41
42
post = root;
42
42
-
hideLoader();
43
43
}).catch((error) => {
44
44
-
hideLoader();
45
44
showError(error);
45
45
+
loadingFailed = true;
46
46
});
47
47
</script>
48
48
···
71
71
{/if}
72
72
73
73
<PostWrapper {post} context="thread" />
74
74
+
{:else if !loadingFailed}
75
75
+
<MainLoader />
74
76
{/if}
+1
-14
src/skythread.js
···
71
71
let { q, author, post, quotes, hash, page } = Object.fromEntries(params);
72
72
73
73
if (quotes) {
74
74
-
showLoader();
75
74
loadQuotesPage(decodeURIComponent(quotes));
76
75
} else if (hash) {
77
77
-
showLoader();
78
76
loadHashtagPage(decodeURIComponent(hash));
79
77
} else if (q) {
80
80
-
showLoader();
81
78
svelte.mount(ThreadPage, { target: $id('thread'), props: { url: q }});
82
79
} else if (author && post) {
83
83
-
showLoader();
84
80
svelte.mount(ThreadPage, { target: $id('thread'), props: { author: author, rkey: post }});
85
81
} else if (page) {
86
82
openPage(page);
···
103
99
}, {
104
100
rootMargin: '1000px 0px'
105
101
});
106
106
-
}
107
107
-
108
108
-
function showLoader() {
109
109
-
$id('loader').style.display = 'block';
110
110
-
}
111
111
-
112
112
-
function hideLoader() {
113
113
-
$id('loader').style.display = 'none';
114
102
}
115
103
116
104
function showSearch() {
···
238
226
}
239
227
240
228
if (page == 'notif') {
241
241
-
showLoader();
242
229
let div = $id('thread');
243
230
div.classList.add('notifications');
244
231
svelte.mount(NotificationsPage, { target: div });
···
286
273
window.init = init;
287
274
window.BlueskyAPI = BlueskyAPI;
288
275
289
289
-
export { showLoginDialog, showBiohazardDialog, hideLoader, submitLogin };
276
276
+
export { showLoginDialog, showBiohazardDialog, submitLogin };
-17
style.css
···
234
234
border: 1px solid hsl(210, 90%, 80%);
235
235
}
236
236
237
237
-
#loader {
238
238
-
display: none;
239
239
-
position: fixed;
240
240
-
top: 0;
241
241
-
bottom: 0;
242
242
-
left: 0;
243
243
-
right: 0;
244
244
-
margin: auto;
245
245
-
width: 36px;
246
246
-
height: 36px;
247
247
-
}
248
248
-
249
249
-
#loader img {
250
250
-
width: 36px;
251
251
-
animation: rotation 3s infinite linear;
252
252
-
}
253
253
-
254
237
#thread {
255
238
padding-top: 1px;
256
239
}