Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 1 | /** |
| 2 | * A workspace's Projects page, for a workspace with hundreds of them: what | |
| 3 | * the address asks for (a search, filters, a sort, a view and a page), and | |
| 4 | * the list that answers it. Read from and written to the address, so a | |
| 5 | * filtered list can be shared, bookmarked and gone back to. | |
| 6 | */ | |
| 7 | ||
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 8 | import type { MirrorState, Project, ProjectEcosystem, ProjectKind } from "@g1t/contracts"; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 9 | |
| 10 | export type ProjectSort = "updated" | "name" | "active" | "pushed"; | |
| 11 | export type ProjectView = "list" | "grid"; | |
| 12 | ||
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 13 | /** |
| 14 | * The Kind filter's choices: a kind of project, or where it is managed. | |
| 15 | * `mirror` is a repository that mirrors a remote (whatever state the mirror | |
| 16 | * is in), `g1t` one that is g1t's own. | |
| 17 | */ | |
| 18 | export type ProjectKindFilter = "all" | ProjectKind | "mirror" | "g1t"; | |
| 19 | ||
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 20 | export type ProjectQuery = { |
| 21 | /** Words in the name, slug or description. */ | |
| 22 | q: string; | |
| 23 | visibility: "all" | "public" | "private"; | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 24 | kind: ProjectKindFilter; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 25 | /** A language, from the manifests at the project's root. */ |
| 26 | language: string | null; | |
| 27 | /** Only projects with Deployments on. */ | |
| 28 | deployments: boolean; | |
| 29 | /** Archived projects: left out (the default), shown too, or only them. */ | |
| 30 | archived: "hide" | "include" | "only"; | |
| 31 | sort: ProjectSort; | |
| 32 | view: ProjectView; | |
| 33 | /** 1 first. */ | |
| 34 | page: number; | |
| 35 | }; | |
| 36 | ||
| 37 | /** Projects to a page. */ | |
| 38 | export const PAGE_SIZE = 30; | |
| 39 | ||
| 40 | export const DEFAULT_QUERY: ProjectQuery = { | |
| 41 | q: "", | |
| 42 | visibility: "all", | |
| 43 | kind: "all", | |
| 44 | language: null, | |
| 45 | deployments: false, | |
| 46 | archived: "hide", | |
| 47 | sort: "updated", | |
| 48 | view: "list", | |
| 49 | page: 1, | |
| 50 | }; | |
| 51 | ||
| 52 | export const SORTS: { value: ProjectSort; label: string }[] = [ | |
| 53 | { value: "updated", label: "Recently updated" }, | |
| 54 | { value: "pushed", label: "Recently pushed" }, | |
| 55 | { value: "active", label: "Most active" }, | |
| 56 | { value: "name", label: "Name" }, | |
| 57 | ]; | |
| 58 | ||
| 59 | /** The language a project's manifests say it is written in. */ | |
| 60 | export const LANGUAGE_OF: Record<ProjectEcosystem, string> = { | |
| 61 | npm: "JavaScript", | |
| 62 | cargo: "Rust", | |
| 63 | go: "Go", | |
| 64 | composer: "PHP", | |
| 65 | python: "Python", | |
| 66 | }; | |
| 67 | ||
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 68 | /** The remote a project's repository mirrors, and where the mirror stands, for its row. */ |
| 69 | export type ListedMirror = { state: MirrorState; remote: string }; | |
| 70 | ||
| 71 | /** | |
| 72 | * What the list needs of a project, with whether its Deployments are on and | |
| 73 | * the remote its repository mirrors (null for one managed on g1t). | |
| 74 | */ | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 75 | export type Listed = Pick< |
| 76 | Project, | |
| 77 | "id" | "slug" | "name" | "description" | "private" | "archived" | "kind" | "ecosystem" | "updatedAt" | "pushedAt" | "activity" | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 78 | > & { deploying: boolean; mirror: ListedMirror | null }; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 79 | |
| 80 | const one = <T extends string>(value: string | null, allowed: readonly T[], fallback: T): T => | |
| 81 | (allowed as readonly string[]).includes(value ?? "") ? (value as T) : fallback; | |
| 82 | ||
| 83 | /** What the address asks for; anything it does not say, or says wrong, is the default. */ | |
| 84 | export function readProjectQuery(params: URLSearchParams): ProjectQuery { | |
| 85 | const page = Number.parseInt(params.get("page") ?? "", 10); | |
| 86 | return { | |
| 87 | q: (params.get("q") ?? "").trim().slice(0, 100), | |
| 88 | visibility: one(params.get("visibility"), ["all", "public", "private"] as const, "all"), | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 89 | kind: one(params.get("kind"), ["all", "app", "library", "tool", "docs", "other", "mirror", "g1t"] as const, "all"), |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 90 | language: params.get("language")?.trim() || null, |
| 91 | deployments: params.get("deployments") === "on", | |
| 92 | archived: one(params.get("archived"), ["hide", "include", "only"] as const, "hide"), | |
| 93 | sort: one(params.get("sort"), ["updated", "name", "active", "pushed"] as const, "updated"), | |
| 94 | view: one(params.get("view"), ["list", "grid"] as const, "list"), | |
| 95 | page: Number.isFinite(page) && page > 0 ? page : 1, | |
| 96 | }; | |
| 97 | } | |
| 98 | ||
| 99 | /** | |
| 100 | * The address for `query` with `change` made, leaving out what is the | |
| 101 | * default. Changing anything but the page goes back to the first page. | |
| 102 | */ | |
| 103 | export function projectQueryString(query: ProjectQuery, change: Partial<ProjectQuery> = {}): string { | |
| 104 | const next = { ...query, ...change }; | |
| 105 | if (!("page" in change)) next.page = 1; | |
| 106 | const params = new URLSearchParams(); | |
| 107 | if (next.q) params.set("q", next.q); | |
| 108 | if (next.visibility !== "all") params.set("visibility", next.visibility); | |
| 109 | if (next.kind !== "all") params.set("kind", next.kind); | |
| 110 | if (next.language) params.set("language", next.language); | |
| 111 | if (next.deployments) params.set("deployments", "on"); | |
| 112 | if (next.archived !== "hide") params.set("archived", next.archived); | |
| 113 | if (next.sort !== "updated") params.set("sort", next.sort); | |
| 114 | if (next.view !== "list") params.set("view", next.view); | |
| 115 | if (next.page > 1) params.set("page", String(next.page)); | |
| 116 | const text = params.toString(); | |
| 117 | return text ? `?${text}` : ""; | |
| 118 | } | |
| 119 | ||
| 120 | /** Whether anything narrows the list, beyond leaving archived projects out. */ | |
| 121 | export function isFiltered(query: ProjectQuery): boolean { | |
| 122 | return Boolean( | |
| 123 | query.q || query.visibility !== "all" || query.kind !== "all" || query.language || query.deployments || query.archived !== "hide", | |
| 124 | ); | |
| 125 | } | |
| 126 | ||
| 127 | /** When it last changed: its own settings, or a push, whichever is later. */ | |
| 128 | export function lastUpdated(project: Pick<Listed, "updatedAt" | "pushedAt">): string { | |
| 129 | return project.pushedAt && project.pushedAt > project.updatedAt ? project.pushedAt : project.updatedAt; | |
| 130 | } | |
| 131 | ||
| 132 | function matches(project: Listed, words: string[]): boolean { | |
| 133 | const text = `${project.name} ${project.slug} ${project.description ?? ""}`.toLowerCase(); | |
| 134 | return words.every((word) => text.includes(word)); | |
| 135 | } | |
| 136 | ||
| 137 | /** The projects `query` asks for, in its order, before paging. */ | |
| 138 | export function filterProjects(projects: Listed[], query: ProjectQuery): Listed[] { | |
| 139 | const words = query.q.toLowerCase().split(/\s+/).filter(Boolean); | |
| 140 | const shown = projects.filter( | |
| 141 | (project) => | |
| 142 | (query.archived === "include" || (query.archived === "only") === project.archived) && | |
| 143 | (query.visibility === "all" || (query.visibility === "private") === project.private) && | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 144 | (query.kind === "all" || |
| 145 | (query.kind === "mirror" ? project.mirror != null : query.kind === "g1t" ? project.mirror == null : project.kind === query.kind)) && | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 146 | (!query.language || (project.ecosystem != null && LANGUAGE_OF[project.ecosystem] === query.language)) && |
| 147 | (!query.deployments || project.deploying) && | |
| 148 | (words.length === 0 || matches(project, words)), | |
| 149 | ); | |
| 150 | return sortProjects(shown, query.sort, words); | |
| 151 | } | |
| 152 | ||
| 153 | const byName = (a: Listed, b: Listed) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" }) || a.slug.localeCompare(b.slug); | |
| 154 | ||
| 155 | /** | |
| 156 | * In `sort`'s order, ties by name. While searching, a name that starts | |
| 157 | * with what was typed comes first. | |
| 158 | */ | |
| 159 | export function sortProjects(projects: Listed[], sort: ProjectSort, words: string[] = []): Listed[] { | |
| 160 | const first = words[0]; | |
| 161 | const leads = (project: Listed) => | |
| 162 | first != null && (project.name.toLowerCase().startsWith(first) || project.slug.startsWith(first)) ? 0 : 1; | |
| 163 | const order: Record<ProjectSort, (a: Listed, b: Listed) => number> = { | |
| 164 | name: byName, | |
| 165 | updated: (a, b) => lastUpdated(b).localeCompare(lastUpdated(a)), | |
| 166 | pushed: (a, b) => (b.pushedAt ?? "").localeCompare(a.pushedAt ?? ""), | |
| 167 | active: (a, b) => b.activity - a.activity, | |
| 168 | }; | |
| 169 | return [...projects].sort((a, b) => leads(a) - leads(b) || order[sort](a, b) || byName(a, b)); | |
| 170 | } | |
| 171 | ||
| 172 | /** One page of them, and where it sits among the rest. */ | |
| 173 | export function pageOf<T>(list: T[], page: number, size = PAGE_SIZE): { items: T[]; page: number; pages: number; from: number; to: number } { | |
| 174 | const pages = Math.max(1, Math.ceil(list.length / size)); | |
| 175 | const current = Math.min(Math.max(1, page), pages); | |
| 176 | const start = (current - 1) * size; | |
| 177 | const items = list.slice(start, start + size); | |
| 178 | return { items, page: current, pages, from: items.length ? start + 1 : 0, to: start + items.length }; | |
| 179 | } | |
| 180 | ||
| 181 | export type Facets = { | |
| 182 | visibility: { public: number; private: number }; | |
| Merge branch 'main' into worktree-agent-a69aeabc4b0deeb97 | 183 | kind: Record<ProjectKind, number>; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 184 | languages: { name: string; count: number }[]; |
| 185 | deploying: number; | |
| 186 | archived: number; | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 187 | /** Projects whose repository mirrors a remote; the rest are managed on g1t. */ |
| 188 | mirrors: number; | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 189 | }; |
| 190 | ||
| 191 | /** How many of the workspace's projects each filter would show, before any is chosen. */ | |
| 192 | export function facetsOf(projects: Listed[]): Facets { | |
| 193 | const languages = new Map<string, number>(); | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 194 | const facets: Facets = { |
| 195 | visibility: { public: 0, private: 0 }, | |
| 196 | kind: { app: 0, library: 0, tool: 0, docs: 0, other: 0 }, | |
| 197 | languages: [], | |
| 198 | deploying: 0, | |
| 199 | archived: 0, | |
| 200 | mirrors: 0, | |
| 201 | }; | |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 202 | for (const project of projects) { |
| 203 | facets.visibility[project.private ? "private" : "public"]++; | |
| 204 | facets.kind[project.kind]++; | |
| 205 | if (project.deploying) facets.deploying++; | |
| A desktop release goes out with what can be built. Linux is built on g1t's machines in every release; Windows and macOS each wait on a repository variable: a machine registered for them, or for Windows a cross build on Linux once aka.ms and download.visualstudio.microsoft.com are workflow-only domains, which is where the first release stopped. A platform with no machine is skipped and the download page says it is coming, with a word for the visitor whose platform this release lacks. The guide and CONTRIBUTING say so. | 206 | if (project.mirror) facets.mirrors++; |
| Navigation: the workspace's own page with tabs, pins and recent in the sidebar | 207 | if (project.archived) facets.archived++; |
| 208 | if (project.ecosystem) { | |
| 209 | const language = LANGUAGE_OF[project.ecosystem]; | |
| 210 | languages.set(language, (languages.get(language) ?? 0) + 1); | |
| 211 | } | |
| 212 | } | |
| 213 | facets.languages = [...languages].map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)); | |
| 214 | return facets; | |
| 215 | } | |
| 216 | ||
| 217 | /** | |
| 218 | * The index of the row to move to from `at` by key, in a list of `count` | |
| 219 | * rows: arrows and j/k move one, Home and End to either end. Null for any | |
| 220 | * other key. | |
| 221 | */ | |
| 222 | export function nextRow(key: string, at: number, count: number): number | null { | |
| 223 | if (count === 0) return null; | |
| 224 | switch (key) { | |
| 225 | case "ArrowDown": | |
| 226 | case "j": | |
| 227 | return Math.min(count - 1, at + 1); | |
| 228 | case "ArrowUp": | |
| 229 | case "k": | |
| 230 | return Math.max(0, at - 1); | |
| 231 | case "Home": | |
| 232 | return 0; | |
| 233 | case "End": | |
| 234 | return count - 1; | |
| 235 | default: | |
| 236 | return null; | |
| 237 | } | |
| 238 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.