file-ssr.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import { DIFFS_TAG_NAME, FileDiff, VirtualizedFileDiff } from "@pierre/diffs"
  2. import { type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
  3. import { createEffect, onCleanup, onMount, Show, splitProps } from "solid-js"
  4. import { Dynamic, isServer } from "solid-js/web"
  5. import { useWorkerPool } from "../context/worker-pool"
  6. import { createDefaultOptions, styleVariables } from "../pierre"
  7. import { markCommentedDiffLines } from "../pierre/commented-lines"
  8. import { fixDiffSelection } from "../pierre/diff-selection"
  9. import {
  10. applyViewerScheme,
  11. clearReadyWatcher,
  12. createReadyWatcher,
  13. notifyShadowReady,
  14. observeViewerScheme,
  15. } from "../pierre/file-runtime"
  16. import { acquireVirtualizer, virtualMetrics } from "../pierre/virtualizer"
  17. import { File, type DiffFileProps, type FileProps } from "./file"
  18. type SSRDiffFileProps<T> = DiffFileProps<T> & {
  19. preloadedDiff: PreloadMultiFileDiffResult<T>
  20. }
  21. function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
  22. let container!: HTMLDivElement
  23. let fileDiffRef!: HTMLElement
  24. let fileDiffInstance: FileDiff<T> | undefined
  25. let sharedVirtualizer: NonNullable<ReturnType<typeof acquireVirtualizer>> | undefined
  26. const ready = createReadyWatcher()
  27. const workerPool = useWorkerPool(props.diffStyle)
  28. const [local, others] = splitProps(props, [
  29. "mode",
  30. "media",
  31. "before",
  32. "after",
  33. "class",
  34. "classList",
  35. "annotations",
  36. "selectedLines",
  37. "commentedLines",
  38. "onLineSelected",
  39. "onLineSelectionEnd",
  40. "onLineNumberSelectionEnd",
  41. "onRendered",
  42. "preloadedDiff",
  43. ])
  44. const getRoot = () => fileDiffRef?.shadowRoot ?? undefined
  45. const getVirtualizer = () => {
  46. if (sharedVirtualizer) return sharedVirtualizer.virtualizer
  47. const result = acquireVirtualizer(container)
  48. if (!result) return
  49. sharedVirtualizer = result
  50. return result.virtualizer
  51. }
  52. const setSelectedLines = (range: DiffFileProps<T>["selectedLines"], attempt = 0) => {
  53. const diff = fileDiffInstance
  54. if (!diff) return
  55. const fixed = fixDiffSelection(getRoot(), range ?? null)
  56. if (fixed === undefined) {
  57. if (attempt >= 120) return
  58. requestAnimationFrame(() => setSelectedLines(range ?? null, attempt + 1))
  59. return
  60. }
  61. diff.setSelectedLines(fixed)
  62. }
  63. const notifyRendered = () => {
  64. notifyShadowReady({
  65. state: ready,
  66. container,
  67. getRoot,
  68. isReady: (root) => root.querySelector("[data-line]") != null,
  69. settleFrames: 1,
  70. onReady: () => {
  71. setSelectedLines(local.selectedLines ?? null)
  72. local.onRendered?.()
  73. },
  74. })
  75. }
  76. onMount(() => {
  77. if (isServer) return
  78. onCleanup(observeViewerScheme(() => fileDiffRef))
  79. const virtualizer = getVirtualizer()
  80. fileDiffInstance = virtualizer
  81. ? new VirtualizedFileDiff<T>(
  82. {
  83. ...createDefaultOptions(props.diffStyle),
  84. ...others,
  85. ...local.preloadedDiff,
  86. },
  87. virtualizer,
  88. virtualMetrics,
  89. workerPool,
  90. )
  91. : new FileDiff<T>(
  92. {
  93. ...createDefaultOptions(props.diffStyle),
  94. ...others,
  95. ...local.preloadedDiff,
  96. },
  97. workerPool,
  98. )
  99. applyViewerScheme(fileDiffRef)
  100. // @ts-expect-error private field required for hydration
  101. fileDiffInstance.fileContainer = fileDiffRef
  102. fileDiffInstance.hydrate({
  103. oldFile: local.before,
  104. newFile: local.after,
  105. lineAnnotations: local.annotations ?? [],
  106. fileContainer: fileDiffRef,
  107. containerWrapper: container,
  108. })
  109. notifyRendered()
  110. })
  111. createEffect(() => {
  112. const diff = fileDiffInstance
  113. if (!diff) return
  114. diff.setLineAnnotations(local.annotations ?? [])
  115. diff.rerender()
  116. })
  117. createEffect(() => {
  118. setSelectedLines(local.selectedLines ?? null)
  119. })
  120. createEffect(() => {
  121. const ranges = local.commentedLines ?? []
  122. requestAnimationFrame(() => {
  123. const root = getRoot()
  124. if (!root) return
  125. markCommentedDiffLines(root, ranges)
  126. })
  127. })
  128. onCleanup(() => {
  129. clearReadyWatcher(ready)
  130. fileDiffInstance?.cleanUp()
  131. sharedVirtualizer?.release()
  132. sharedVirtualizer = undefined
  133. })
  134. return (
  135. <div
  136. data-component="file"
  137. data-mode="diff"
  138. style={styleVariables}
  139. class={local.class}
  140. classList={local.classList}
  141. ref={container}
  142. >
  143. <Dynamic component={DIFFS_TAG_NAME} ref={fileDiffRef} id="ssr-diff">
  144. <Show when={isServer}>
  145. <template shadowrootmode="open" innerHTML={local.preloadedDiff.prerenderedHTML} />
  146. </Show>
  147. </Dynamic>
  148. </div>
  149. )
  150. }
  151. export type FileSSRProps<T = {}> = FileProps<T>
  152. export function FileSSR<T>(props: FileSSRProps<T>) {
  153. if (props.mode !== "diff" || !props.preloadedDiff) return File(props)
  154. return DiffSSRViewer(props as SSRDiffFileProps<T>)
  155. }