Add branch browsing and selector

Signed-off-by: Sebastian Martinez <sebastinez@me.com>
This commit is contained in:
Sebastian Martinez
2022-01-13 10:37:01 +01:00
parent 157e025eca
commit ddf7b27f55
10 changed files with 393 additions and 175 deletions
+139
View File
@@ -0,0 +1,139 @@
<script lang="ts">
import { Info, getOid } from "@app/project";
import { formatCommit, isOid } from "@app/utils";
import { createEventDispatcher } from "svelte";
export let branches: [string, string][];
export let project: Info;
export let revision: string;
const dispatch = createEventDispatcher();
let branchesDropdown = false;
const switchBranch = (name: string) => {
dispatch("revisionChanged", name);
};
// Sort branches array alphabetically
const sortBranches = ([firstBranchName,]: [string, string], [secondBranchName,]: [string, string]) => {
if (firstBranchName < secondBranchName) return -1;
if (firstBranchName > secondBranchName) return 1;
return 0;
};
branches = branches.sort(sortBranches);
// Casting commit to string, since the commit will always be defined here
$: commit = getOid(project.head, revision, branches);
</script>
<style>
.commit {
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-family-monospace);
}
.commit .branch {
cursor: pointer;
padding: 0.5rem 0.75rem;
color: var(--color-secondary);
background-color: var(--color-secondary-background);
border-radius: 0.25rem 0 0 0.25rem;
}
.commit .branch.not-allowed {
cursor: not-allowed;
}
.branch:hover {
background-color: var(--color-foreground-background-lighter);
}
.commit .hash {
display: inline-block;
color: var(--color-secondary);
background-color: var(--color-secondary-background-darker);
padding: 0.5rem 0.75rem;
border-radius: inherit;
}
.item {
cursor: pointer;
padding: 0.3rem;
}
.item:hover {
background-color: var(--color-foreground-background-lighter);
}
.dropdown {
background-color: var(--color-foreground-background);
padding: 1rem;
margin-top: 0.5rem;
border-radius: 0.25rem;
display: none;
position: absolute;
}
.branch-dropdown.branch-dropdown-visible {
display: block;
}
.stat {
font-family: var(--font-family-monospace);
padding: 0.5rem 0.75rem;
background: var(--color-foreground-background);
}
@media (max-width: 720px) {
.dropdown {
left: 32px;
z-index: 10;
}
}
</style>
<div class="commit">
<!-- Check for branches listing feature -->
{#if branches.length > 0}
<span>
<div on:click={() => branchesDropdown = !branchesDropdown} class="stat branch" class:not-allowed={!branches}>
{#if commit === project.head}
{project.meta.defaultBranch}
<!-- If commit is no sha1 commit show branch or tag name -->
{:else if !isOid(revision)}
{revision}
{:else}
Browse...
{/if}
</div>
<div
class="dropdown branch-dropdown"
class:branch-dropdown-visible={branchesDropdown}
>
{#each branches as [name,]}
<div class="item" on:click={() => switchBranch(name)}>{name}</div>
{/each}
</div>
</span>
{#if commit === project.head || !isOid(revision)}
<div class="hash">
{formatCommit(commit)}
</div>
{:else}
<div class="hash desktop">
{commit}
</div>
<div class="hash mobile">
{formatCommit(commit)}
</div>
{/if}
<!-- If there is no branch listing available, show default branch name if commit is head and else show entire commit -->
{:else if commit === project.head}
<div class="stat branch not-allowed">
{project.meta.defaultBranch}
</div>
<div class="hash">
{formatCommit(commit)}
</div>
{:else}
<div class="hash desktop">
{commit}
</div>
<div class="hash mobile">
{formatCommit(commit)}
</div>
{/if}
</div>
+22 -8
View File
@@ -1,4 +1,5 @@
<script lang="ts">
import { createEventDispatcher, onMount } from 'svelte';
import { navigate } from 'svelte-routing';
import type { Config } from '@app/config';
import * as proj from '@app/project';
@@ -9,6 +10,8 @@
import Blob from './Blob.svelte';
import Readme from './Readme.svelte';
const dispatch = createEventDispatcher();
enum Status {
Loading,
Loaded,
@@ -18,18 +21,24 @@
| { status: Status.Loaded; path: string; blob: proj.Blob };
export let urn: string;
export let commit: string;
export let revision: string;
export let config: Config;
export let path: string;
export let org: string | null = null;
export let user: string | null = null;
export let tree: proj.Tree;
export let project: proj.Info;
export let branches: [string, string][];
// When the component is loaded the first time, the blob is yet to be loaded.
let state: State = { status: Status.Loading, path };
// Whether the mobile file tree is visible.
let mobileFileTree = false;
onMount(() => {
dispatch("routeParamsChange", { content: proj.ProjectContent.Tree, revision, path });
});
const loadBlob = async (path: string): Promise<proj.Blob> => {
if (state.status == Status.Loaded && state.path === path) {
return state.blob;
@@ -46,28 +55,30 @@
return state.blob;
};
const onSelect = async ({ detail: path }: { detail: string }) => {
const onSelect = async ({ detail: newPath }: { detail: string }) => {
// Ensure we don't spend any time in a "loading" state. This means
// the loading spinner won't be shown, and instead the blob will be
// displayed once loaded.
const blob = await loadBlob(path);
const blob = await loadBlob(newPath);
getBlob = new Promise(resolve => resolve(blob));
dispatch("routeParamsChange", { content: proj.ProjectContent.Tree, revision, path });
// Close mobile tree if user navigates to other file
mobileFileTree = false;
navigateBrowser(commit, path);
navigateBrowser(revision, newPath);
};
const navigateBrowser = (commit: string, path?: string) => {
const navigateBrowser = (revision: string, path?: string) => {
// Replaces path with current path if none passed.
if (path === undefined) path = state.path;
if (org) {
navigate(proj.path({ urn, org, commit, path }));
navigate(proj.path({ urn, org, revision, path }));
} else if (user) {
navigate(proj.path({ urn, user, commit, path }));
navigate(proj.path({ urn, user, revision, path }));
} else {
navigate(proj.path({ urn, commit, path }));
navigate(proj.path({ urn, revision, path }));
}
};
@@ -81,6 +92,9 @@
// This is reactive to respond to path changes that don't originate from this
// component, eg. when using the browser's "back" button.
$: [revision, path] = proj.splitPrefixFromPath(revision, branches, project.head);
$: commit = proj.getOid(project.head, revision, branches);
$: dispatch("routeParamsChange", { content: proj.ProjectContent.Tree, revision, path });
$: getBlob = loadBlob(path);
$: loadingPath = state.status == Status.Loading ? state.path : null;
</script>
+12 -4
View File
@@ -1,18 +1,26 @@
<script lang="ts">
import { createEventDispatcher, onMount } from "svelte";
import CommitTeaser from "./CommitTeaser.svelte";
import { getCommits } from "@app/project";
import { getCommits, Info, getOid, ProjectContent } from "@app/project";
import type { Config } from "@app/config";
import Loading from "@app/Loading.svelte";
import { groupCommitHistory, GroupedCommitsHistory } from "./lib";
export let commit: string;
export let revision: string;
export let urn: string;
export let config: Config;
export let project: Info;
export let branches: [string, string][];
async function fetchCommits(): Promise<GroupedCommitsHistory> {
const commitsQuery = await getCommits(urn, commit, config);
const commitsQuery = await getCommits(urn, getOid(project.head, revision, branches), config);
return groupCommitHistory(commitsQuery);
}
const dispatch = createEventDispatcher();
onMount(() => {
dispatch("routeParamsChange", { content: ProjectContent.History, revision, path: "/" });
});
</script>
<style>
@@ -31,7 +39,7 @@
.commit {
padding: 0.25rem 0;
}
@media (max-width: 720px) {
@media (max-width: 960px) {
.history {
padding-left: 2rem;
}
+56 -85
View File
@@ -3,18 +3,24 @@
import * as utils from '@app/utils';
import Loading from '@app/Loading.svelte';
import { ethers } from "ethers";
import type { Info, Tree } from '@app/project';
import { ProjectContent } from "@app/project";
import { ProjectContent, getOid } from '@app/project';
import type { Info, Tree } from "@app/project";
import type { Profile } from '@app/profile';
import BranchSelector from './BranchSelector.svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let config: Config;
export let anchors: string | null = null;
export let urn: string;
export let commit: string;
export let path: string;
export let project: Info;
export let profile: Profile | null = null;
export let tree: Tree;
export let branches: [string, string][] = [];
export let content: ProjectContent;
export let revision: string;
// Whether the clone dropdown is visible.
let cloneDropdown = false;
@@ -22,8 +28,12 @@
let seedDropdown = false;
// Switches between the browser and commit view
const switchContent = () => {
content = content == ProjectContent.Browser ? ProjectContent.Commits : ProjectContent.Browser;
const toggleContent = (input: ProjectContent) => {
dispatch("routeParamsChange", { content: content === input ? ProjectContent.Tree : input, revision, path });
};
const updateRevision = (newRevision: string) => {
dispatch("routeParamsChange", { content, revision: newRevision, path });
};
const GetAllAnchors = `
@@ -47,12 +57,12 @@
const unpadded = utils.decodeRadicleId(urn);
const id = ethers.utils.hexZeroPad(unpadded, 32);
const allAnchors = await utils.querySubgraph(config.orgs.subgraph, GetAllAnchors, { project: id, org: anchors });
console.log(allAnchors);
return allAnchors.anchors
.map((anchor: AnchorObject) => utils.formatProjectHash(ethers.utils.arrayify(anchor.multihash)));
}
$: getAnchor = anchors ? getAllAnchors(anchors, urn) : null;
$: commit = getOid(project.head, revision, branches);
</script>
<style>
@@ -65,37 +75,11 @@
justify-content: left;
flex-wrap: wrap;
gap: 0.5rem;
}
header > * {
border-radius: 0.25rem;
min-width: max-content;
}
.commit {
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-family-monospace);
}
.commit .branch {
padding: 0.5rem 0.75rem;
color: var(--color-secondary);
background-color: var(--color-secondary-background);
border-radius: 0.25rem 0 0 0.25rem;
}
.commit .hash {
display: inline-block;
color: var(--color-secondary);
background-color: var(--color-secondary-background);
padding: 0.5rem 0.75rem;
border-radius: inherit;
}
.branch + .hash {
background-color: var(--color-secondary-background-darker);
border-radius: 0 0.25rem 0.25rem 0;
}
.anchor {
display: inline-flex;
}
@@ -222,63 +206,50 @@
</style>
<header>
<div class="commit">
{#if commit === project.head}
<div class="branch">
{project.meta.defaultBranch}
</div>
<div class="hash">
{utils.formatCommit(commit)}
</div>
{:else}
<div class="hash desktop">
{commit}
</div>
<div class="hash mobile">
{utils.formatCommit(commit)}
</div>
{/if}
</div>
<div class="anchor">
{#if anchors}
{#await getAnchor}
<Loading small margins />
{:then anchor}
{#if anchor}
<!-- commit is head and latest anchor -->
{#if commit == anchor[0] && commit === project.head}
<span class="anchor-widget anchor-latest">
<span class="anchor-label" title="{anchors}">latest 🔐</span>
</span>
<!-- commit is not head but latest anchor -->
{:else if commit == anchor[0] && commit !== project.head}
<span class="anchor-widget" on:click={() => commit = project.head}>
<span class="anchor-label" title="{anchors}">latest 🔐</span>
</span>
<!-- commit is not head a stale anchor -->
{:else if anchor?.includes(commit)}
<span class="anchor-widget" on:click={() => commit = anchor[0]}>
<span class="anchor-label" title="{anchors}">stale 🔒</span>
</span>
<!-- commit is not anchored, could be head or any other commit -->
{#if revision}
<BranchSelector {branches} {project} {revision}
on:revisionChanged={(event) => updateRevision(event.detail)} />
<div class="anchor">
{#if anchors}
{#await getAnchor}
<Loading small margins />
{:then anchor}
{#if anchor}
<!-- commit is head and latest anchor -->
{#if commit == anchor[0] && commit === project.head}
<span class="anchor-widget anchor-latest">
<span class="anchor-label" title="{anchors}">latest 🔐</span>
</span>
<!-- commit is not head but latest anchor -->
{:else if commit == anchor[0] && commit !== project.head}
<span class="anchor-widget" on:click={() => updateRevision(project.head)}>
<span class="anchor-label" title="{anchors}">latest 🔐</span>
</span>
<!-- commit is not head a stale anchor -->
{:else if anchor?.includes(commit)}
<span class="anchor-widget" on:click={() => updateRevision(anchor[0])}>
<span class="anchor-label" title="{anchors}">stale 🔒</span>
</span>
<!-- commit is not anchored, could be head or any other commit -->
{:else}
<span class="anchor-widget not-anchored" on:click={() => updateRevision(anchor[0])}>
<span class="anchor-label">not anchored 🔓</span>
</span>
{/if}
{:else}
<span class="anchor-widget not-anchored" on:click={() => commit = anchor[0]}>
<!-- commit is not head and neither an anchor, and there are no anchors available -->
<span class="anchor-widget not-anchored not-allowed">
<span class="anchor-label">not anchored 🔓</span>
</span>
{/if}
{:else}
<!-- commit is not head and neither an anchor, and there are no anchors available -->
<span class="anchor-widget not-anchored not-allowed">
<span class="anchor-label">not anchored 🔓</span>
{:catch}
<span class="anchor-widget error" title="Not able to fetch anchor from subgraph">
<span class="anchor-label">❌</span>
</span>
{/if}
{:catch}
<span class="anchor-widget error" title="Not able to fetch anchor from subgraph">
<span class="anchor-label">❌</span>
</span>
{/await}
{/if}
</div>
{/await}
{/if}
</div>
{/if}
{#if config.seed.git.host}
<span>
<div class="clone" on:click={() => (cloneDropdown = !cloneDropdown)}>
@@ -330,7 +301,7 @@
{/if}
</div>
</span>
<div class="stat commit-count" class:active={content == ProjectContent.Commits} on:click={switchContent}>
<div class="stat commit-count" class:active={content == ProjectContent.History} on:click={() => toggleContent(ProjectContent.History)}>
<strong>{tree.stats.commits}</strong> commit(s)
</div>
<div class="stat">
@@ -0,0 +1,52 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import type { Config } from "@app/config";
import type { Info, Tree } from "@app/project";
import { Route, Router } from "svelte-routing";
import Browser from "./Browser.svelte";
import History from "./Commit/History.svelte";
export let urn: string;
export let project: Info;
export let config: Config;
export let org: string;
export let tree: Tree;
export let path: string;
export let user: string;
export let branches: [string, string][];
const dispatch = createEventDispatcher();
function forwardRouteParams({ detail: newParams }: { detail: any }) {
dispatch("routeParamsChange", newParams);
}
</script>
<Router>
<!-- The default action is to render Browser with the default branch head -->
<Route path="/">
<Browser {urn} {org} {user} {config} {tree} {project} {branches}
path={"/"}
revision={project.head}
on:routeParamsChange={forwardRouteParams} />
</Route>
<Route path="/tree">
<Browser {urn} {org} {user} {config} {tree} {project} {branches}
path={"/"}
revision={project.head}
on:routeParamsChange={forwardRouteParams} />
</Route>
<Route path="/tree/*" let:params>
<Browser {urn} {org} {user} {config} {tree} {project} {branches} {path}
revision={params["*"]}
on:routeParamsChange={forwardRouteParams} />
</Route>
<Route path="/history">
<History {urn} revision={project.head} {config} {project} {branches}
on:routeParamsChange={forwardRouteParams} />
</Route>
<Route path="/history/*" let:params>
<History {urn} revision={params["*"]} {config} {project} {branches}
on:routeParamsChange={forwardRouteParams} />
</Route>
</Router>
+7 -30
View File
@@ -4,44 +4,21 @@
import type { Config } from '@app/config';
export let config: Config;
</script>
<Route path="/projects/:urn/head/*" let:params>
<View {config} urn={params.urn} path={params['*'] || "/"} />
</Route>
<Route path="/projects/:urn/:commit/*" let:params>
<View {config} urn={params.urn} commit={params.commit} path={params['*'] || "/"} />
</Route>
<Route path="/projects/:urn" let:params>
<View {config} urn={params.urn} path="/" />
<Route path="/projects/:urn/*" let:params>
<View {config} urn={params.urn} />
</Route>
<!-- With an Org context -->
<Route path="/orgs/:org/projects/:urn/head/*" let:params>
<View {config} org={params.org} urn={params.urn} path={params['*'] || "/"} />
</Route>
<Route path="/orgs/:org/projects/:urn/:commit/*" let:params>
<View {config} org={params.org} urn={params.urn} commit={params.commit} path={params["*"] || "/"} />
</Route>
<Route path="/orgs/:org/projects/:urn" let:params>
<View {config} org={params.org} urn={params.urn} path="/" />
<Route path="/orgs/:org/projects/:urn/*" let:params>
<View {config} org={params.org} urn={params.urn} />
</Route>
<!-- With a User context -->
<Route path="/users/:user/projects/:urn/head/*" let:params>
<View {config} user={params.user} urn={params.urn} path={params['*'] || "/"} />
</Route>
<Route path="/users/:user/projects/:urn/:commit/*" let:params>
<View {config} user={params.user} urn={params.urn} commit={params.commit} path={params["*"] || "/"} />
</Route>
<Route path="/users/:user/projects/:urn" let:params>
<View {config} user={params.user} urn={params.urn} path="/" />
<Route path="/users/:user/projects/:urn/*" let:params>
<View {config} user={params.user} urn={params.urn} />
</Route>
+33 -25
View File
@@ -8,22 +8,22 @@
import { Profile, ProfileType } from '@app/profile';
import type { Info } from '@app/project';
import { formatOrg } from '@app/utils';
import { getOid } from '@app/project';
import Browser from './Browser.svelte';
import Header from './Header.svelte';
import History from "./Commit/History.svelte";
import Header from '@app/base/projects/Header.svelte';
import ProjectContentRoutes from '@app/base/projects/ProjectContentRoutes.svelte';
export let urn: string;
export let org = "";
export let user = "";
export let commit = "";
export let config: Config;
export let path: string;
let content = proj.ProjectContent.Browser;
let parentName = formatOrg(org || user, config);
let pageTitle = parentName ? `${parentName}/${urn}` : urn;
let projectInfo: Info | null = null;
let revision: string;
let content: proj.ProjectContent;
let path: string;
let getProject = new Promise<Profile | null>(resolve => {
if (org) {
Profile.get(org, ProfileType.Project, config).then(p => resolve(p));
@@ -36,11 +36,14 @@
const seed = profile?.seed;
const cfg = seed ? config.withSeed(seed) : config;
const info = await proj.getInfo(urn, cfg);
commit = commit ? commit : info.head;
projectInfo = info;
return { project: info, config: cfg, profile };
// Checks for delegates returned from seed node, as feature check of the seed node
if (info.meta.delegates) {
const branches = await proj.getBranchesByPeer(urn, info.meta.delegates[0], cfg);
return { project: info, branches: [...Object.entries(branches.heads)], peer: info.meta.delegates[0], config: cfg, profile };
}
return { project: info, branches: Array([info.meta.defaultBranch, info.head]) as [string, string][], config: cfg, profile };
});
const parentUrl = (profile: Profile) => {
@@ -61,11 +64,20 @@
}
}
function updateRouteParams({ detail: newParams }: { detail: { path: string; revision: string; content: proj.ProjectContent } }) {
let newLocation = proj.path({ urn, user, org, content: newParams.content, revision: newParams.revision, path: newParams.path });
if (newLocation !== window.location.pathname) {
navigate(newLocation);
}
if (content !== newParams.content) content = newParams.content;
if (revision !== newParams.revision) revision = newParams.revision;
if (path !== newParams.path) path = newParams.path;
}
const back = () => window.history.back();
// React to changes to the project commit. We have to manually
// set the URL as well, to match the current commit.
$: projectRoot = proj.path({ urn, user, org, commit });
$: navigate(proj.path({ urn, user, org, commit, path }));
$: projectRoot = proj.path({ urn, user, org });
</script>
<style>
@@ -149,23 +161,19 @@
<div class="urn">{urn}</div>
<div class="description">{result.project.meta.description}</div>
</header>
{#await proj.getTree(urn, commit, "/", config)}
<!-- Loading -->
{:then tree}
<Header {urn} {tree}
{#await proj.getTree(urn, getOid(result.project.head, revision, result.branches), "/", config) then tree}
<Header {urn} {tree} {revision} {content} {path}
anchors={result.profile?.anchorsAccount ?? org}
config={result.config}
project={result.project}
bind:commit={commit}
bind:content={content}
/>
{#if content == proj.ProjectContent.Browser}
<Browser {urn} {org} {user} {path} {tree}
commit={commit}
config={result.config} />
{:else if content == proj.ProjectContent.Commits}
<History {urn} config={result.config} bind:commit={commit} />
{/if}
branches={result.branches}
profile={result.profile}
on:routeParamsChange={updateRouteParams} />
<ProjectContentRoutes {urn} {org} {user} {tree} {path}
project={result.project}
branches={result.branches}
config={result.config}
on:routeParamsChange={updateRouteParams} />
{:catch err}
<div class="container center-content">
<div class="error error-message text-xsmall">
+1 -1
View File
@@ -41,7 +41,7 @@
urn: project.id,
org,
user,
commit: project.anchor?.stateHash,
revision: project.anchor?.stateHash,
})
);
}
+53 -9
View File
@@ -1,8 +1,11 @@
import type { Config } from '@app/config';
import * as api from '@app/api';
import type { CommitsHistory } from '@app/base/projects/Commit/lib';
import { isOid } from '@app/utils';
export type Urn = string;
export type Peer = string;
export type Branch = { [key: string]: string };
export interface Project {
id: string;
@@ -18,8 +21,8 @@ export interface PendingProject extends Project {
// Enumerates the space below the Header component in the projects View component
export enum ProjectContent {
Browser,
Commits,
Tree,
History,
}
export interface Info {
@@ -32,6 +35,7 @@ export interface Meta {
description: string;
defaultBranch: string;
maintainers: Urn[];
delegates: Peer[];
}
export interface Tree {
@@ -85,6 +89,10 @@ export interface Blob {
info: EntryInfo;
}
export interface Branches {
heads: Branch;
}
export async function getInfo(urn: string, config: Config): Promise<Info> {
return api.get(`projects/${urn}`, {}, config);
}
@@ -97,15 +105,17 @@ export async function getProjects(config: Config): Promise<any> {
return api.get("projects", {}, config);
}
export async function getBranchesByPeer(urn: string, peer: string, config: Config): Promise<Branches> {
return api.get(`projects/${urn}/remotes/${peer}`, {}, config);
}
export async function getTree(
urn: string,
commit: string,
path: string,
config: Config
): Promise<Tree> {
if (path === "/") {
path = "";
}
if (path === "/") path = "";
return api.get(`projects/${urn}/tree/${commit}/${path}`, {}, config);
}
@@ -128,9 +138,9 @@ export async function getReadme(
}
export function path(
opts: { urn: string; org?: string; user?: string; commit?: string; path?: string }
opts: { urn: string; org?: string; content?: ProjectContent; user?: string; revision?: string; path?: string }
): string {
const { urn, org, user, commit, path } = opts;
const { urn, org, user, content, revision, path } = opts;
const result = [];
if (org) {
@@ -140,8 +150,18 @@ export function path(
}
result.push("projects", urn);
if (commit) {
result.push(commit);
switch (content) {
case ProjectContent.History:
result.push("history");
break;
default:
result.push("tree");
break;
}
if (revision) {
result.push(revision);
} else if (path) {
result.push("head");
}
@@ -152,3 +172,27 @@ export function path(
}
return "/" + result.join("/");
}
// We need a SHA1 commit in some places, so we return early if the revision is a SHA and else we look into branches.
// As fallback we use the head commit.
export function getOid(head: string, revision: string, branches?: [string, string][]): string {
if (isOid(revision)) return revision;
if (branches) {
const branch = branches.find(([name,]) => name === revision);
return branch ? branch[1] : head;
}
return head;
}
// Splits the path consisting of a revision (eg. branch or commit) and file path into a tuple [revision, file-path]
export function splitPrefixFromPath(input: string, branches: [string, string][], head: string): [string, string] {
const branch = branches.find(([branchName,]) => input.startsWith(branchName));
const commitPath = [input.slice(0, 40), input.slice(41)];
if (branch) {
const [rev, path] = [input.slice(0, branch[0].length), input.slice(branch[0].length + 1)];
return [rev, path ? path : "/"];
} else if (isOid(commitPath[0])) {
return [commitPath[0], commitPath[1] ? commitPath[1] : "/"];
}
return [head, "/"];
}
+18 -13
View File
@@ -28,10 +28,10 @@ export interface Safe {
}
export interface SafeTransaction {
to: string;
value: string;
data: string;
operation: number;
to: string;
value: string;
data: string;
operation: number;
}
export interface Token {
@@ -43,17 +43,17 @@ export interface Token {
}
export enum Status {
Signing,
Pending,
Success,
Failed,
}
Signing,
Pending,
Success,
Failed,
}
export type State =
{ status: Status.Signing }
| { status: Status.Pending }
| { status: Status.Success }
| { status: Status.Failed; error: string };
{ status: Status.Signing }
| { status: Status.Pending }
| { status: Status.Success }
| { status: Status.Failed; error: string };
export async function isReverseRecordSet(address: string, domain: string, config: Config): Promise<boolean> {
const name = await config.provider.lookupAddress(address);
@@ -175,6 +175,11 @@ export function isRadicleId(input: string): boolean {
return /^rad:[a-z]+:[a-zA-Z0-9]+$/.test(input);
}
// Check whether the input is a SHA1 commit.
export function isOid(input: string): boolean {
return /^[a-fA-F0-9]{40}$/.test(input);
}
// Check whether the input is a URL.
export function isUrl(input: string): boolean {
return /^https?:\/\//.test(input);