Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add local switch and contextual help to sidebar
Sebastian Martinez committed 2 years ago
commit 10170d79e5ff135d713f37165fb218354cdcd161
parent ff593e65673411705e644d1ca22e7a6fe37c88be
5 files changed +207 -37
modified src/lib/httpd.ts
@@ -121,6 +121,11 @@ async function checkState() {
    .runExclusive(async () => {
      try {
        const { state: node } = await api.getNode();
+

+
        if (httpdState && httpdState.state !== "stopped") {
+
          httpdState.node = node;
+
        }
+

        if (httpdState && httpdState.state === "authenticated") {
          const sess = await api.session.getById(httpdState.session.id);
          const unixTimeInSeconds = Math.floor(Date.now() / 1000);
@@ -130,7 +135,7 @@ async function checkState() {
          ) {
            update({ state: "running", node });
          } else {
-
            update({ ...httpdState, node });
+
            update(httpdState);
          }
        } else {
          update({ state: "running", node });
modified src/views/home/Index.svelte
@@ -1,16 +1,13 @@
<script lang="ts">
  import type { ProjectBaseUrlActivity } from "./router";

-
  import { api } from "@app/lib/httpd";
-
  import { twemoji } from "@app/lib/utils";
+
  import { isLocal, twemoji } from "@app/lib/utils";

  import AppLayout from "@app/App/AppLayout.svelte";
  import Link from "@app/components/Link.svelte";
  import ProjectCard from "@app/components/ProjectCard.svelte";

  export let projects: ProjectBaseUrlActivity[];
-

-
  $: localProjects = projects[0]?.baseUrl === api.baseUrl;
</script>

<style>
@@ -69,7 +66,7 @@

    {#if projects.length > 0}
      <div class="heading">
-
        {#if localProjects}
+
        {#if isLocal(projects[0].baseUrl.hostname)}
          <!-- prettier-ignore -->
          <span>Explore projects on your <span class="txt-bold">local node</span>.</span>
        {:else}
modified src/views/projects/Layout.svelte
@@ -8,7 +8,7 @@
  import IconSmall from "@app/components/IconSmall.svelte";
  import Link from "@app/components/Link.svelte";
  import MobileFooter from "@app/App/MobileFooter.svelte";
-
  import Sidebar from "./Sidebar.svelte";
+
  import Sidebar from "@app/views/projects/Sidebar.svelte";

  export let activeTab: ActiveTab | undefined = undefined;
  export let baseUrl: BaseUrl;
modified src/views/projects/Sidebar.svelte
@@ -2,11 +2,17 @@
  import type { ActiveTab } from "./Header.svelte";
  import type { BaseUrl, Project } from "@httpd-client";

+
  import { ResponseError } from "@httpd-client/lib/fetcher";
+

+
  import { api, httpdStore } from "@app/lib/httpd";
+
  import { isLocal } from "@app/lib/utils";
  import { onMount } from "svelte";

  import Button from "@app/components/Button.svelte";
+
  import ContextHelp from "@app/views/projects/Sidebar/ContextHelp.svelte";
  import IconSmall from "@app/components/IconSmall.svelte";
  import Link from "@app/components/Link.svelte";
+
  import Loading from "@app/components/Loading.svelte";
  import Popover from "@app/components/Popover.svelte";

  import Help from "@app/App/Help.svelte";
@@ -27,6 +33,20 @@
    );
  }

+
  // To avoid concurrent request.
+
  let queryingLocalProject: boolean = true;
+
  let localProject: "notFound" | "found" | undefined = undefined;
+
  $: hideContextHelp =
+
    isLocal(baseUrl.hostname) && $httpdStore.state === "authenticated";
+
  $: loadingContextHelp =
+
    $httpdStore.state !== "stopped" && localProject === undefined;
+

+
  httpdStore.subscribe(async () => {
+
    if ($httpdStore.state !== "stopped" && !queryingLocalProject) {
+
      await detectLocalProject();
+
    }
+
  });
+

  function loadSidebarState(): boolean {
    const storedSidebarState = window.localStorage.getItem(SIDEBAR_STATE_KEY);

@@ -42,8 +62,22 @@
    storeSidebarState(expanded);
  }

-
  onMount(() => {
+
  async function detectLocalProject(): Promise<void> {
+
    queryingLocalProject = true;
+
    localProject = await api.project
+
      .getById(project.id)
+
      .then<"found">(() => "found")
+
      .catch((error: unknown) =>
+
        error instanceof ResponseError && error.status === 404
+
          ? "notFound"
+
          : undefined,
+
      );
+
    queryingLocalProject = false;
+
  }
+

+
  onMount(async () => {
    expanded = loadSidebarState();
+
    await detectLocalProject();
  });
</script>

@@ -55,6 +89,9 @@
    flex-direction: column;
    justify-content: space-between;
  }
+
  .expanded {
+
    width: 22.5rem;
+
  }
  .project-navigation {
    display: flex;
    flex-direction: column;
@@ -85,9 +122,16 @@
    justify-content: space-between;
    width: 100%;
  }
+
  .help-box {
+
    padding: 1rem;
+
    background-color: var(--color-background-float);
+
    border: 1px solid var(--color-border-hint);
+
    font-size: var(--font-size-small);
+
    border-radius: var(--border-radius-small);
+
  }
</style>

-
<div class="sidebar">
+
<div class="sidebar" class:expanded>
  {#if expanded}
    <div style="display: flex; flex-direction: column; gap: 1rem;">
      <div class="project-navigation">
@@ -164,39 +208,62 @@
        </Link>
      </div>
    </div>
+
    <div style="display: flex; flex-direction:column; gap: 1rem;">
+
      {#if !hideContextHelp}
+
        {#if loadingContextHelp}
+
          <div
+
            style="display: flex; justify-content: center; align-items: center; height: 2rem;">
+
            <Loading small />
+
          </div>
+
        {:else}
+
          <div class="help-box">
+
            <ContextHelp
+
              {localProject}
+
              {baseUrl}
+
              projectId={project.id}
+
              hideLocalButton={isLocal(baseUrl.hostname)}
+
              disableLocalButton={$httpdStore.state !== "authenticated" ||
+
                localProject !== "found"} />
+
          </div>
+
        {/if}
+
      {/if}

-
    <div class="sidebar-footer" style:flex-direction="row">
-
      <Button title={"Collapse"} on:click={toggleSidebar} variant="background">
-
        <IconSmall name="chevron-left" />
-
      </Button>
-
      <div style:width="1.5rem" />
-

-
      <Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
+
      <div class="sidebar-footer" style:flex-direction="row">
        <Button
-
          variant="background"
-
          title="Settings"
-
          slot="toggle"
-
          let:toggle
-
          on:click={toggle}>
-
          <IconSmall name="settings" />
-
          Settings
+
          title={"Collapse"}
+
          on:click={toggleSidebar}
+
          variant="background">
+
          <IconSmall name="chevron-left" />
        </Button>
+
        <div style:width="1.5rem" />

-
        <Settings slot="popover" />
-
      </Popover>
-
      <Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
-
        <Button
-
          variant="background"
-
          title="Help"
-
          slot="toggle"
-
          let:toggle
-
          on:click={toggle}>
-
          <IconSmall name="help" />
-
          Help
-
        </Button>
+
        <Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
+
          <Button
+
            variant="background"
+
            title="Settings"
+
            slot="toggle"
+
            let:toggle
+
            on:click={toggle}>
+
            <IconSmall name="settings" />
+
            Settings
+
          </Button>

-
        <Help slot="popover" />
-
      </Popover>
+
          <Settings slot="popover" />
+
        </Popover>
+
        <Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
+
          <Button
+
            variant="background"
+
            title="Help"
+
            slot="toggle"
+
            let:toggle
+
            on:click={toggle}>
+
            <IconSmall name="help" />
+
            Help
+
          </Button>
+

+
          <Help slot="popover" />
+
        </Popover>
+
      </div>
    </div>
  {:else}
    <div style="display: flex; flex-direction: column; gap: 1rem;">
@@ -287,6 +354,37 @@

        <Help slot="popover" />
      </Popover>
+

+
      {#if !hideContextHelp}
+
        {#if loadingContextHelp}
+
          <div
+
            style="display: flex; justify-content: center; align-items: center; height: 2rem;">
+
            <Loading small condensed />
+
          </div>
+
        {:else}
+
          <Popover popoverPositionBottom="0" popoverPositionLeft="3rem">
+
            <Button
+
              stylePadding="0 0.75rem"
+
              variant="background"
+
              title="Help"
+
              slot="toggle"
+
              let:toggle
+
              on:click={toggle}>
+
              <IconSmall name="globe" />
+
            </Button>
+

+
            <ContextHelp
+
              {localProject}
+
              {baseUrl}
+
              popover
+
              projectId={project.id}
+
              hideLocalButton={isLocal(baseUrl.hostname)}
+
              disableLocalButton={$httpdStore.state !== "authenticated" ||
+
                localProject !== "found"}
+
              slot="popover" />
+
          </Popover>
+
        {/if}
+
      {/if}
    </div>
  {/if}
</div>
added src/views/projects/Sidebar/ContextHelp.svelte
@@ -0,0 +1,70 @@
+
<script lang="ts">
+
  import type { BaseUrl } from "@httpd-client";
+

+
  import { api, httpdStore } from "@app/lib/httpd";
+
  import { isLocal } from "@app/lib/utils";
+

+
  import Button from "@app/components/Button.svelte";
+
  import ExternalLink from "@app/components/ExternalLink.svelte";
+
  import IconSmall from "@app/components/IconSmall.svelte";
+
  import Link from "@app/components/Link.svelte";
+

+
  export let baseUrl: BaseUrl;
+
  export let disableLocalButton: boolean;
+
  export let hideLocalButton: boolean;
+
  export let localProject: "notFound" | "found" | undefined;
+
  export let projectId: string;
+
  export let popover: boolean = false;
+
</script>
+

+
<style>
+
  .help {
+
    font-size: var(--font-size-small);
+
  }
+
  .popover {
+
    width: 18.5rem;
+
  }
+
  .title {
+
    padding-bottom: 0.75rem;
+
  }
+
</style>
+

+
<div class="help" class:popover>
+
  {#if $httpdStore.state === "stopped"}
+
    <div class="title txt-bold">Device not connected</div>
+
    <div>Click the Connect button in the top right corner to get started.</div>
+
  {:else if localProject === "notFound"}
+
    <div class="title txt-bold">Project not available locally</div>
+
    <div>
+
      This project hasn't been found on your local node. Click the Clone button
+
      in the top right corner to get a local copy.
+
    </div>
+
  {:else if $httpdStore.state === "running" && localProject === "found"}
+
    <div class="title txt-bold">Not authenticated</div>
+
    <div>To make changes you need to authenticate yourself.</div>
+
    <div>
+
      Click the Authenticate button in the top right corner to get
+
      authenticated.
+
    </div>
+
  {:else if !isLocal(baseUrl.hostname) && localProject === "found"}
+
    <div class="title txt-bold">Read Only</div>
+
    <div>This is a read only preview hosted on</div>
+
    <ExternalLink href={baseUrl.hostname} />
+
  {/if}
+

+
  {#if !hideLocalButton}
+
    <div style:padding-top="1rem">
+
      <Link
+
        disabled={disableLocalButton}
+
        route={{
+
          resource: "project.source",
+
          node: api.baseUrl,
+
          project: projectId,
+
        }}>
+
        <Button size="large" styleWidth="100%" disabled={disableLocalButton}>
+
          <IconSmall name="device" />Make changes on your local node
+
        </Button>
+
      </Link>
+
    </div>
+
  {/if}
+
</div>