Preload blobs in project source browser

This commit is contained in:
Alexis Sellier
2021-06-15 17:51:45 +02:00
parent e7f15acd97
commit 9bb9245d90
5 changed files with 84 additions and 46 deletions
+26 -3
View File
@@ -4,6 +4,7 @@
export let center = false;
export let fadeIn = false;
export let margins = false;
export let condensed = false;
</script>
<style>
@@ -34,6 +35,11 @@
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
.spinner.condensed > div {
-webkit-animation: sk-bouncedelay-condensed 0.7s infinite linear both;
animation: sk-bouncedelay-condensed 0.7s infinite linear both;
}
.spinner.small > div {
width: 0.5rem;
height: 0.5rem;
@@ -53,6 +59,21 @@
animation-delay: -0.16s;
}
@-webkit-keyframes sk-bouncedelay-condensed {
0%, 100% { -webkit-transform: scale(0.2) }
50% { -webkit-transform: scale(1.0) }
}
@keyframes sk-bouncedelay-condensed {
0%, 100% {
-webkit-transform: scale(0.2);
transform: scale(0.2);
} 50% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
}
@-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
@@ -85,8 +106,10 @@
}
</style>
<div class="spinner" class:fade-in={fadeIn} class:small class:center class:margins>
<div class="spinner" class:fade-in={fadeIn} class:small class:center class:margins class:condensed>
<div class="bounce1" style="background-color: var(--color-{color})"></div>
<div class="bounce2" style="background-color: var(--color-{color})"></div>
<div class="bounce3" style="background-color: var(--color-{color})"></div>
{#if !condensed}
<div class="bounce2" style="background-color: var(--color-{color})"></div>
<div class="bounce3" style="background-color: var(--color-{color})"></div>
{/if}
</div>
+42 -6
View File
@@ -11,13 +11,49 @@
import Blob from './Blob.svelte';
import Readme from './Readme.svelte';
enum Status {
Idle,
Loading,
Loaded,
}
type State =
{ status: Status.Idle }
| { status: Status.Loading; path: string }
| { status: Status.Loaded; path: string; blob: proj.Blob };
export let urn: string;
export let commit: string;
export let config: Config;
export let path: string;
export let org = "";
const onSelect = ({ detail: path }: { detail: string }) => {
// When the component is loaded the first time, the blob is yet to be loaded.
let state: State = { status: Status.Idle };
const loadBlob = async (path: string): Promise<proj.Blob> => {
if (state.status == Status.Loaded && state.path === path) {
return state.blob;
}
const isMarkdownPath = utils.isMarkdownPath(path);
const promise = path === "/"
? proj.getReadme(urn, commit, config)
: proj.getBlob(urn, commit, path, { highlight: !isMarkdownPath }, config);
state = { status: Status.Loading, path };
state = { status: Status.Loaded, path, blob: await promise };
return state.blob;
};
const onSelect = async ({ detail: path }: { detail: string }) => {
// Ensure we don't spend any time in a "loading" state. This means
// the loading spinner won't be shown, and instead the blob will be
// displayed once loaded.
const blob = await loadBlob(path);
getBlob = new Promise(resolve => resolve(blob));
navigate(proj.path({ urn, org, commit, path }));
};
@@ -25,11 +61,11 @@
return proj.getTree(urn, commit, path, config);
};
$: isMarkdownPath = utils.isMarkdownPath(path);
$: getBlob = path === "/"
? proj.getReadme(urn, commit, config)
: proj.getBlob(urn, commit, path, { highlight: !isMarkdownPath }, config);
// This is reactive to respond to path changes that don't originate from this
// component, eg. when using the browser's "back" button.
$: getBlob = loadBlob(path);
$: getAnchor = org ? Org.getAnchor(org, urn, config) : null;
$: loading = state.status == Status.Loading ? state.path : null;
</script>
<style>
@@ -167,7 +203,7 @@
{#if tree.entries.length}
<div class="column-left">
<div class="source-tree">
<Tree {tree} {path} {fetchTree} on:select={onSelect} />
<Tree {tree} {path} {fetchTree} {loading} on:select={onSelect} />
</div>
</div>
<div class="column-right">
+8 -28
View File
@@ -4,26 +4,14 @@
import type { Config } from '@app/config';
export let config: Config;
const joinPaths = (path: string, glob: string): string => {
return glob.length > 0 ? [path, glob].join("/") : path;
};
</script>
<Route path="/projects/:urn/head/:path/*" let:params>
<View {config} urn={params.urn} path={joinPaths(params.path, params['*'])} />
<Route path="/projects/:urn/head/*" let:params>
<View {config} urn={params.urn} path={params['*'] || "/"} />
</Route>
<Route path="/projects/:urn/:commit/:path/*" let:params>
<View {config} urn={params.urn} commit={params.commit} path={joinPaths(params.path, params['*'])} />
</Route>
<Route path="/projects/:urn/:commit" let:params>
<View {config} urn={params.urn} commit={params.commit} path="/" />
</Route>
<Route path="/projects/:urn/head" let:params>
<View {config} urn={params.urn} path="/" />
<Route path="/projects/:urn/:commit/*" let:params>
<View {config} urn={params.urn} commit={params.commit} path={params['*'] || "/"} />
</Route>
<Route path="/projects/:urn" let:params>
@@ -32,20 +20,12 @@
<!-- With an Org context -->
<Route path="/orgs/:org/projects/:urn/head/:path/*" let:params>
<View {config} org={params.org} urn={params.urn} path={joinPaths(params.path, params['*'])} />
<Route path="/orgs/:org/projects/:urn/head/*" let:params>
<View {config} org={params.org} urn={params.urn} path={params['*'] || "/"} />
</Route>
<Route path="/orgs/:org/projects/:urn/:commit/:path/*" let:params>
<View {config} org={params.org} urn={params.urn} commit={params.commit} path={joinPaths(params.path, params['*'])} />
</Route>
<Route path="/orgs/:org/projects/:urn/:commit" let:params>
<View {config} org={params.org} urn={params.urn} commit={params.commit} path="/" />
</Route>
<Route path="/orgs/:org/projects/:urn/head" let:params>
<View {config} org={params.org} urn={params.urn} path="/" />
<Route path="/orgs/:org/projects/:urn/:commit/*" let:params>
<View {config} org={params.org} urn={params.urn} commit={params.commit} path={params["*"] || "/"} />
</Route>
<Route path="/orgs/:org/projects/:urn" let:params>
+2 -1
View File
@@ -10,6 +10,7 @@
export let fetchTree: (path: string) => Promise<Tree>;
export let path: string;
export let tree: Tree;
export let loading: string | null = null;
const dispatch = createEventDispatcher();
const onSelect = ({ detail: path }: { detail: string }): void => {
@@ -29,7 +30,7 @@
{:else}
<File
active={entry.path === path}
loading={false}
loading={entry.path === loading}
name={entry.info.name}
on:click={() => onSelect({ detail: entry.path })}
/>
+6 -8
View File
@@ -12,7 +12,7 @@
border-radius: 0.25rem;
cursor: pointer;
display: flex;
flex: 1;
justify-content: space-between;
line-height: 1.5em;
margin: 0.125rem 0;
padding: 0.25rem;
@@ -44,12 +44,10 @@
</style>
<div class="file" class:active on:click>
{#if loading}
<div class="spinner">
<Loading small />
</div>
{:else}
<!-- Nothing -->
{/if}
<span class="name">{name}</span>
<div class="spinner">
{#if loading}
<Loading small condensed />
{/if}
</div>
</div>