Refactor projects

Signed-off-by: Sebastian Martinez <sebastinez@me.com>
This commit is contained in:
Sebastian Martinez
2022-02-21 22:42:52 -03:00
parent 935044ac4c
commit ee507215fb
10 changed files with 217 additions and 217 deletions
+1
View File
@@ -5,6 +5,7 @@ import { styles } from "@test/support/index";
const defaultProps = {
project: {
head: "e678629cd37c770c640a2cd997fc76303c815772",
urn: "rad:git:hnrkqdpm9ub19oc8dccx44echy76hzfsezyio",
name: "nakamoto",
description: "Privacy-preserving Bitcoin light-client implementation in Rust",
defaultBranch: "master",
+7 -9
View File
@@ -17,15 +17,13 @@
{ status: Status.Loading; path: string }
| { status: Status.Loaded; path: string; blob: proj.Blob };
export let source: proj.Source;
export let project: proj.Project;
export let tree: proj.Tree;
export let browserStore: Readable<proj.Browser>;
const { urn, project } = source;
$: browser = $browserStore;
$: path = browser.path || "/";
$: revision = browser.revision || source.branches[project.head];
$: revision = browser.revision || project.branches[project.head];
// When the component is loaded the first time, the blob is yet to be loaded.
let state: State = { status: Status.Loading, path };
@@ -39,8 +37,8 @@
const isMarkdownPath = utils.isMarkdownPath(path);
const promise = path === "/"
? proj.getReadme(urn, commit, source.seed.api)
: proj.getBlob(urn, commit, path, { highlight: !isMarkdownPath }, source.seed.api);
? project.getReadme(commit)
: project.getBlob(commit, path, { highlight: !isMarkdownPath });
state = { status: Status.Loading, path };
state = { status: Status.Loaded, path, blob: await promise };
@@ -59,19 +57,19 @@
mobileFileTree = false;
if (path) {
proj.navigateTo({ path: newPath, revision }, source);
project.navigateTo({ path: newPath, revision });
}
};
const fetchTree = async (path: string) => {
return proj.getTree(urn, commit, path, source.seed.api);
return project.getTree(commit, path);
};
const toggleMobileFileTree = () => {
mobileFileTree = !mobileFileTree;
};
$: commit = proj.getOid(revision, source.branches) || project.head;
$: commit = proj.getOid(revision, project.branches) || project.head;
$: getBlob = loadBlob(path);
$: loadingPath = state.status == Status.Loading ? state.path : null;
</script>
+4 -5
View File
@@ -4,16 +4,15 @@
import { formatCommitTime } from "@app/commit";
import { formatCommit } from "@app/utils";
export let source: proj.Source;
export let project: proj.Project;
export let commit: string;
const { seed, urn } = source;
const onBrowse = (event: { detail: string }) => {
proj.navigateTo({
project.navigateTo({
content: proj.ProjectContent.Tree,
revision: commit,
path: event.detail
}, source);
});
};
</script>
@@ -61,7 +60,7 @@
}
</style>
{#await proj.getCommit(urn, commit, seed.api) then commit}
{#await project.getCommit(commit) then commit}
<div class="commit">
<header>
<div class="summary">
+8 -9
View File
@@ -2,20 +2,19 @@
import type { Writable } from 'svelte/store';
import { navigate } from 'svelte-routing';
import * as utils from '@app/utils';
import * as proj from '@app/project';
import { Browser, ProjectContent, Source } from '@app/project';
import { Browser, ProjectContent, Project } from '@app/project';
import AnchorBadge from '@app/base/profiles/AnchorBadge.svelte';
import type { Tree } from "@app/project";
import BranchSelector from './BranchSelector.svelte';
import PeerSelector from './PeerSelector.svelte';
export let source: Source;
export let project: Project;
export let tree: Tree;
export let commit: string;
export let browserStore: Writable<Browser>;
export let peerSelector: boolean; // If peerSelector should be showed.
let { urn, project, peers, seed, anchors } = source;
let { urn, peers, branches, seed, anchors } = project;
$: browser = $browserStore;
$: revision = browser.revision || commit;
@@ -31,19 +30,19 @@
// Switches between the browser and commit view.
const toggleContent = (input: ProjectContent) => {
proj.navigateTo({
project.navigateTo({
content: content === input ? ProjectContent.Tree : input
}, source);
});
};
const updatePeer = (peer: string) => {
dropdownState.peer = false;
proj.navigateTo({ peer, revision: null }, source);
project.navigateTo({ peer, revision: null });
};
const updateRevision = (revision: string) => {
dropdownState.branch = false;
proj.navigateTo({ revision }, source);
project.navigateTo({ revision });
};
</script>
@@ -157,7 +156,7 @@
bind:peersDropdown={dropdownState.peer}
on:peerChanged={(event) => updatePeer(event.detail)} />
{/if}
<BranchSelector branches={source.branches} {project} {revision} {toggleDropdown}
<BranchSelector {branches} {project} {revision} {toggleDropdown}
bind:branchesDropdown={dropdownState.branch}
on:branchChanged={(event) => updateRevision(event.detail)} />
<div class="anchor">
+5 -7
View File
@@ -1,20 +1,18 @@
<script lang="ts">
import CommitTeaser from "./Commit/CommitTeaser.svelte";
import { getCommits, Source, ProjectContent } from "@app/project";
import * as proj from "@app/project";
import type { Project } from "@app/project";
import { ProjectContent } from "@app/project";
import Loading from "@app/Loading.svelte";
import { groupCommitHistory, GroupedCommitsHistory } from "@app/commit";
export let source: Source;
export let project: Project;
export let commit: string;
let { urn, seed } = source;
const navigateHistory = (revision: string, content?: ProjectContent) => {
proj.navigateTo({ content, revision, path: null }, source);
project.navigateTo({ content, revision, path: null });
};
const fetchCommits = async (parentCommit: string): Promise<GroupedCommitsHistory> => {
const commitsQuery = await getCommits(urn, parentCommit, seed.api);
const commitsQuery = await project.getCommits(parentCommit);
return groupCommitHistory(commitsQuery);
};
</script>
+16 -18
View File
@@ -14,22 +14,20 @@
export let peer: string | null = null;
export let config: Config;
export let source: proj.Source;
export let project: proj.Project;
export let content: proj.ProjectContent;
export let revision: string;
const project = source.project;
let parentName = source.profile ? formatProfile(source.profile.nameOrAddress, config) : null;
let parentName = project.profile ? formatProfile(project.profile.nameOrAddress, config) : null;
let pageTitle = parentName ? `${parentName}/${project.name}` : project.name;
function rootPath(source: proj.Source): string {
return proj.pathTo({
function rootPath(): string {
return project.pathTo({
content: proj.ProjectContent.Tree,
peer: null,
path: "/",
revision: null,
}, source);
});
}
const baseName = parentName
@@ -107,30 +105,30 @@
<header>
<div class="title bold">
{#if source.profile}
<a class="org-avatar" title={source.profile.nameOrAddress} href="/{source.profile.nameOrAddress}">
<Avatar source={source.profile.avatar || source.profile.address} address={source.profile.address}/>
{#if project.profile}
<a class="org-avatar" title={project.profile.nameOrAddress} href="/{project.profile.nameOrAddress}">
<Avatar source={project.profile.avatar || project.profile.address} address={project.profile.address}/>
</a>
<span class="divider">/</span>
{/if}
<Link to={rootPath(source)}>{source.project.name}</Link>
<Link to={rootPath()}>{project.name}</Link>
{#if peer}
<span class="divider" title={peer}>/ {formatSeedId(peer)}</span>
{/if}
</div>
<div class="urn">{source.urn}</div>
<div class="description">{source.project.description}</div>
<div class="urn">{project.urn}</div>
<div class="description">{project.description}</div>
</header>
{#await proj.getRoot(source.project, revision, source.branches, source.seed.api) then { tree, commit }}
<Header {tree} {commit} {browserStore} {source} peerSelector={! source.profile} />
{#await project.getRoot(revision) then { tree, commit }}
<Header {tree} {commit} {browserStore} {project} peerSelector={! project.profile} />
{#if content == proj.ProjectContent.Tree}
<Browser {source} {tree} {browserStore} />
<Browser {project} {tree} {browserStore} />
{:else if content == proj.ProjectContent.History}
<History {source} {commit} />
<History {project} {commit} />
{:else if content == proj.ProjectContent.Commit}
<Commit {source} {commit} />
<Commit {project} {commit} />
{/if}
{:catch err}
<div class="container center-content">
+4 -4
View File
@@ -10,15 +10,15 @@
export let revision: string | null = null;
export let peer: string | null;
export let content: proj.ProjectContent = proj.ProjectContent.Tree;
export let source: proj.Source;
export let project: proj.Project;
export let config: Config;
const browse: proj.BrowseTo = { content, peer, path: "/" };
const head = source.branches[source.project.defaultBranch];
const head = project.branches[project.defaultBranch];
// route is passed when the URL has more params after e.g. /tree or /history
$: if (route) {
const { path, revision } = proj.parseRoute(route, source.branches);
const { path, revision } = proj.parseRoute(route, project.branches);
if (path) browse.path = path;
if (revision) browse.revision = revision;
@@ -36,6 +36,6 @@
peer={browser.peer}
revision={browser.revision || head}
content={browser.content}
{source}
{project}
{config}
/>
+10 -45
View File
@@ -1,11 +1,8 @@
<script lang="ts">
import type { Config } from '@app/config';
import { Route, Router } from "svelte-routing";
import * as proj from '@app/project';
import { Project, ProjectContent } from '@app/project';
import Loading from '@app/Loading.svelte';
import { Profile, ProfileType } from '@app/profile';
import { isRadicleId } from '@app/utils';
import { Seed } from '@app/base/seeds/Seed';
import NotFound from '@app/NotFound.svelte';
import ProjectRoute from "./ProjectRoute.svelte";
@@ -15,38 +12,6 @@
export let profileName: string | null = null; // Address or name of parent profile.
export let peer: string | null = null;
export let config: Config;
const getProject = async (peer: string | null): Promise<proj.Source> => {
const profile = profileName ? await Profile.get(profileName, ProfileType.Project, config) : null;
const seed = profile ? profile.seed : seedHost ? await Seed.lookup(seedHost, config) : null;
if (!profile && !seed) {
throw new Error("Couldn't load project");
}
if (! seed?.valid) {
throw new Error("Couldn't load project: invalid seed");
}
const project = await proj.getInfo(id, seed.api);
const urn = isRadicleId(id) ? id : project.urn;
const anchors = profile ? await profile.confirmedProjectAnchors(urn, config) : [];
// Older versions of http-api don't include the URN.
if (! project.urn) project.urn = urn;
const peers: proj.PeerId[] = project.delegates
? await proj.getRemotes(urn, seed.api)
: [];
let remote: proj.Remote = {
heads: { [project.defaultBranch]: project.head }
};
if (peer) {
remote = await proj.getRemote(urn, peer, seed.api);
}
return { urn, seed, project, peers, branches: remote.heads, profile, anchors };
};
</script>
<style>
@@ -72,35 +37,35 @@
</style>
<main>
{#await getProject(peer)}
{#await Project.get(id, peer, profileName, seedHost, config)}
<header>
<Loading center />
</header>
{:then source}
{:then project}
<Router>
<!-- The default action is to render Browser with the default branch head -->
<Route path="/">
<ProjectRoute content={proj.ProjectContent.Tree} {peer} {source} {config} />
<ProjectRoute content={ProjectContent.Tree} {peer} {project} {config} />
</Route>
<Route path="/tree">
<ProjectRoute content={proj.ProjectContent.Tree} {peer} {source} {config} />
<ProjectRoute content={ProjectContent.Tree} {peer} {project} {config} />
</Route>
<Route path="/tree/*" let:params>
<ProjectRoute route={params["*"]} content={proj.ProjectContent.Tree} {peer} {source} {config} />
<ProjectRoute route={params["*"]} content={ProjectContent.Tree} {peer} {project} {config} />
</Route>
<Route path="/history">
<ProjectRoute content={proj.ProjectContent.History} {peer} {source} {config} />
<ProjectRoute content={ProjectContent.History} {peer} {project} {config} />
</Route>
<Route path="/history/*" let:params>
<ProjectRoute route={params["*"]} content={proj.ProjectContent.History} {peer} {source} {config} />
<ProjectRoute route={params["*"]} content={ProjectContent.History} {peer} {project} {config} />
</Route>
<Route path="/commits/:commit" let:params>
<ProjectRoute revision={params.commit} content={proj.ProjectContent.Commit} {peer} {source} {config} />
<ProjectRoute revision={params.commit} content={ProjectContent.Commit} {peer} {project} {config} />
</Route>
<Route path="/commits/*" let:params>
<ProjectRoute route={params["*"]} content={proj.ProjectContent.Commit} {peer} {source} {config} />
<ProjectRoute route={params["*"]} content={ProjectContent.Commit} {peer} {project} {config} />
</Route>
</Router>
{:catch}
+5 -5
View File
@@ -1,6 +1,6 @@
import * as api from '@app/api';
import type { Config } from '@app/config';
import * as proj from '@app/project';
import * as proj from "@app/project";
import { isDomain } from '@app/utils';
import { assert } from '@app/error';
@@ -65,15 +65,15 @@ export class Seed {
}
async getProject(urn: string): Promise<proj.ProjectInfo> {
return proj.getInfo(urn, this.api);
return proj.Project.getInfo(urn, this.api);
}
async getProjects(id?: string): Promise<proj.ProjectInfo[]> {
const result = id
? await proj.getDelegateProjects(id, this.api)
: await proj.getProjects(this.api);
? await proj.Project.getDelegateProjects(id, this.api)
: await proj.Project.getProjects(this.api);
return result.map((project: any) => ({ ...project, id: project.urn }));
return result.map((project: proj.ProjectInfo) => ({ ...project, id: project.urn }));
}
static async getPeer({ host, port }: api.Host): Promise<{ id: string }> {
+157 -115
View File
@@ -2,9 +2,10 @@ import { navigate } from 'svelte-routing';
import { get, writable } from 'svelte/store';
import * as api from '@app/api';
import type { Commit, CommitHeader, CommitsHistory } from '@app/commit';
import { isOid } from '@app/utils';
import type { Profile } from '@app/profile';
import type { Seed } from '@app/base/seeds/Seed';
import { isOid, isRadicleId } from '@app/utils';
import { Profile, ProfileType } from '@app/profile';
import { Seed } from '@app/base/seeds/Seed';
import type { Config } from '@app/config';
export type Urn = string;
export type PeerId = string;
@@ -28,17 +29,6 @@ export interface PendingAnchor {
};
}
// Params to render correctly source code related views
export interface Source {
urn: string;
project: ProjectInfo;
peers: PeerId[];
branches: Branches;
anchors: string[];
seed: Seed;
profile?: Profile | null;
}
// Enumerates the space below the Header component in the projects View component
export enum ProjectContent {
Tree,
@@ -129,107 +119,6 @@ export function browse(browse: BrowseTo): void {
browserStore.set({ ...browser, ...browse });
}
export function pathTo(browse: BrowseTo, source: Source): string {
const browser = get(browserStore);
const options: PathOptions = {
urn: source.urn,
...browser,
...browse
};
if (source.profile) {
options.profile = source.profile?.nameOrAddress;
} else {
options.seed = source.seed.host;
}
return path(options);
}
export function navigateTo(browse: BrowseTo, source: Source): void {
navigate(pathTo(browse, source));
}
export async function getInfo(nameOrUrn: string, host: api.Host): Promise<ProjectInfo> {
const info = await api.get(`projects/${nameOrUrn}`, {}, host);
return {
...info,
...info.meta // Nb. This is only needed while we are upgrading to the new http-api.
};
}
export async function getCommits(urn: string, commit: string, host: api.Host): Promise<CommitsHistory> {
return api.get(`projects/${urn}/commits?from=${commit}`, {}, host);
}
export async function getCommit(urn: string, commit: string, host: api.Host): Promise<Commit> {
return api.get(`projects/${urn}/commits/${commit}`, {}, host);
}
export async function getProjects(host: api.Host): Promise<ProjectInfo[]> {
return api.get("projects", {}, host);
}
export async function getDelegateProjects(delegate: string, host: api.Host): Promise<ProjectInfo[]> {
return api.get(`delegates/${delegate}/projects`, {}, host);
}
export async function getRemote(urn: string, peer: string, host: api.Host): Promise<Remote> {
return api.get(`projects/${urn}/remotes/${peer}`, {}, host);
}
export async function getRemotes(urn: string, host: api.Host): Promise<PeerId[]> {
return api.get(`projects/${urn}/remotes`, {}, host);
}
export async function getRoot(
project: ProjectInfo,
revision: string | null,
heads: Branches,
host: api.Host
): Promise<{ tree: Tree; commit: string }> {
const urn = project.urn;
const head = heads[project.defaultBranch];
const commit = revision ? getOid(revision, heads) : head;
if (! commit) {
throw new Error(`Revision ${revision} not found`);
}
const tree = await getTree(urn, commit, "/", host);
return { tree, commit };
}
export async function getTree(
urn: string,
commit: string,
path: string,
host: api.Host
): Promise<Tree> {
if (path === "/") path = "";
return api.get(`projects/${urn}/tree/${commit}/${path}`, {}, host);
}
export async function getBlob(
urn: string,
commit: string,
path: string,
options: { highlight: boolean },
host: api.Host
): Promise<Blob> {
return api.get(`projects/${urn}/blob/${commit}/${path}`, options, host);
}
export async function getReadme(
urn: string,
commit: string,
host: api.Host
): Promise<Blob> {
return api.get(`projects/${urn}/readme/${commit}`, {}, host);
}
export function path(opts: PathOptions): string {
const { urn, profile, seed, peer, content, revision, path } = opts;
const result = [];
@@ -304,3 +193,156 @@ export function parseRoute(input: string, branches: Branches): { path?: string;
}
return parsed;
}
export class Project implements ProjectInfo {
urn: string;
head: string;
name: string;
description: string;
defaultBranch: string;
maintainers: Urn[];
delegates: PeerId[];
seed: Seed;
peers: string[];
branches: Branches;
profile: Profile | null;
anchors: string[];
constructor(urn: string, info: ProjectInfo, seed: Seed, peers: string[], branches: Branches, profile: Profile | null, anchors: string[]) {
this.urn = urn;
this.head = info.head;
this.name = info.name;
this.description = info.description;
this.defaultBranch = info.defaultBranch;
this.maintainers = info.maintainers;
this.delegates = info.delegates;
this.seed = seed;
this.peers = peers;
this.branches = branches;
this.profile = profile;
this.anchors = anchors;
}
async getRoot(
revision: string | null,
): Promise<{ tree: Tree; commit: string }> {
const head = this.branches[this.defaultBranch];
const commit = revision ? getOid(revision, this.branches) : head;
if (! commit) {
throw new Error(`Revision ${revision} not found`);
}
const tree = await this.getTree(commit, "/");
return { tree, commit };
}
static async getInfo(nameOrUrn: string, host: api.Host): Promise<ProjectInfo> {
const info = await api.get(`projects/${nameOrUrn}`, {}, host);
return {
...info,
...info.meta // Nb. This is only needed while we are upgrading to the new http-api.
};
}
static async getProjects(host: api.Host): Promise<ProjectInfo[]> {
return api.get("projects", {}, host);
}
static async getDelegateProjects(delegate: string, host: api.Host): Promise<ProjectInfo[]> {
return api.get(`delegates/${delegate}/projects`, {}, host);
}
static async getRemote(urn: string, peer: string, host: api.Host): Promise<Remote> {
return api.get(`projects/${urn}/remotes/${peer}`, {}, host);
}
static async getRemotes(urn: string, host: api.Host): Promise<PeerId[]> {
return api.get(`projects/${urn}/remotes`, {}, host);
}
async getCommits(commit: string): Promise<CommitsHistory> {
return api.get(`projects/${this.urn}/commits?from=${commit}`, {}, this.seed.api);
}
async getCommit(commit: string): Promise<Commit> {
return api.get(`projects/${this.urn}/commits/${commit}`, {}, this.seed.api);
}
async getTree(
commit: string,
path: string,
): Promise<Tree> {
if (path === "/") path = "";
return api.get(`projects/${this.urn}/tree/${commit}/${path}`, {}, this.seed.api);
}
async getBlob(
commit: string,
path: string,
options: { highlight: boolean },
): Promise<Blob> {
return api.get(`projects/${this.urn}/blob/${commit}/${path}`, options, this.seed.api);
}
async getReadme(
commit: string,
): Promise<Blob> {
return api.get(`projects/${this.urn}/readme/${commit}`, {}, this.seed.api);
}
navigateTo(browse: BrowseTo): void {
navigate(this.pathTo(browse));
}
pathTo(browse: BrowseTo): string {
const browser = get(browserStore);
const options: PathOptions = {
urn: this.urn,
...browser,
...browse
};
if (this.profile) {
options.profile = this.profile?.nameOrAddress;
} else {
options.seed = this.seed.host;
}
return path(options);
}
static async get(id: string, peer: string | null, profileName: string | null, seedHost: string | null, config: Config): Promise<Project> {
const profile = profileName ? await Profile.get(profileName, ProfileType.Project, config) : null;
const seed = profile ? profile.seed : seedHost ? await Seed.lookup(seedHost, config) : null;
if (!profile && !seed) {
throw new Error("Couldn't load project");
}
if (! seed?.valid) {
throw new Error("Couldn't load project: invalid seed");
}
const info = await Project.getInfo(id, seed.api);
const urn = isRadicleId(id) ? id : info.urn;
const anchors = profile ? await profile.confirmedProjectAnchors(urn, config) : [];
// Older versions of http-api don't include the URN.
if (! info.urn) info.urn = urn;
const peers: PeerId[] = info.delegates
? await Project.getRemotes(urn, seed.api)
: [];
let remote: Remote = {
heads: { [info.defaultBranch]: info.head }
};
if (peer) {
remote = await Project.getRemote(urn, peer, seed.api);
}
return new Project(urn, info, seed, peers, remote.heads, profile, anchors);
}
}