Skip to content
Source
Sign inSign up

contextPresentationRegistry.ts

133 lines4.5 KB

1import { COMPOSER_CONTEXT_KINDS, type KnownComposerContextKind } from "@t3tools/contracts";
2​
3export interface ContextPresentationCapability {
4 details: "none" | "tooltip" | "popover";
5 expanded: "none" | "inline-block" | "modal";
6 defaultDraftView: "compact" | "expanded";
7}
8​
9export interface ContextPresentationDefinition {
10 kind: KnownComposerContextKind;
11 capabilities: ContextPresentationCapability;
13​
14const DEFINITIONS = [
15 {
16 kind: "image",
17 capabilities: { details: "tooltip", expanded: "modal", defaultDraftView: "compact" },
18 },
19 {
20 kind: "file",
21 capabilities: { details: "tooltip", expanded: "modal", defaultDraftView: "compact" },
22 },
23 {
24 kind: "terminal",
25 capabilities: { details: "popover", expanded: "none", defaultDraftView: "compact" },
26 },
27 {
28 kind: "element",
29 capabilities: { details: "popover", expanded: "none", defaultDraftView: "compact" },
30 },
31 {
32 kind: "preview-annotation",
33 capabilities: { details: "popover", expanded: "modal", defaultDraftView: "compact" },
34 },
35 {
36 kind: "review-comment",
37 capabilities: { details: "popover", expanded: "none", defaultDraftView: "compact" },
38 },
39 {
40 kind: "mention",
41 capabilities: { details: "none", expanded: "none", defaultDraftView: "compact" },
42 },
43 {
44 kind: "skill",
45 capabilities: { details: "tooltip", expanded: "none", defaultDraftView: "compact" },
46 },
47 {
48 kind: "thread",
49 capabilities: { details: "tooltip", expanded: "none", defaultDraftView: "compact" },
50 },
51 {
52 kind: "environment",
53 capabilities: { details: "tooltip", expanded: "none", defaultDraftView: "compact" },
54 },
55 {
56 kind: "response",
57 capabilities: { details: "none", expanded: "inline-block", defaultDraftView: "compact" },
58 },
59] as const satisfies ReadonlyArray<ContextPresentationDefinition>;
60​
61function buildDefinitionRegistry(
62 definitions: ReadonlyArray<ContextPresentationDefinition>,
63): ReadonlyMap<KnownComposerContextKind, ContextPresentationDefinition> {
64 const registry = new Map<KnownComposerContextKind, ContextPresentationDefinition>();
65 for (const definition of definitions) {
66 if (registry.has(definition.kind)) {
67 throw new Error(`Duplicate context presentation definition: ${definition.kind}`);
68 }
69 registry.set(definition.kind, definition);
70 }
71 for (const kind of COMPOSER_CONTEXT_KINDS) {
72 if (!registry.has(kind)) {
73 throw new Error(`Missing context presentation definition: ${kind}`);
74 }
75 }
76 return registry;
78​
79const CONTEXT_PRESENTATION_DEFINITIONS = buildDefinitionRegistry(DEFINITIONS);
80​
81function contextPresentationDefinition(
82 kind: KnownComposerContextKind,
83): ContextPresentationDefinition {
84 return CONTEXT_PRESENTATION_DEFINITIONS.get(kind)!;
86​
87export interface ContextPresentationHandler<TRecord, TRenderContext, TResult> {
88 kind: KnownComposerContextKind;
89 canRender?: (record: TRecord, context: TRenderContext) => boolean;
90 render: (
91 record: TRecord,
92 context: TRenderContext,
93 definition: ContextPresentationDefinition,
94 ) => TResult;
96​
97export interface ContextPresentationRegistry<TRecord, TRenderContext, TResult> {
98 definition(kind: KnownComposerContextKind): ContextPresentationDefinition;
99 render(kind: string, record: TRecord | undefined, context: TRenderContext): TResult;
101​
102export function createContextPresentationRegistry<TRecord, TRenderContext, TResult>(options: {
103 handlers: ReadonlyArray<ContextPresentationHandler<TRecord, TRenderContext, TResult>>;
104 requiredKinds?: ReadonlyArray<KnownComposerContextKind>;
105 fallback: (kind: string, record: TRecord | undefined, context: TRenderContext) => TResult;
106}): ContextPresentationRegistry<TRecord, TRenderContext, TResult> {
107 const handlers = new Map<
108 KnownComposerContextKind,
109 ContextPresentationHandler<TRecord, TRenderContext, TResult>
110 >();
111 for (const handler of options.handlers) {
112 if (handlers.has(handler.kind)) {
113 throw new Error(`Duplicate context presentation handler: ${handler.kind}`);
114 }
115 handlers.set(handler.kind, handler);
116 }
117 for (const kind of options.requiredKinds ?? []) {
118 if (!handlers.has(kind)) {
119 throw new Error(`Missing context presentation handler: ${kind}`);
120 }
121 }
122​
123 return {
124 definition: contextPresentationDefinition,
125 render(kind, record, context) {
126 const handler = handlers.get(kind as KnownComposerContextKind);
127 if (!handler || record === undefined || handler.canRender?.(record, context) === false) {
128 return options.fallback(kind, record, context);
129 }
130 return handler.render(record, context, contextPresentationDefinition(handler.kind));
131 },
132 };