OXYGENHUB
◆ main oxygenhub-vscode / src / diffusion.ts
4.3 KiB · last changed feat: a Diffusion view, what each repository takes from and diffuses to (closes #5) 22 days ago source blame history raw
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
}