137 lines
2.9 KiB
TypeScript
137 lines
2.9 KiB
TypeScript
import { api } from "#lib/api/client.js";
|
|
|
|
export interface Change {
|
|
base: string;
|
|
hash: string;
|
|
path: string;
|
|
space: string;
|
|
}
|
|
|
|
export interface Revision {
|
|
changes: Change[];
|
|
machine: string;
|
|
main_branch: string;
|
|
main_sha: string;
|
|
seq: number;
|
|
time: number;
|
|
}
|
|
|
|
export interface Repo {
|
|
key: string;
|
|
name: string;
|
|
seq: number;
|
|
time: number;
|
|
}
|
|
|
|
export interface Machine {
|
|
created_at: number;
|
|
id: number;
|
|
last_push: number;
|
|
name: string;
|
|
revisions: number;
|
|
}
|
|
|
|
export interface FileEntry {
|
|
hash: string;
|
|
time: number;
|
|
}
|
|
|
|
export interface RepoState {
|
|
seq: number;
|
|
spaces: Record<string, Record<string, FileEntry>>;
|
|
}
|
|
|
|
export interface Code {
|
|
code: string;
|
|
command: string;
|
|
expires_at: number;
|
|
}
|
|
|
|
export const SHARED = "shared";
|
|
const PATTERNS = 7;
|
|
const WEB = "web";
|
|
const REVOKED = "revoked";
|
|
const SHORT_SHA = 7;
|
|
const BRANCH_PREFIX = "branch:";
|
|
|
|
export const lithology = (machine: string, machines: string[]): string => {
|
|
if (machine === WEB) {
|
|
return "lith lith-web";
|
|
}
|
|
const index = machines.indexOf(machine);
|
|
if (machine === REVOKED || index < 0) {
|
|
return "lith lith-gone";
|
|
}
|
|
return `lith lith-${index % PATTERNS}`;
|
|
};
|
|
|
|
export const shortSha = (sha: string): string =>
|
|
sha ? sha.slice(0, SHORT_SHA) : "no commit";
|
|
|
|
export const spaceName = (space: string): string =>
|
|
space.startsWith(BRANCH_PREFIX) ? space.slice(BRANCH_PREFIX.length) : space;
|
|
|
|
export const fileId = (space: string, path: string): string =>
|
|
`${space}\n${path}`;
|
|
|
|
export const changeKind = (
|
|
change: Change
|
|
): "added" | "deleted" | "modified" => {
|
|
if (!change.base) {
|
|
return "added";
|
|
}
|
|
return change.hash ? "modified" : "deleted";
|
|
};
|
|
|
|
const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [
|
|
["year", 31_536_000_000],
|
|
["month", 2_592_000_000],
|
|
["day", 86_400_000],
|
|
["hour", 3_600_000],
|
|
["minute", 60_000],
|
|
];
|
|
const relative = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
|
const absolute = new Intl.DateTimeFormat("en-GB", {
|
|
dateStyle: "medium",
|
|
timeStyle: "short",
|
|
});
|
|
|
|
export const ago = (time: number): string => {
|
|
if (!time) {
|
|
return "never";
|
|
}
|
|
const delta = time - Date.now();
|
|
for (const [unit, size] of UNITS) {
|
|
if (Math.abs(delta) >= size) {
|
|
return relative.format(Math.round(delta / size), unit);
|
|
}
|
|
}
|
|
return "just now";
|
|
};
|
|
|
|
export const exact = (time: number): string => absolute.format(time);
|
|
|
|
const blobs = new Map<string, Promise<Uint8Array>>();
|
|
|
|
export const blob = (hash: string): Promise<Uint8Array> => {
|
|
let pending = blobs.get(hash);
|
|
if (!pending) {
|
|
pending = api.bytes(`/blobs/${hash}`);
|
|
blobs.set(hash, pending);
|
|
}
|
|
return pending;
|
|
};
|
|
|
|
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
|
|
export const text = (bytes: Uint8Array): string | null => {
|
|
if (bytes.includes(0)) {
|
|
return null;
|
|
}
|
|
try {
|
|
return decoder.decode(bytes);
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|