8000 feat(settings): move Speakers from the sidebar into Settings by adeelahmad · Pull Request #60 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 7 additions & 15 deletions nextjs-frontend/__tests__/navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,13 @@ import { safeCallbackUrl } from "@/lib/definitions";
describe("navigation", () => {
const labels = (admin: boolean) => navFor(admin).map((i) => i.label);

it("shows Settings and Admin to admins only", () => {
it("shows Admin to admins only, and Settings (where Speakers are) to everyone", () => {
expect(labels(true)).toEqual(expect.arrayContaining(["Settings", "Admin"]));
expect(labels(false)).not.toContain("Settings");
expect(labels(false)).toContain("Settings");
expect(labels(false)).not.toContain("Admin");
expect(labels(true)).not.toContain("Speakers");
expect(labels(false)).toEqual(
expect.arrayContaining([
"Home",
"Library",
"Search",
"Chat",
"Speakers",
"Graph",
"Reports",
"Pipelines",
"Sources",
]),
expect.arrayContaining(["Home", "Library", "Search", "Chat", "Graph", "Reports", "Pipelines", "Sources"]),
);
});

Expand All @@ -28,8 +19,9 @@ describe("navigation", () => {
expect(isActive("/library", "/")).toBe(false);
expect(isActive("/resources/12", "/library")).toBe(true);
expect(isActive("/recordings/12", "/library")).toBe(true); // the old address, on its way to /resources
expect(isActive("/speakers/4", "/speakers")).toBe(true);
expect(isActive("/speakersx", "/speakers")).toBe(false);
expect(isActive("/speakers/4", "/settings")).toBe(true); // a speaker's profile, under Settings
expect(isActive("/settings/speakers", "/settings")).toBe(true);
expect(isActive("/speakersx", "/settings")).toBe(false);
});
});

Expand Down
17 changes: 12 additions & 5 deletions nextjs-frontend/app/(app)/settings/[section]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { Suspense } from "react";

import { SECTIONS, type SectionId } from "@/components/settings/model";
import { SECTIONS, WORKSPACE_SECTIONS, type AnySectionId } from "@/components/settings/model";
import { SettingsShell } from "@/components/settings/settings-shell";

const ALL = [...WORKSPACE_SECTIONS, ...SECTIONS];

export async function generateMetadata({ params }: { params: Promise<{ section: string }> }): Promise<Metadata> {
const { section } = await params;
const s = SECTIONS.find((x) => x.id === section);
const s = ALL.find((x) => x.id === section);
return { title: s ? `${s.label} · Settings` : "Settings" };
}

/** One settings section (admins): /settings/voice-ids, /settings/iiif… */
/** One settings section: /settings/speakers (every member), /settings/voice-ids, /settings/iiif… (admins). */
export default async function SettingsPage({ params }: { params: Promise<{ section: string }> }) {
const { section } = await params;
if (!SECTIONS.some((s) => s.id === section)) notFound();
return <SettingsShell section={section as SectionId} />;
if (!ALL.some((s) => s.id === section)) notFound();
return (
<Suspense>
<SettingsShell section={section as AnySectionId} />
</Suspense>
);
}
4 changes: 2 additions & 2 deletions nextjs-frontend/app/(app)/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { redirect } from "next/navigation";

/** Settings open on their first section. */
/** Settings open on Speakers, the one section every member has. */
export default function SettingsIndex() {
redirect("/settings/transcription");
redirect("/settings/speakers");
}
20 changes: 7 additions & 13 deletions nextjs-frontend/app/(app)/speakers/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import type { Metadata } from "next";
import { Suspense } from "react";
import { redirect } from "next/navigation";

import { SpeakersPage } from "@/components/speakers/speakers-page";

export const metadata: Metadata = { title: "Speakers" };

/** Speakers (SP1–SP3). */
export default function Page() {
return (
<Suspense>
<SpeakersPage />
</Suspense>
);
/** Speakers moved into Settings; old links (and bookmarks with ?ns= or ?tab=) land there. */
export default async function Page({ searchParams }: { searchParams: Promise<Record<string, string | string[]>> }) {
const p = new URLSearchParams();
for (const [k, v] of Object.entries(await searchParams)) for (const x of [v].flat()) p.append(k, x);
const q = p.toString();
redirect(`/settings/speakers${q ? `?${q}` : ""}`);
}
14 changes: 5 additions & 9 deletions nextjs-frontend/components/app-shell/nav-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
AudioLines,
CalendarClock,
ChartNoAxesColumn,
HardDriveDownload,
Expand All @@ -20,7 +19,7 @@ export type NavItem = {
icon: LucideIcon;
/** Only shown to archive administrators. */
adminOnly?: boolean;
/** A count shown next to the label (library size, speakers to review). */
/** A count shown next to the label (library size, speakers to review in Settings). */
countKey?: "recordings" | "reviews";
/** Draw a divider after this item. */
divider?: boolean;
Expand All @@ -37,12 +36,6 @@ export const NAV: NavItem[] = [
},
{ label: "Search", href: "/search", icon: Search },
{ label: "Chat", href: "/chat", icon: MessagesSquare },
{
label: "Speakers",
href: "/speakers",
icon: AudioLines,
countKey: "reviews",
},
{ label: "Graph", href: "/graph", icon: Waypoints },
{
label: "Reports",
Expand All @@ -53,7 +46,8 @@ export const NAV: NavItem[] = [
{ label: "Pipelines", href: "/pipelines", icon: Workflow },
{ label: "Routines", href: "/routines", icon: CalendarClock, adminOnly: true },
{ label: "Sources", href: "/sources", icon: HardDriveDownload },
{ label: "Settings", href: "/settings", icon: Settings, adminOnly: true },
// Everyone has Settings: members find Speakers there, admins the archive's settings too.
{ label: "Settings", href: "/settings", icon: Settings, countKey: "reviews" },
{ label: "Admin", href: "/admin", icon: Shield, adminOnly: true },
];

Expand All @@ -76,5 +70,7 @@ export function isActive(pathname: string, href: string): boolean {
["/templates", "/workflows", "/content-types"].some((p) => pathname === p || pathname.startsWith(`${p}/`))
)
return true;
// Speakers live in Settings; a speaker's profile keeps its own address.
if (href === "/settings" && (pathname === "/speakers" || pathname.startsWith("/speakers/"))) return true;
return pathname === href || pathname.startsWith(`${href}/`);
}
2 changes: 1 addition & 1 deletion nextjs-frontend/components/home/attention.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export function buildAttention(input: {
meta: `${pending.length} voice ${pending.length === 1 ? "match" : "matches"} to review in ${r.namespace}`,
action: {
label: "Review",
do: { type: "link", href: "/speakers", namespace: r.namespace },
do: { type: "link", href: "/settings/speakers", namespace: r.namespace },
},
});
}
Expand Down
2 changes: 1 addition & 1 deletion nextjs-frontend/components/recording/summary-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function SummaryTab() {
</Button>
{admin ? (
<Button asChild size="sm" variant="secondary">
<Link href="/settings">Open provider settings</Link>
<Link href="/settings/llm">Open provider settings</Link>
</Button>
) : (
<Button size="sm" variant="secondary" disabled disabledReason="Only admins can change the AI provider">
Expand Down
10 changes: 10 additions & 0 deletions nextjs-frontend/components/settings/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,16 @@ export type SectionId =
| "iiif"
| "startup";

/** Sections every member of a namespace has, kept apart from the archive's settings (which are for admins). */
export type WorkspaceSectionId = "speakers";
export type AnySectionId = SectionId | WorkspaceSectionId;

export const WORKSPACE_SECTIONS: { id: WorkspaceSectionId; label: string }[] = [{ id: "speakers", label: "Speakers" }];

export function isWorkspaceSection(id: string): id is WorkspaceSectionId {
return WORKSPACE_SECTIONS.some((s) => s.id === id);
}

export type SectionSpec = {
id: SectionId;
label: string;
Expand Down
Loading
Loading
0