Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Move Blob loading into router
Rūdolfs Ošiņš committed 2 years ago
commit 4c101c256794b3f5d837215fe10c345ad5811ad2
parent 742a8eb1ef473080345f758ab9823d8fb7c513a4
8 files changed +105 -136
modified src/components/Markdown.svelte
@@ -2,7 +2,7 @@
  import dompurify from "dompurify";
  import matter from "@radicle/gray-matter";
  import { marked } from "marked";
-
  import { onMount } from "svelte";
+
  import { afterUpdate } from "svelte";
  import { toDom } from "hast-util-to-dom";

  import * as utils from "@app/lib/utils";
@@ -45,16 +45,13 @@
      event.preventDefault();
      if ($activeRouteStore.resource === "projects") {
        void updateProjectRoute({
-
          path: utils.canonicalize(
-
            event.target.getAttribute("href"),
-
            $activeRouteStore.params.path ?? "",
-
          ),
+
          path: utils.canonicalize(event.target.getAttribute("href"), path),
        });
      }
    }
  }

-
  onMount(async () => {
+
  afterUpdate(async () => {
    // Don't underline <a> tags that contain images.
    for (const e of container.querySelectorAll("a")) {
      if (e.firstElementChild instanceof HTMLImageElement) {
modified src/views/projects/Blob.svelte
@@ -20,7 +20,7 @@
  $: fileExtension = blob.path.split(".").pop() ?? "";
  $: lastCommit = blob.lastCommit;

-
  const parentDir = blob.path
+
  $: parentDir = blob.path
    .match(/^.*\/|/)
    ?.values()
    .next().value;
@@ -28,15 +28,23 @@

  onMount(async () => {
    window.addEventListener("hashchange", setTarget);
-
    if (!blob.content) {
-
      return;
-
    }
-
    const output = await highlight(blob.content, fileExtension);
-
    if (output) {
-
      content = lineNumbersGutter(output);
-
    }
  });

+
  $: {
+
    if (blob.content) {
+
      highlight(blob.content, fileExtension)
+
        .then(output => {
+
          if (output) {
+
            content = lineNumbersGutter(output);
+
          }
+
        })
+
        /* eslint-disable-next-line @typescript-eslint/no-empty-function */
+
        .catch(() => {
+
          // TODO: handle error.
+
        });
+
    }
+
  }
+

  onDestroy(() => {
    window.removeEventListener("hashchange", setTarget);
  });
@@ -45,12 +53,13 @@
    setTarget();
  });

-
  const isMarkdown = isMarkdownPath(blob.path);
-
  let showMarkdown = isMarkdown;
-
  const toggleMarkdown = () => {
+
  $: isMarkdown = isMarkdownPath(blob.path);
+
  $: showMarkdown = isMarkdown;
+

+
  function toggleMarkdown() {
    window.location.hash = "";
    showMarkdown = !showMarkdown;
-
  };
+
  }

  function setTarget() {
    for (const item of document.getElementsByClassName("highlight")) {
modified src/views/projects/Browser.svelte
@@ -1,33 +1,16 @@
-
<script lang="ts" context="module">
-
  import { writable } from "svelte/store";
-

-
  export const browserErrorStore = writable<
-
    { message: string; path: string } | undefined
-
  >();
-
</script>
-

<script lang="ts">
-
  import type { BaseUrl, Blob, Project, Tree } from "@httpd-client";
+
  import type { BaseUrl, Project, Tree } from "@httpd-client";
+
  import type { BlobResult } from "./router";

  import * as utils from "@app/lib/utils";
  import { HttpdClient } from "@httpd-client";

  import Button from "@app/components/Button.svelte";
-
  import Loading from "@app/components/Loading.svelte";
  import Placeholder from "@app/components/Placeholder.svelte";

  import BlobComponent from "./Blob.svelte";
  import TreeComponent from "./Tree.svelte";

-
  enum Status {
-
    Loading,
-
    Loaded,
-
  }
-

-
  type State =
-
    | { status: Status.Loading; path: string }
-
    | { status: Status.Loaded; path: string; blob: Blob; commit: string };
-

  export let path: string;
  export let hash: string | undefined = undefined;
  export let project: Project;
@@ -36,58 +19,25 @@
  export let revision: string | undefined;
  export let commit: string;

-
  // When the component is loaded the first time, the blob is yet to be loaded.
-
  let state: State = { status: Status.Loading, path };
+
  export let blobResult: BlobResult;
+

  // Whether the mobile file tree is visible.
  let mobileFileTree = false;

  const api = new HttpdClient(baseUrl);

-
  // When a user clicks between multiple files, we want to retain the previous
-
  // file contents and show them while the new file is loading to prevent the
-
  // UI from flickering or showing a loading indicator.
-
  let previousBlob: Blob;
-

-
  const loadBlob = async (projectId: string, commit: string, path: string) => {
-
    browserErrorStore.set(undefined);
-
    if (
-
      state.status === Status.Loaded &&
-
      state.path === path &&
-
      state.commit === commit
-
    ) {
-
      return state.blob;
-
    }
-

-
    state = { status: Status.Loading, path };
-

-
    let blob;
-
    if (path === "/") {
-
      blob = await api.project.getReadme(projectId, commit);
-
    } else {
-
      blob = await api.project.getBlob(projectId, commit, path);
-
    }
-

-
    state = { status: Status.Loaded, path, blob, commit };
-
    previousBlob = blob;
-
    return blob;
-
  };
-

  const fetchTree = async (path: string) => {
    return api.project.getTree(project.id, commit, path).catch(() => {
-
      browserErrorStore.set({
-
        message: "Not able to expand directory",
-
        path,
-
      });
+
      blobResult = {
+
        ok: false,
+
        error: {
+
          message: "Not able to expand directory",
+
          path,
+
        },
+
      };
      return undefined;
    });
  };
-

-
  $: getBlob = loadBlob(project.id, commit, path).catch(() => {
-
    browserErrorStore.set({ message: "Not able to load file", path });
-
    return undefined;
-
  });
-
  $: loadingPath =
-
    !$browserErrorStore && state.status === Status.Loading ? state.path : null;
</script>

<style>
@@ -192,7 +142,6 @@
            {tree}
            {path}
            {fetchTree}
-
            {loadingPath}
            revision={revision ?? project.defaultBranch}
            on:select={() => {
              // Close mobile tree if user navigates to other file
@@ -201,46 +150,21 @@
        </div>
      </div>
      <div class="column-right">
-
        {#if $browserErrorStore}
+
        {#if blobResult.ok}
+
          <BlobComponent
+
            {path}
+
            {hash}
+
            blob={blobResult.blob}
+
            rawPath={utils.getRawBasePath(project.id, baseUrl, commit)} />
+
        {:else}
          <Placeholder emoji="🍂">
            <span slot="title">
-
              <div class="txt-monospace">{$browserErrorStore.path}</div>
+
              <div class="txt-monospace">{blobResult.error.path}</div>
            </span>
            <span slot="body">
-
              <span>
-
                {#if $browserErrorStore.path === "/"}
-
                  The README could not be loaded.
-
                {:else}
-
                  {$browserErrorStore.message}
-
                {/if}
-
              </span>
+
              {blobResult.error.message}
            </span>
          </Placeholder>
-
        {:else}
-
          {#await getBlob}
-
            {#if previousBlob}
-
              <div class="layout-desktop">
-
                <BlobComponent
-
                  {path}
-
                  {hash}
-
                  blob={previousBlob}
-
                  rawPath={utils.getRawBasePath(project.id, baseUrl, commit)} />
-
              </div>
-
              <div class="layout-mobile">
-
                <Loading small center />
-
              </div>
-
            {:else}
-
              <Loading small center />
-
            {/if}
-
          {:then blob}
-
            {#if blob}
-
              <BlobComponent
-
                {path}
-
                {hash}
-
                {blob}
-
                rawPath={utils.getRawBasePath(project.id, baseUrl, commit)} />
-
            {/if}
-
          {/await}
        {/if}
      </div>
    {:else}
modified src/views/projects/Tree.svelte
@@ -11,7 +11,6 @@
  export let path: string;
  export let tree: Tree;
  export let revision: string;
-
  export let loadingPath: string | null = null;

  const dispatch = createEventDispatcher<{ select: string }>();
  const onSelect = ({ detail: path }: { detail: string }): void => {
@@ -23,7 +22,6 @@
  {#if entry.kind === "tree"}
    <Folder
      {fetchTree}
-
      {loadingPath}
      {revision}
      name={entry.name}
      prefix={`${entry.path}/`}
modified src/views/projects/Tree/Folder.svelte
@@ -12,7 +12,6 @@
  export let name: string;
  export let prefix: string;
  export let currentPath: string;
-
  export let loadingPath: string | null = null;
  export let revision: string;

  $: expanded = currentPath.indexOf(prefix) === 0;
@@ -86,7 +85,6 @@
              name={entry.name}
              on:select={onSelectFile}
              prefix={`${entry.path}/`}
-
              {loadingPath}
              {revision}
              {currentPath} />
          {:else}
modified src/views/projects/View.svelte
@@ -23,7 +23,6 @@
  export let baseUrl: BaseUrl;

  export let hash: string | undefined = undefined;
-
  export let path: string | undefined = undefined;
  export let peer: string | undefined = undefined;
  export let revision: string | undefined = undefined;
  export let search: string | undefined = undefined;
@@ -79,7 +78,8 @@
        {revision}
        commit={view.params.selectedCommit}
        tree={view.params.loadedTree}
-
        path={path || "/"}
+
        blobResult={view.blobResult}
+
        path={view.path}
        {hash} />
    {:else if view.resource === "history"}
      <History
modified src/views/projects/router.ts
@@ -1,6 +1,7 @@
import type { LoadError } from "@app/lib/router/definitions";
import type {
  BaseUrl,
+
  Blob,
  Commit,
  CommitHeader,
  Issue,
@@ -64,7 +65,6 @@ export interface ProjectLoadedParams {
  view: ProjectLoadedView;

  hash?: string;
-
  path?: string;
  peer?: string;
  revision?: string;
  search?: string;
@@ -77,10 +77,16 @@ interface LoadedSourceBrowsingParams {
  selectedCommit: string;
}

+
export type BlobResult =
+
  | { ok: true; blob: Blob }
+
  | { ok: false; error: { message: string; path: string } };
+

export type ProjectLoadedView =
  | {
      resource: "tree";
      params: LoadedSourceBrowsingParams;
+
      path: string;
+
      blobResult: BlobResult;
    }
  | {
      resource: "commits";
@@ -205,8 +211,56 @@ export async function loadProjectRoute(
        loadedTree: tree,
        selectedCommit: commit,
      };
+
      if (params.view.resource === "tree") {
+
        let blobResult: BlobResult;
+

+
        const path = params.path || "/";
+
        try {
+
          if (path === "/") {
+
            blobResult = {
+
              ok: true,
+
              blob: await api.project.getReadme(project.id, commit),
+
            };
+
          } else {
+
            blobResult = {
+
              ok: true,
+
              blob: await api.project.getBlob(project.id, commit, path),
+
            };
+
          }
+
        } catch {
+
          if (path === "/") {
+
            blobResult = {
+
              ok: false,
+
              error: {
+
                message: "The README could not be loaded",
+
                path,
+
              },
+
            };
+
          } else {
+
            blobResult = {
+
              ok: false,
+
              error: {
+
                message: "Not able to load file",
+
                path,
+
              },
+
            };
+
          }
+
        }

-
      if (params.view.resource === "history") {
+
        return {
+
          resource: "projects",
+
          params: {
+
            ...params,
+
            project,
+
            view: {
+
              resource: params.view.resource,
+
              params: viewParams,
+
              path,
+
              blobResult,
+
            },
+
          },
+
        };
+
      } else if (params.view.resource === "history") {
        const commitsResponse = await api.project.getAllCommits(project.id, {
          parent: commit,
          page: 0,
@@ -226,8 +280,7 @@ export async function loadProjectRoute(
            },
          },
        };
-
      }
-
      if (params.view.resource === "commits") {
+
      } else if (params.view.resource === "commits") {
        const loadedCommit = await api.project.getCommitBySha(
          params.id,
          commit,
@@ -246,17 +299,7 @@ export async function loadProjectRoute(
          },
        };
      } else {
-
        return {
-
          resource: "projects",
-
          params: {
-
            ...params,
-
            project,
-
            view: {
-
              resource: params.view.resource,
-
              params: viewParams,
-
            },
-
          },
-
        };
+
        return params.view;
      }
    } else if (params.view.resource === "issue") {
      try {
modified tests/e2e/project.spec.ts
@@ -417,7 +417,7 @@ test.describe("browser error handling", () => {

    await page.goto(sourceBrowsingUrl);
    await expect(
-
      page.locator("text=The README could not be loaded."),
+
      page.locator("text=The README could not be loaded"),
    ).toBeVisible();
  });
  test("error appears when navigating to missing file", async ({ page }) => {