Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Create the loading / splashscreen and make sure the Radicle identity / profile is ready to use
Merged did:key:z6MkkfM3...sVz5 opened 1 year ago
17 files changed +219 -142 56046b43 0cab98ac
modified index.html
@@ -3,9 +3,17 @@

<head>
  <meta charset="UTF-8" />
-
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Radicle</title>
+

+
  <link rel="preload" href="/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
+
  <link rel="preload" href="/fonts/Inter-Medium.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
+
  <link rel="preload" href="/fonts/Inter-SemiBold.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
+
  <link rel="preload" href="/fonts/Inter-Bold.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
+

+
  <link rel="stylesheet" href="/index.css">
+
  <link rel="stylesheet" href="/colors.css">
+
  <link rel="stylesheet" href="/typography.css">
</head>

<body>
added public/colors.css
@@ -0,0 +1,4 @@
+
:root {
+
  --color-background-default: #0a0d10;
+
  --color-foreground-contrast: #f9f9fb;
+
}
added public/images/warning.png
added public/index.css
@@ -0,0 +1,11 @@
+
* {
+
  box-sizing: border-box;
+
}
+

+
body {
+
  height: 100%;
+
  margin: 0;
+
  padding: 0;
+
  color: var(--color-foreground-contrast);
+
  background-color: var(--color-background-default);
+
}
added public/typography.css
@@ -0,0 +1,48 @@
+
@font-face {
+
  font-family: "Inter";
+
  font-style: normal;
+
  font-weight: 400;
+
  font-display: swap;
+
  src: url("fonts/Inter-Regular.woff2");
+
}
+
@font-face {
+
  font-family: "Inter";
+
  font-weight: 500;
+
  font-display: swap;
+
  src: url("fonts/Inter-Medium.woff2");
+
}
+
@font-face {
+
  font-family: "Inter";
+
  font-weight: 600;
+
  font-display: swap;
+
  src: url("fonts/Inter-SemiBold.woff2");
+
}
+
@font-face {
+
  font-family: "Inter";
+
  font-weight: 700;
+
  font-display: swap;
+
  src: url("fonts/Inter-Bold.woff2");
+
}
+

+
:root {
+
  --font-family-sans-serif: Inter, sans-serif;
+
  --font-size-small: 0.875rem; /* 14px */
+
  --font-size-regular: 1rem; /* 16px */
+
  --font-size-medium: 1.25rem; /* 20px */
+
}
+

+
html {
+
  font-family: var(--font-family-sans-serif);
+
  font-size: 16px;
+
  font-weight: var(--font-weight-regular);
+
}
+

+
.txt-small {
+
  font-size: var(--font-size-small);
+
}
+
.txt-regular {
+
  font-size: var(--font-size-regular);
+
}
+
.txt-medium {
+
  font-size: var(--font-size-medium);
+
}
deleted public/vite.svg
@@ -1 +0,0 @@
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

\ No newline at end of file
modified src-tauri/Cargo.lock
@@ -3183,6 +3183,7 @@ dependencies = [
name = "radicle-desktop"
version = "0.0.0"
dependencies = [
+
 "anyhow",
 "radicle",
 "radicle-surf",
 "serde",
modified src-tauri/Cargo.toml
@@ -19,6 +19,7 @@ crate-type = ["staticlib", "cdylib", "lib"]
tauri-build = { version = "2.0.0-rc.0", features = ["isolation"] }

[dependencies]
+
anyhow = { version = "1.0.86" }
radicle = { version = "0.12.0" }
radicle-surf = { version = "0.21.0" }
serde = { version = "1.0", features = ["derive"] }
added src-tauri/src/auth.rs
@@ -0,0 +1,27 @@
+
use anyhow::anyhow;
+
use radicle::crypto::ssh;
+

+
use crate::{error::Error, AppState};
+

+
#[tauri::command]
+
pub fn authenticate(ctx: tauri::State<AppState>) -> Result<(), Error> {
+
    let profile = &ctx.profile;
+

+
    if !profile.keystore.is_encrypted()? {
+
        return Ok(());
+
    }
+
    match ssh::agent::Agent::connect() {
+
        Ok(mut agent) => {
+
            if agent.request_identities()?.contains(&profile.public_key) {
+
                return Ok(());
+
            } else {
+
                Err(Error::WithHint {
+
                    err: anyhow!("Not able to find your keys in the ssh agent"),
+
                    hint: "Make sure to run <code>rad auth</code> in your terminal to add your keys to the ssh-agent.",
+
                })?
+
            }
+
        }
+
        Err(e) if e.is_not_running() => Err(Error::WithHint { err: anyhow!("SSH Agent is not running"), hint: "For now we require the user to have an ssh agent running, since we don't have passphrase inputs yet." })?, 
+
        Err(e) => Err(e)?,
+
    }
+
}
modified src-tauri/src/error.rs
@@ -54,6 +54,25 @@ pub enum Error {
    #[error(transparent)]
    Surf(#[from] radicle_surf::Error),

+
    /// Crypto error.
+
    #[error(transparent)]
+
    Crypto(#[from] radicle::crypto::ssh::keystore::Error),
+

+
    /// SSH Agent error.
+
    #[error(transparent)]
+
    Agent(#[from] radicle::crypto::ssh::agent::Error),
+

+
    /// Memory Signer error.
+
    #[error(transparent)]
+
    MemorySigner(#[from] radicle::crypto::ssh::keystore::MemorySignerError),
+

+
    /// An error with a hint.
+
    #[error("{err} {hint}")]
+
    WithHint {
+
        err: anyhow::Error,
+
        hint: &'static str,
+
    },
+

    /// Storage error.
    #[error(transparent)]
    Storage(#[from] radicle::storage::Error),
@@ -63,11 +82,35 @@ pub enum Error {
    Node(#[from] radicle::node::Error),
}

+
#[derive(Serialize)]
+
struct ErrorWrapperWithHint {
+
    err: String,
+
    hint: String,
+
}
+

+
#[derive(Serialize)]
+
struct ErrorWrapper {
+
    err: String,
+
}
+

impl Serialize for Error {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::ser::Serializer,
    {
-
        serializer.serialize_str(self.to_string().as_ref())
+
        if let Error::WithHint { err, hint } = self {
+
            let error_wrapper = ErrorWrapperWithHint {
+
                err: err.to_string(),
+
                hint: hint.to_string(),
+
            };
+

+
            return error_wrapper.serialize(serializer);
+
        }
+

+
        let wrapper = ErrorWrapper {
+
            err: self.to_string(),
+
        };
+

+
        wrapper.serialize(serializer)
    }
}
modified src-tauri/src/lib.rs
@@ -1,14 +1,37 @@
-
#[tauri::command]
-
fn greet(name: &str) -> String {
-
    format!("Hello, {}!", name)
+
mod auth;
+
mod error;
+

+
use auth::authenticate;
+
use tauri::Manager;
+

+
struct AppState {
+
    profile: radicle::Profile,
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
+
        .setup(|app| {
+
            let profile: radicle::Profile = match radicle::Profile::load() {
+
                Ok(profile) => Ok::<radicle::Profile, error::Error>(profile),
+
                Err(radicle::profile::Error::NotFound(path)) => Err(error::Error::WithHint {
+
                    err: anyhow::anyhow!("Radicle profile not found in '{}'.", path.display()),
+
                    hint: "To setup your radicle profile, run `rad auth`.",
+
                }
+
                .into()),
+
                Err(e) => Err(error::Error::WithHint {
+
                    err: e.into(),
+
                    hint: "Could not load radicle profile",
+
                }),
+
            }?;
+

+
            app.manage(AppState { profile });
+

+
            Ok(())
+
        })
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_window_state::Builder::default().build())
-
        .invoke_handler(tauri::generate_handler![greet])
+
        .invoke_handler(tauri::generate_handler![authenticate])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
modified src-tauri/tauri.conf.json
@@ -12,13 +12,16 @@
    "windows": [
      {
        "title": "Radicle",
-
        "minWidth": 800,
-
        "minHeight": 600
+
        "minWidth": 1200,
+
        "minHeight": 780
      }
    ],
    "security": {
      "csp": {
-
        "default-src": "'self'"
+
        "default-src": "'self'",
+
        "connect-src": "ipc: http://ipc.localhost",
+
        "img-src": "'self' asset: http://asset.localhost blob: data:",
+
        "style-src": "'unsafe-inline' 'self'"
      },
      "pattern": {
        "use": "isolation",
modified src/App.svelte
@@ -1,54 +1,5 @@
<script lang="ts">
-
  import { invoke } from "@tauri-apps/api/core";
-

-
  import svelteLogo from "./assets/svelte.svg";
-
  import viteLogo from "/vite.svg";
+
  import Startup from "./views/Startup.svelte";
</script>

-
<style>
-
  .logo {
-
    height: 6em;
-
    padding: 1.5em;
-
    will-change: filter;
-
    transition: filter 300ms;
-
  }
-
  .logo:hover {
-
    filter: drop-shadow(0 0 2em #646cffaa);
-
  }
-
  .logo.svelte:hover {
-
    filter: drop-shadow(0 0 2em #ff3e00aa);
-
  }
-
  .read-the-docs {
-
    color: #888;
-
  }
-
</style>
-

-
<main>
-
  <div>
-
    <a href="https://vitejs.dev" target="_blank" rel="noreferrer">
-
      <img src={viteLogo} class="logo" alt="Vite Logo" />
-
    </a>
-
    <a href="https://svelte.dev" target="_blank" rel="noreferrer">
-
      <img src={svelteLogo} class="logo svelte" alt="Svelte Logo" />
-
    </a>
-
  </div>
-
  <h1>Vite + Svelte</h1>
-

-
  {#await invoke("greet", { name: "radicle" }) then result}
-
    <span>
-
      Greetings from the Tauri core: {result}
-
    </span>
-
  {/await}
-

-
  <p>
-
    Check out <a
-
      href="https://github.com/sveltejs/kit#readme"
-
      target="_blank"
-
      rel="noreferrer">
-
      SvelteKit
-
    </a>
-
    , the official Svelte app framework powered by Vite!
-
  </p>
-

-
  <p class="read-the-docs">Click on the Vite and Svelte logos to learn more</p>
-
</main>
+
<Startup />
deleted src/app.css
@@ -1,79 +0,0 @@
-
:root {
-
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
-
  line-height: 1.5;
-
  font-weight: 400;
-

-
  color-scheme: light dark;
-
  color: rgba(255, 255, 255, 0.87);
-
  background-color: #242424;
-

-
  font-synthesis: none;
-
  text-rendering: optimizeLegibility;
-
  -webkit-font-smoothing: antialiased;
-
  -moz-osx-font-smoothing: grayscale;
-
}
-

-
a {
-
  font-weight: 500;
-
  color: #646cff;
-
  text-decoration: inherit;
-
}
-
a:hover {
-
  color: #535bf2;
-
}
-

-
body {
-
  margin: 0;
-
  display: flex;
-
  place-items: center;
-
  min-width: 320px;
-
  min-height: 100vh;
-
}
-

-
h1 {
-
  font-size: 3.2em;
-
  line-height: 1.1;
-
}
-

-
.card {
-
  padding: 2em;
-
}
-

-
#app {
-
  max-width: 1280px;
-
  margin: 0 auto;
-
  padding: 2rem;
-
  text-align: center;
-
}
-

-
button {
-
  border-radius: 8px;
-
  border: 1px solid transparent;
-
  padding: 0.6em 1.2em;
-
  font-size: 1em;
-
  font-weight: 500;
-
  font-family: inherit;
-
  background-color: #1a1a1a;
-
  cursor: pointer;
-
  transition: border-color 0.25s;
-
}
-
button:hover {
-
  border-color: #646cff;
-
}
-
button:focus,
-
button:focus-visible {
-
  outline: 4px auto -webkit-focus-ring-color;
-
}
-

-
@media (prefers-color-scheme: light) {
-
  :root {
-
    color: #213547;
-
    background-color: #ffffff;
-
  }
-
  a:hover {
-
    color: #747bff;
-
  }
-
  button {
-
    background-color: #f9f9f9;
-
  }
-
}
deleted src/assets/svelte.svg
@@ -1 +0,0 @@
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>

\ No newline at end of file
modified src/main.ts
@@ -1,4 +1,3 @@
-
import "./app.css";
import App from "./App.svelte";

const app = new App({
added src/views/Startup.svelte
@@ -0,0 +1,39 @@
+
<script lang="ts">
+
  import { invoke } from "@tauri-apps/api/core";
+
  import { onMount } from "svelte";
+
  import warningIcon from "/images/warning.png";
+

+
  let loading = true;
+
  let error: { err: string; hint?: string } | undefined = undefined;
+

+
  onMount(async () => {
+
    try {
+
      await invoke("authenticate");
+
    } catch (e: any) {
+
      error = e;
+
    } finally {
+
      loading = false;
+
    }
+
  });
+
</script>
+

+
<style>
+
  main {
+
    padding-top: 7rem;
+
    margin: 0 auto;
+
    width: 100%;
+
    text-align: center;
+
  }
+
</style>
+

+
<main>
+
  {#if error}
+
    <img height="32" src={warningIcon} alt="warning" />
+
    <p class="txt-medium">{error.err}</p>
+
    {#if error.hint}
+
      <p class="txt-small">{@html error.hint}</p>
+
    {/if}
+
  {:else if !loading}
+
    <p class="txt-medium">You are all set!</p>
+
  {/if}
+
</main>