opencode/packages/ui/src/components/diff.tsx

105 lines
3.4 KiB
TypeScript
Raw Normal View History

2025-11-25 21:40:11 +08:00
import { type FileContents, FileDiff, type DiffLineAnnotation, FileDiffOptions } from "@pierre/precision-diffs"
import { PreloadMultiFileDiffResult } from "@pierre/precision-diffs/ssr"
2025-11-26 09:59:37 +08:00
import { ComponentProps, createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
2025-11-25 21:40:11 +08:00
import { isServer } from "solid-js/web"
2025-11-26 09:59:37 +08:00
import { createDefaultOptions, styleVariables } from "./pierre"
2025-10-24 21:26:17 +08:00
2025-10-30 23:33:45 +08:00
export type DiffProps<T = {}> = FileDiffOptions<T> & {
2025-11-25 21:40:11 +08:00
preloadedDiff?: PreloadMultiFileDiffResult<T>
2025-10-24 21:26:17 +08:00
before: FileContents
after: FileContents
2025-10-25 00:23:32 +08:00
annotations?: DiffLineAnnotation<T>[]
class?: string
classList?: ComponentProps<"div">["classList"]
2025-10-24 21:26:17 +08:00
}
2025-10-25 00:23:32 +08:00
// interface ThreadMetadata {
// threadId: string
// }
2025-11-26 09:59:37 +08:00
//
//
2025-10-24 21:26:17 +08:00
2025-10-25 00:23:32 +08:00
export function Diff<T>(props: DiffProps<T>) {
let container!: HTMLDivElement
2025-11-25 21:40:11 +08:00
let fileDiffRef!: HTMLElement
2025-11-08 09:59:02 +08:00
const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations"])
2025-10-24 21:26:17 +08:00
2025-11-25 21:40:11 +08:00
let fileDiffInstance: FileDiff<T> | undefined
const cleanupFunctions: Array<() => void> = []
2025-10-24 21:26:17 +08:00
2025-10-25 00:23:32 +08:00
createEffect(() => {
2025-11-25 21:40:11 +08:00
if (props.preloadedDiff) return
container.innerHTML = ""
if (!fileDiffInstance) {
fileDiffInstance = new FileDiff<T>({
2025-11-26 09:59:37 +08:00
...createDefaultOptions(props.diffStyle),
2025-11-25 21:40:11 +08:00
...others,
...(props.preloadedDiff ?? {}),
})
}
2025-11-25 21:47:25 +08:00
fileDiffInstance.render({
2025-11-25 21:40:11 +08:00
oldFile: local.before,
newFile: local.after,
lineAnnotations: local.annotations,
containerWrapper: container,
})
})
onMount(() => {
2025-11-29 10:08:50 +08:00
if (isServer || !props.preloadedDiff) return
2025-11-25 21:40:11 +08:00
fileDiffInstance = new FileDiff<T>({
2025-11-26 09:59:37 +08:00
...createDefaultOptions(props.diffStyle),
2025-10-25 00:23:32 +08:00
...others,
2025-11-25 21:40:11 +08:00
...(props.preloadedDiff ?? {}),
2025-10-25 00:23:32 +08:00
})
2025-11-25 21:40:11 +08:00
// @ts-expect-error - fileContainer is private but needed for SSR hydration
fileDiffInstance.fileContainer = fileDiffRef
2025-11-26 09:59:37 +08:00
fileDiffInstance.hydrate({
oldFile: local.before,
newFile: local.after,
lineAnnotations: local.annotations,
fileContainer: fileDiffRef,
containerWrapper: container,
})
2025-10-25 00:23:32 +08:00
2025-11-25 21:40:11 +08:00
// Hydrate annotation slots with interactive SolidJS components
// if (props.annotations.length > 0 && props.renderAnnotation != null) {
// for (const annotation of props.annotations) {
// const slotName = `annotation-${annotation.side}-${annotation.lineNumber}`;
// const slotElement = fileDiffRef.querySelector(
// `[slot="${slotName}"]`
// ) as HTMLElement;
//
// if (slotElement != null) {
// // Clear the static server-rendered content from the slot
// slotElement.innerHTML = '';
//
// // Mount a fresh SolidJS component into this slot using render().
// // This enables full SolidJS reactivity (signals, effects, etc.)
// const dispose = render(
// () => props.renderAnnotation!(annotation),
// slotElement
// );
// cleanupFunctions.push(dispose);
// }
// }
// }
})
onCleanup(() => {
// Clean up FileDiff event handlers and dispose SolidJS components
fileDiffInstance?.cleanUp()
cleanupFunctions.forEach((dispose) => dispose())
2025-10-24 21:26:17 +08:00
})
2025-10-25 00:23:32 +08:00
return (
2025-11-26 09:59:37 +08:00
<div data-component="diff" style={styleVariables} ref={container}>
2025-11-25 21:40:11 +08:00
<file-diff ref={fileDiffRef} id="ssr-diff">
2025-11-26 09:59:37 +08:00
<Show when={isServer && props.preloadedDiff}>
{(preloadedDiff) => <template shadowrootmode="open" innerHTML={preloadedDiff().prerenderedHTML} />}
</Show>
2025-11-25 21:40:11 +08:00
</file-diff>
</div>
2025-10-25 00:23:32 +08:00
)
2025-10-24 21:26:17 +08:00
}