1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
import { checksum } from "@opencode-ai/util/encode"
import { FileDiff } from "@pierre/diffs"
import { createMediaQuery } from "@solid-primitives/media"
import { createEffect, createMemo, onCleanup, splitProps } from "solid-js"
import { createDefaultOptions, type DiffProps, styleVariables } from "../pierre"
import { getWorkerPool } from "../pierre/worker"
export function Diff<T>(props: DiffProps<T>) {
let container!: HTMLDivElement
let observer: MutationObserver | undefined
let renderToken = 0
const [local, others] = splitProps(props, ["before", "after", "class", "classList", "annotations", "onRendered"])
const mobile = createMediaQuery("(max-width: 640px)")
const options = createMemo(() => {
const opts = {
...createDefaultOptions(props.diffStyle),
...others,
}
if (!mobile()) return opts
return {
...opts,
disableLineNumbers: true,
}
})
let instance: FileDiff<T> | undefined
const getRoot = () => {
const host = container.querySelector("diffs-container")
if (!(host instanceof HTMLElement)) return
const root = host.shadowRoot
if (!root) return
return root
}
const notifyRendered = () => {
if (!local.onRendered) return
observer?.disconnect()
observer = undefined
renderToken++
const token = renderToken
let settle = 0
const isReady = (root: ShadowRoot) => root.querySelector("[data-line]") != null
const notify = () => {
if (token !== renderToken) return
observer?.disconnect()
observer = undefined
requestAnimationFrame(() => {
if (token !== renderToken) return
local.onRendered?.()
})
}
const schedule = () => {
settle++
const current = settle
requestAnimationFrame(() => {
if (token !== renderToken) return
if (current !== settle) return
requestAnimationFrame(() => {
if (token !== renderToken) return
if (current !== settle) return
notify()
})
})
}
const observeRoot = (root: ShadowRoot) => {
observer?.disconnect()
observer = new MutationObserver(() => {
if (token !== renderToken) return
if (!isReady(root)) return
schedule()
})
observer.observe(root, { childList: true, subtree: true })
if (!isReady(root)) return
schedule()
}
const root = getRoot()
if (typeof MutationObserver === "undefined") {
if (!root || !isReady(root)) return
local.onRendered()
return
}
if (root) {
observeRoot(root)
return
}
observer = new MutationObserver(() => {
if (token !== renderToken) return
const root = getRoot()
if (!root) return
observeRoot(root)
})
observer.observe(container, { childList: true, subtree: true })
}
createEffect(() => {
const opts = options()
const workerPool = getWorkerPool(props.diffStyle)
const annotations = local.annotations
const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : ""
const afterContents = typeof local.after?.contents === "string" ? local.after.contents : ""
instance?.cleanUp()
instance = new FileDiff<T>(opts, workerPool)
container.innerHTML = ""
instance.render({
oldFile: {
...local.before,
contents: beforeContents,
cacheKey: checksum(beforeContents),
},
newFile: {
...local.after,
contents: afterContents,
cacheKey: checksum(afterContents),
},
lineAnnotations: annotations,
containerWrapper: container,
})
notifyRendered()
})
onCleanup(() => {
observer?.disconnect()
instance?.cleanUp()
})
return <div data-component="diff" style={styleVariables} ref={container} />
}
|