mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-18 19:30:27 +08:00
Add branch browsing and selector
Signed-off-by: Sebastian Martinez <sebastinez@me.com>
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
urn: project.id,
|
||||
org,
|
||||
user,
|
||||
commit: project.anchor?.stateHash,
|
||||
revision: project.anchor?.stateHash,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
+53
-9
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user