| 1 |
// The Diffusion view (oxygenhub-vscode#5, oxygenhub docs/diffusion.md): for |
| 2 |
// each repository, the subrepos it takes from and the repositories it |
| 3 |
// diffuses to, each with the hub's pill in words and its open bump. |
| 4 |
|
| 5 |
import * as vscode from "vscode"; |
| 6 |
import type * as api from "./hubapi"; |
| 7 |
import { HubAuth } from "./auth"; |
| 8 |
import { hubUrl } from "./hub"; |
| 9 |
import type { HubRepo } from "./proposals"; |
| 10 |
import { look, pinDescription, pinLabel, pinTooltip, tone } from "./diffusionview"; |
| 11 |
|
| 12 |
type Side = "takes" | "gives"; |
| 13 |
|
| 14 |
type Node = |
| 15 |
| { kind: "repo"; repo: HubRepo } |
| 16 |
| { kind: "side"; repo: HubRepo; side: Side; pins: api.DiffusionPin[] } |
| 17 |
| { kind: "pin"; repo: HubRepo; side: Side; pin: api.DiffusionPin } |
| 18 |
| { kind: "none"; repo: HubRepo }; |
| 19 |
|
| 20 |
export type DiffusionPinNode = Extract<Node, { kind: "pin" }>; |
| 21 |
|
| 22 |
export class DiffusionProvider implements vscode.TreeDataProvider<Node> { |
| 23 |
private readonly emitter = new vscode.EventEmitter<void>(); |
| 24 |
readonly onDidChangeTreeData = this.emitter.event; |
| 25 |
|
| 26 |
constructor( |
| 27 |
private readonly repos: () => Promise<HubRepo[]>, |
| 28 |
private readonly auth: HubAuth, |
| 29 |
) {} |
| 30 |
|
| 31 |
refresh(): void { |
| 32 |
this.emitter.fire(); |
| 33 |
} |
| 34 |
|
| 35 |
getTreeItem(node: Node): vscode.TreeItem { |
| 36 |
switch (node.kind) { |
| 37 |
case "repo": { |
| 38 |
const item = new vscode.TreeItem(`${node.repo.owner}/${node.repo.name}`, vscode.TreeItemCollapsibleState.Expanded); |
| 39 |
item.iconPath = new vscode.ThemeIcon("repo"); |
| 40 |
item.contextValue = "diffusionRepo"; |
| 41 |
return item; |
| 42 |
} |
| 43 |
case "side": { |
| 44 |
const item = new vscode.TreeItem(node.side === "takes" ? "Takes from" : "Diffuses to", vscode.TreeItemCollapsibleState.Expanded); |
| 45 |
item.description = String(node.pins.length); |
| 46 |
item.iconPath = new vscode.ThemeIcon(node.side === "takes" ? "arrow-down" : "arrow-up"); |
| 47 |
return item; |
| 48 |
} |
| 49 |
case "pin": { |
| 50 |
const t = tone(node.pin); |
| 51 |
const l = look(t); |
| 52 |
const item = new vscode.TreeItem(pinLabel(node.pin, node.side), vscode.TreeItemCollapsibleState.None); |
| 53 |
item.description = pinDescription(node.pin); |
| 54 |
item.iconPath = new vscode.ThemeIcon(l.icon, new vscode.ThemeColor(l.color)); |
| 55 |
item.tooltip = new vscode.MarkdownString(pinTooltip(node.pin)); |
| 56 |
item.contextValue = node.pin.bump !== null && node.pin.bump !== undefined ? "diffusionPinBump" : "diffusionPin"; |
| 57 |
item.command = { title: "Open", command: "oxygenhub.openDiffusionPin", arguments: [node] }; |
| 58 |
return item; |
| 59 |
} |
| 60 |
case "none": { |
| 61 |
const item = new vscode.TreeItem("Neither pins nor is pinned by a repository on the hub", vscode.TreeItemCollapsibleState.None); |
| 62 |
item.iconPath = new vscode.ThemeIcon("info"); |
| 63 |
return item; |
| 64 |
} |
| 65 |
} |
| 66 |
} |
| 67 |
|
| 68 |
async getChildren(node?: Node): Promise<Node[]> { |
| 69 |
if (!node) { |
| 70 |
return (await this.repos()).map((repo) => ({ kind: "repo", repo })); |
| 71 |
} |
| 72 |
if (node.kind === "repo") { |
| 73 |
try { |
| 74 |
const d = await this.auth.clientFor(node.repo.base).diffusion(node.repo.owner, node.repo.name); |
| 75 |
const out: Node[] = []; |
| 76 |
if (d.takes_from.length > 0) { |
| 77 |
out.push({ kind: "side", repo: node.repo, side: "takes", pins: d.takes_from }); |
| 78 |
} |
| 79 |
if (d.diffuses_to.length > 0) { |
| 80 |
out.push({ kind: "side", repo: node.repo, side: "gives", pins: d.diffuses_to }); |
| 81 |
} |
| 82 |
return out.length > 0 ? out : [{ kind: "none", repo: node.repo }]; |
| 83 |
} catch (e) { |
| 84 |
void vscode.window.showWarningMessage(`OxygenHub: ${(e as Error).message}`); |
| 85 |
return []; |
| 86 |
} |
| 87 |
} |
| 88 |
if (node.kind === "side") { |
| 89 |
return node.pins.map((pin) => ({ kind: "pin", repo: node.repo, side: node.side, pin })); |
| 90 |
} |
| 91 |
return []; |
| 92 |
} |
| 93 |
} |
| 94 |
|
| 95 |
/** Where a row leads on the hub: its open bump, else the other repository. */ |
| 96 |
export function pinUrl(node: DiffusionPinNode): string { |
| 97 |
const [owner, name] = (node.side === "takes" ? node.pin.upstream : node.pin.consumer).split("/"); |
| 98 |
const [cOwner, cName] = node.pin.consumer.split("/"); |
| 99 |
if (node.pin.bump !== null && node.pin.bump !== undefined) { |
| 100 |
return hubUrl(node.repo.base, cOwner, cName, { kind: "proposal", number: node.pin.bump }); |
| 101 |
} |
| 102 |
return hubUrl(node.repo.base, owner, name, { kind: "repository" }); |
| 103 |
} |
| 104 |
|
| 105 |
/** The repository's Diffusion tab on the hub. */ |
| 106 |
export function diffusionTab(repo: HubRepo): string { |
| 107 |
return hubUrl(repo.base, repo.owner, repo.name, { kind: "diffusion" }); |
| 108 |
} |