Skip to content

Commit

Permalink
Add our own router
Browse files Browse the repository at this point in the history
  • Loading branch information
rudolfs committed Sep 3, 2024
1 parent 484d0d5 commit 8179ed0
Show file tree
Hide file tree
Showing 14 changed files with 684 additions and 332 deletions.
468 changes: 233 additions & 235 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@
"@tauri-apps/cli": "^2.0.0-rc.1",
"@tsconfig/svelte": "^5.0.4",
"eslint": "^9.9.1",
"baconjs": "^3.0.19",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.43.0",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.6",
"svelte": "^4.2.19",
"svelte-check": "^4.0.0",
"svelte-eslint-parser": "^0.41.0",
"svelte-routing": "^2.13.0",
"tslib": "^2.7.0",
"typescript": "^5.2.2",
"typescript-eslint": "^8.4.0",
Expand Down
7 changes: 0 additions & 7 deletions public/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,3 @@ html {
height: 100%;
width: 100%;
}

code {
font-family: var(--font-family-monospace);
font-size: var(--font-size-small);
background-color: var(--color-fill-ghost);
padding: 0.125rem 0.25rem;
}
46 changes: 38 additions & 8 deletions src/App.svelte
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
<script lang="ts">
import { Router, Route } from "svelte-routing";
import { onMount } from "svelte";
import { invoke } from "@tauri-apps/api/core";
import * as router from "@app/lib/router";
import { theme } from "@app/components/ThemeSwitch.svelte";
import { unreachable } from "@app/lib/utils";
import DesignSystem from "@app/views/DesignSystem.svelte";
import Repos from "@app/views/Repos.svelte";
import Startup from "@app/views/Startup.svelte";
import Home from "@app/views/Home.svelte";
import AuthenticationError from "@app/views/AuthenticationError.svelte";
const activeRouteStore = router.activeRouteStore;
void router.loadFromLocation();
onMount(async () => {
try {
await invoke("authenticate");
void router.push({ resource: "home" });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
void router.push({
resource: "authenticationError",
params: {
error: e.err,
hint: e.hint,
},
});
}
});
$: document.documentElement.setAttribute("data-theme", $theme);
</script>

<Router>
<Route path="/" component={Startup} />
<Route path="/repos" component={Repos} />
<Route path="/design-system" component={DesignSystem} />
</Router>
{#if $activeRouteStore.resource === "booting"}
<!-- Don't show anything -->
{:else if $activeRouteStore.resource === "home"}
<Home />
{:else if $activeRouteStore.resource === "authenticationError"}
<AuthenticationError {...$activeRouteStore.params} />
{:else if $activeRouteStore.resource === "designSystem"}
<DesignSystem />
{:else}
{unreachable($activeRouteStore)}
{/if}
23 changes: 19 additions & 4 deletions src/components/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import Icon from "./Icon.svelte";
import Popover from "./Popover.svelte";
import ThemeSwitch from "./ThemeSwitch.svelte";
export let currentPage: string;
</script>

<style>
Expand All @@ -26,17 +28,30 @@
gap: 0.5rem;
padding: 0 0.5rem;
}
.navigation :global(svg:hover) {
display: flex;
color: var(--color-fill-secondary);
}
</style>

<header class="flex-item">
<div class="wrapper flex-item">
<div class="wrapper-left flex-item">
<div class="flex-item" style:gap="0.5rem">
<Icon name="arrow-left" />
<Icon name="arrow-right" />
<div class="flex-item navigation" style:gap="0.5rem">
<Icon
name="arrow-left"
on:click={() => {
window.history.back();
}} />
<Icon
name="arrow-right"
on:click={() => {
window.history.forward();
}} />
</div>
<Fill variant="ghost" stylePadding="0 0.5rem" styleHeight="32px">
Repositories
{currentPage}
</Fill>
<Border variant="ghost" stylePadding="0 0.25rem" styleHeight="32px">
<Icon name="plus" />
Expand Down
3 changes: 3 additions & 0 deletions src/components/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
svg {
display: flex;
flex-shrink: 0;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
</style>

Expand Down
39 changes: 39 additions & 0 deletions src/components/Link.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script lang="ts" strictEvents>
import type { Route } from "@app/lib/router/definitions";
import { createEventDispatcher } from "svelte";
import { push, routeToPath } from "@app/lib/router";
export let route: Route;
export let disabled: boolean = false;
const dispatch = createEventDispatcher<{
afterNavigate: null;
}>();
function navigateToRoute(event: MouseEvent): void {
event.preventDefault();
if (disabled) {
return;
}
void push(route);
dispatch("afterNavigate");
}
</script>

<style>
a {
color: var(--color-fill-secondary);
text-decoration: none;
}
a:hover {
text-decoration: underline;
text-decoration-thickness: 1px;
text-underline-offset: 2px;
}
</style>

<a on:click={navigateToRoute} href={routeToPath(route)}>
<slot />
</a>
113 changes: 113 additions & 0 deletions src/lib/mutexExecutor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright © 2021 The Radicle Upstream Contributors
//
// This file is part of radicle-upstream, distributed under the GPLv3
// with Radicle Linking Exception. For full terms see the included
// LICENSE file.

//@ts-expect-error the typescript bindings are out of date.
import * as Bacon from "baconjs";

// A task executor that runs only one task concurrently. If a new task
// is run, any previously running task is aborted and the promise
// returned from `run()` will return undefined.
//
// import * as mutexExecutor from "ui/src/mutexExecutor"
// const executor = mutexExecutor.create()
// const first = await executor.run(async () => {
// await sleep(1000)
// return "first"
// })
// const second = await executor.run(async () => "second")
//
// In the example above the promise `first` will resolve to `undefined`
// while the promise `second` will resolve to "second".
//
// If the first tasks throws after the second task has run the
// behavior is the same.
//
// const first = await executor.run(async () => {
// await sleep(1000)
// throw new Error()
// })
//
// The task call back receives an AbortSignal as a parameter. The abort
// event is emitted when another task is run.
export function create(): MutexExecutor {
return new MutexExecutor();
}

// A worker that asynchronously process one item at a time and provides
// the result as an event stream.
//
// import * as mutexExecutor from "ui/src/mutexExecutor"
// const worker = mutexExecutor.createWorker(async (value) => {
// await sleep(1000)
// return value
// })
//
// const firstPromise = worker.output.firstToPromise()
// worker.push("first)
// assert.equal(await firstPromise, "first")
//
// When an item is submitted to the worker while the previous items is
// still being processed, the result of the first item will not be
// emitted to `worker.output`. Instead, only the last item will be
// emitted.
export function createWorker<In, Out>(
fn: (x: In, abortSignal: AbortSignal) => Promise<Out>,
): MutexWorker<In, Out> {
return new MutexWorker(fn);
}

class MutexExecutor {
private runningTaskId = 0;
private abortController: AbortController | null = null;

public async run<T>(
f: (abortSignal: AbortSignal) => Promise<T>,
): Promise<T | undefined> {
this.runningTaskId += 1;
const taskId = this.runningTaskId;

if (this.abortController) {
this.abortController.abort();
}
this.abortController = new AbortController();
return f(this.abortController.signal).then(
data => {
if (this.runningTaskId === taskId) {
return data;
} else {
return undefined;
}
},
err => {
if (this.runningTaskId === taskId) {
throw err;
} else {
return undefined;
}
},
);
}
}

class MutexWorker<In, Out> {
private outputBus = new Bacon.Bus<Out>();
private executor = new MutexExecutor();

public output: Bacon.EventStream<Out>;

public constructor(
private fn: (x: In, abortSignal: AbortSignal) => Promise<Out>,
) {
this.output = this.outputBus.toEventStream();
}

public async submit(x: In): Promise<void> {
const output = await this.executor.run(abort => this.fn(x, abort));
if (output !== undefined) {
this.outputBus.push(output);
}
}
}
Loading

0 comments on commit 8179ed0

Please sign in to comment.