Vectrum
Sign In
67 lines · 2.3 KB · TypeScript
import type { Status } from "@/lib/issue-meta";

// The issue list's view state (scope, layout, grouping, filters) and its URL form.
// Shared by the server page, which parses it, and the client tracker.

export type Scope = "all" | "active" | "backlog";
export type ViewMode = "list" | "board";
export type Grouping = "status" | "priority" | "assignee" | "none";
export type Ordering = "manual" | "priority" | "updated" | "created";

export type TrackerView = {
  scope: Scope;
  view: ViewMode;
  group: Grouping;
  order: Ordering;
  q: string;
  assignee: string[];
  priority: string[];
  label: string[];
};

export const DEFAULT_VIEW: TrackerView = {
  scope: "all",
  view: "list",
  group: "status",
  order: "manual",
  q: "",
  assignee: [],
  priority: [],
  label: [],
};

export const SCOPE_STATUSES: Record<Scope, Status[] | null> = {
  all: null,
  active: ["todo", "in_progress", "in_review"],
  backlog: ["backlog"],
};

/** Reads a view from URL search params, ignoring anything unknown. */
export function parseView(params: Record<string, string | string[] | undefined>): TrackerView {
  const one = (key: string) => (typeof params[key] === "string" ? (params[key] as string) : "");
  const list = (key: string) => one(key).split(",").filter(Boolean);
  const pick = <T extends string>(value: string, allowed: readonly T[], fallback: T) =>
    (allowed as readonly string[]).includes(value) ? (value as T) : fallback;
  return {
    scope: pick(one("scope"), ["all", "active", "backlog"], DEFAULT_VIEW.scope),
    view: pick(one("view"), ["list", "board"], DEFAULT_VIEW.view),
    group: pick(one("group"), ["status", "priority", "assignee", "none"], DEFAULT_VIEW.group),
    order: pick(one("order"), ["manual", "priority", "updated", "created"], DEFAULT_VIEW.order),
    q: one("q"),
    assignee: list("assignee"),
    priority: list("priority"),
    label: list("label"),
  };
}

export function viewToQuery(view: TrackerView) {
  const params = new URLSearchParams();
  for (const key of Object.keys(DEFAULT_VIEW) as (keyof TrackerView)[]) {
    const value = view[key];
    const text = Array.isArray(value) ? value.join(",") : value;
    const fallback = DEFAULT_VIEW[key];
    if (text && text !== (Array.isArray(fallback) ? fallback.join(",") : fallback)) params.set(key, text);
  }
  const query = params.toString();
  return query ? `?${query}` : "";
}