Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Remove search functionality
Sebastian Martinez committed 2 years ago
commit 809daadd041193078791f934a7e495b5aedc20ea
parent 16a1821fff2db27d23301c2e787b6af41ad45f9e
11 files changed +28 -350
modified src/App/Header.svelte
@@ -1,8 +1,6 @@
<script lang="ts">
-
  import Link from "@app/components/Link.svelte";
-

  import Connect from "@app/App/Header/Connect.svelte";
-
  import Search from "@app/App/Header/Search.svelte";
+
  import Link from "@app/components/Link.svelte";
</script>

<style>
@@ -44,7 +42,6 @@
        alt="Radicle logo"
        src="/radicle.svg" />
    </Link>
-
    <Search />
  </div>

  <div class="right">
deleted src/App/Header/Search.svelte
@@ -1,133 +0,0 @@
-
<script lang="ts" strictEvents>
-
  import debounce from "lodash/debounce";
-
  import { createEventDispatcher } from "svelte";
-

-
  import * as Search from "@app/lib/search";
-
  import * as modal from "@app/lib/modal";
-
  import * as router from "@app/lib/router";
-
  import { searchPlaceholder } from "@app/lib/shared";
-
  import { unreachable } from "@app/lib/utils";
-

-
  import Icon from "@app/components/Icon.svelte";
-
  import SearchResultsModal from "@app/modals/SearchResultsModal.svelte";
-
  import TextInput from "@app/components/TextInput.svelte";
-

-
  const dispatch = createEventDispatcher<{
-
    finished: null;
-
  }>();
-

-
  export let input = "";
-

-
  let loading = false;
-
  let shaking = false;
-
  let expanded = false;
-

-
  // Clears search input on user navigation.
-
  router.activeRouteStore.subscribe(() => (input = ""));
-

-
  function shake() {
-
    shaking = true;
-
    debounce(() => (shaking = false), 500)();
-
  }
-

-
  async function search() {
-
    if (!valid) {
-
      return;
-
    }
-

-
    loading = true;
-

-
    const query = input;
-
    const searchResult = await Search.searchProjectsAndProfiles(input);
-

-
    if (searchResult.type === "nothing") {
-
      shake();
-
    } else if (searchResult.type === "error") {
-
      shake();
-
    } else if (searchResult.type === "projects") {
-
      input = "";
-
      if (searchResult.results.length === 1) {
-
        const { project, baseUrl } = searchResult.results[0];
-
        void router.push({
-
          resource: "project.source",
-
          project: project.id,
-
          node: baseUrl,
-
        });
-
      } else {
-
        modal.show({
-
          component: SearchResultsModal,
-
          props: {
-
            results: searchResult.results,
-
            query,
-
          },
-
        });
-
      }
-
      dispatch("finished");
-
      expanded = false;
-
    } else {
-
      unreachable(searchResult);
-
    }
-
    loading = false;
-
  }
-

-
  $: valid = input !== "";
-
</script>
-

-
<style>
-
  .search-bar {
-
    display: flex;
-
  }
-
  .shaking {
-
    animation: horizontal-shaking 0.35s;
-
  }
-
  @keyframes horizontal-shaking {
-
    0% {
-
      transform: translateX(0);
-
    }
-
    25% {
-
      transform: translateX(5px);
-
    }
-
    50% {
-
      transform: translateX(-5px);
-
    }
-
    75% {
-
      transform: translateX(5px);
-
    }
-
    100% {
-
      transform: translateX(0);
-
    }
-
  }
-
  .search {
-
    transition: all 0.2s;
-
    width: 11rem;
-
    color: var(--color-fill-secondary);
-
  }
-
  .expanded {
-
    width: 25.5rem;
-
  }
-
  @media (max-width: 720px) {
-
    .expanded {
-
      width: 11rem;
-
    }
-
  }
-
</style>
-

-
<div class="search" class:expanded>
-
  <div class="search-bar" class:shaking>
-
    <TextInput
-
      valid={input !== ""}
-
      {loading}
-
      disabled={loading}
-
      bind:value={input}
-
      on:focus={() => (expanded = true)}
-
      on:blur={() => {
-
        if (input === "") expanded = false;
-
      }}
-
      on:submit={search}
-
      placeholder={searchPlaceholder}>
-
      <svelte:fragment slot="left">
-
        <Icon name="magnifying-glass" />
-
      </svelte:fragment>
-
    </TextInput>
-
  </div>
-
</div>
modified src/App/Hotkeys.svelte
@@ -3,7 +3,6 @@

  import ColorPaletteModal from "@app/modals/ColorPaletteModal.svelte";
  import HotkeysModal from "@app/modals/HotkeysModal.svelte";
-
  import { searchPlaceholder } from "@app/lib/shared";

  const onKeydown = (event: KeyboardEvent) => {
    if (event.key === "Escape") {
@@ -15,14 +14,6 @@
      case "?":
        modal.toggle({ component: HotkeysModal, props: {} });
        break;
-
      case "/": {
-
        event.preventDefault();
-
        const searchInput: HTMLElement | null = document.querySelector(
-
          `*[placeholder="${searchPlaceholder}"]`,
-
        );
-
        searchInput?.focus();
-
        break;
-
      }
      case "d":
        if (import.meta.env.PROD) {
          return;
added src/lib/projects.ts
@@ -0,0 +1,25 @@
+
import type { BaseUrl, Project } from "@httpd-client";
+

+
import { HttpdClient } from "@httpd-client";
+
import { isFulfilled } from "@app/lib/utils";
+

+
export interface ProjectBaseUrl {
+
  project: Project;
+
  baseUrl: BaseUrl;
+
}
+

+
export async function getProjectsFromNodes(
+
  params: { id: string; baseUrl: BaseUrl }[],
+
): Promise<ProjectBaseUrl[]> {
+
  const projectPromises = params.map(async param => {
+
    const api = new HttpdClient(param.baseUrl);
+
    const project = await api.project.getById(param.id);
+
    return {
+
      project,
+
      baseUrl: param.baseUrl,
+
    };
+
  });
+

+
  const results = await Promise.allSettled(projectPromises);
+
  return results.filter(isFulfilled).map(r => r.value);
+
}
deleted src/lib/search.ts
@@ -1,66 +0,0 @@
-
import type { BaseUrl, Project } from "@httpd-client";
-

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

-
export interface ProjectBaseUrl {
-
  project: Project;
-
  baseUrl: BaseUrl;
-
}
-

-
type SearchResult =
-
  | { type: "nothing" }
-
  | { type: "error"; message: string }
-
  | { type: "projects"; results: ProjectBaseUrl[] };
-

-
export async function searchProjectsAndProfiles(
-
  query: string,
-
): Promise<SearchResult> {
-
  try {
-
    const pinned = config.nodes.pinned.map(node => ({
-
      id: query,
-
      baseUrl: node.baseUrl,
-
    }));
-

-
    if (utils.isRepositoryId(query)) {
-
      const results = await getProjectsFromNodes(pinned);
-

-
      if (results.length === 0) {
-
        return { type: "nothing" };
-
      } else {
-
        return {
-
          type: "projects",
-
          results,
-
        };
-
      }
-
    }
-

-
    return { type: "nothing" };
-
  } catch (error) {
-
    let message = "An unknown error occoured while searching.";
-

-
    if (error instanceof Error) {
-
      message = error.message;
-
    }
-

-
    return { type: "error", message };
-
  }
-
}
-

-
export async function getProjectsFromNodes(
-
  params: { id: string; baseUrl: BaseUrl }[],
-
): Promise<ProjectBaseUrl[]> {
-
  const projectPromises = params.map(async param => {
-
    const api = new HttpdClient(param.baseUrl);
-
    const project = await api.project.getById(param.id);
-
    return {
-
      project,
-
      baseUrl: param.baseUrl,
-
    };
-
  });
-

-
  const results = await Promise.allSettled(projectPromises);
-
  return results.filter(isFulfilled).map(r => r.value);
-
}
deleted src/lib/shared.ts
@@ -1,4 +0,0 @@
-
// This file is shared between tests and the UI code. It should only contain
-
// basic JS primitives, so that it works in both browser and node environments.
-

-
export const searchPlaceholder = "Enter an RID";
modified src/modals/HotkeysModal.svelte
@@ -40,11 +40,6 @@
          <KeyHint>?</KeyHint>
        </div>

-
        <div class="pair">
-
          <div class="description">Search</div>
-
          <KeyHint>/</KeyHint>
-
        </div>
-

        {#if import.meta.env.DEV}
          <div class="pair">
            <div class="description">Color palette</div>
deleted src/modals/SearchResultsModal.svelte
@@ -1,55 +0,0 @@
-
<script lang="ts" strictEvents>
-
  import type { ProjectBaseUrl } from "@app/lib/search";
-

-
  import * as modal from "@app/lib/modal";
-
  import { formatRepositoryId } from "@app/lib/utils";
-

-
  import Modal from "@app/components/Modal.svelte";
-
  import Icon from "@app/components/Icon.svelte";
-
  import Link from "@app/components/Link.svelte";
-

-
  export let query: string;
-
  export let results: ProjectBaseUrl[];
-
</script>
-

-
<style>
-
  .results {
-
    text-align: left;
-
  }
-
  ul {
-
    list-style-type: none;
-
    padding: 0;
-
  }
-
  li {
-
    margin: 0.5rem 0;
-
  }
-
</style>
-

-
<Modal title={`Results for "${query}"`}>
-
  <Icon name="magnifying-glass" size="48" slot="icon" />
-

-
  <span slot="body" class="results">
-
    {#if results.length > 0}
-
      <ul>
-
        {#each results as result}
-
          <li>
-
            <Link
-
              on:afterNavigate={modal.hide}
-
              route={{
-
                resource: "project.source",
-
                node: result.baseUrl,
-
                project: result.project.id,
-
              }}>
-
              <span title={result.baseUrl.hostname}>
-
                <span>{result.project.name}</span>
-
                <span class="id">
-
                  &nbsp;{formatRepositoryId(result.project.id)}
-
                </span>
-
              </span>
-
            </Link>
-
          </li>
-
        {/each}
-
      </ul>
-
    {/if}
-
  </span>
-
</Modal>
modified src/views/home/router.ts
@@ -1,12 +1,12 @@
import type { LoadErrorRoute } from "@app/lib/router/definitions";
-
import type { ProjectBaseUrl } from "@app/lib/search";
+
import type { ProjectBaseUrl } from "@app/lib/projects";
import type { WeeklyActivity } from "@app/lib/commit";

import { get } from "svelte/store";

import { api, httpdStore } from "@app/lib/httpd";
import { config } from "@app/lib/config";
-
import { getProjectsFromNodes } from "@app/lib/search";
+
import { getProjectsFromNodes } from "@app/lib/projects";
import { loadProjectActivity } from "@app/lib/commit";

export interface ProjectBaseUrlActivity extends ProjectBaseUrl {
deleted tests/e2e/hotkeys.spec.ts
@@ -1,39 +0,0 @@
-
import { searchPlaceholder } from "@app/lib/shared";
-
import { test, expect } from "@tests/support/fixtures.js";
-

-
test("global hotkeys", async ({ page }) => {
-
  await page.goto("/");
-
  await page.locator("body").press(`/`);
-
  await expect(page.locator(".search.expanded")).toBeVisible();
-
  // Delaying the input speed a bit to imitate a real user.
-
  await page.keyboard.type("searchquery", { delay: 100 });
-

-
  // Keyboard hint shows up in the search bar.
-
  {
-
    await expect(page.getByText("⏎")).toBeVisible();
-
    await expect(page.getByPlaceholder(searchPlaceholder)).toHaveValue(
-
      "searchquery",
-
    );
-
  }
-

-
  // Other hotkeys don't trigger while input is focussed.
-
  {
-
    await page.keyboard.type("?");
-
    await expect(page.getByPlaceholder(searchPlaceholder)).toHaveValue(
-
      "searchquery?",
-
    );
-
    await expect(
-
      page.locator(".modal").getByText("Keyboard shortcuts"),
-
    ).not.toBeVisible();
-
  }
-

-
  // Hitting `Esc` defocuses the input.
-
  {
-
    await page.locator("body").press("Escape");
-
    await expect(page.getByPlaceholder(searchPlaceholder)).toHaveValue(
-
      "searchquery?",
-
    );
-
    await expect(page.getByText("⏎")).toBeHidden();
-
    await expect(page.getByPlaceholder(searchPlaceholder)).not.toBeFocused();
-
  }
-
});
deleted tests/e2e/search.spec.ts
@@ -1,33 +0,0 @@
-
import { searchPlaceholder } from "@app/lib/shared";
-
import {
-
  test,
-
  expect,
-
  sourceBrowsingRid,
-
  sourceBrowsingUrl,
-
} from "@tests/support/fixtures.js";
-

-
test("navigate to existing project", async ({ page }) => {
-
  await page.goto("/");
-
  const searchInput = page.getByPlaceholder(searchPlaceholder);
-
  await searchInput.click();
-
  await searchInput.fill(sourceBrowsingRid);
-
  await searchInput.press("Enter");
-

-
  await expect(page).toHaveURL(sourceBrowsingUrl);
-
  await expect(searchInput).not.toHaveValue(sourceBrowsingRid);
-
});
-

-
test("navigate to a project that does not exist", async ({ page }) => {
-
  await page.goto("/");
-
  const searchInput = page.getByPlaceholder(searchPlaceholder);
-
  await searchInput.click();
-

-
  const nonExistantId = "rad:zAAAAAAAAAAAAAAAAAAAAAAAAAAA";
-
  await searchInput.fill(nonExistantId);
-
  await searchInput.press("Enter");
-

-
  await page.waitForSelector(".search-bar.shaking");
-

-
  await expect(page).toHaveURL("/");
-
  await expect(searchInput).toHaveValue(nonExistantId);
-
});