extremely wip tangled spa
at main 9.2 kB view raw
1import { createSignal, For, Match, Show, Switch } from "solid-js"; 2import type { DiffTextFragment } from "../../../util/types"; 3import type { TreeNode } from "./data"; 4 5export function RenderTree(props: { tree: TreeNode; skip?: boolean }) { 6 if (props.skip) 7 return ( 8 <For each={props.tree.children}> 9 {(node) => <RenderTree tree={node} />} 10 </For> 11 ); 12 const [displayChildren, setDisplayChildren] = createSignal(true); 13 return ( 14 <Switch> 15 <Match when={props.tree.type === "file"}> 16 <a 17 class="flex min-w-fit cursor-default select-none flex-row items-center gap-1 rounded p-1 text-xs hover:bg-gray-100 hover:dark:bg-gray-700" 18 href={`#file-${encodeURI(props.tree.fullPath)}`} 19 onClick={() => { 20 const hash = `#file-${encodeURI(props.tree.fullPath)}`; 21 if (window.location.hash === hash) { 22 document 23 .getElementById(`file-${props.tree.fullPath}`) 24 ?.scrollIntoView({ behavior: "instant", block: "start" }); 25 } 26 }} 27 > 28 <div class="iconify gravity-ui--file" /> 29 <span class="select-text">{props.tree.name}</span> 30 </a> 31 </Match> 32 <Match when={props.tree.type === "directory"}> 33 <button 34 type="button" 35 class="flex min-w-fit select-none flex-row items-center gap-1 rounded p-1 text-xs hover:bg-gray-100 hover:dark:bg-gray-700" 36 onClick={() => setDisplayChildren(!displayChildren())} 37 > 38 <div class="iconify gravity-ui--folder-fill" /> 39 <span class="select-text">{props.tree.name}</span> 40 </button> 41 <div 42 class={`ml-1 flex flex-col border-gray-200 border-l pl-1 dark:border-gray-700 ${displayChildren() ? "" : "hidden"}`} 43 > 44 <For each={props.tree.children}> 45 {(node) => <RenderTree tree={node} />} 46 </For> 47 </div> 48 </Match> 49 </Switch> 50 ); 51} 52 53function Line(props: { 54 file: string; 55 index: number; 56 line: { Op: number; Line: string }; 57 lineNumber: number; 58 lineNumberOld: string; 59 lineNumberNew: string; 60 filler: string; 61}) { 62 const id = `line-${encodeURI(props.file)}-${props.index}-${props.lineNumber.toString()}`; 63 return ( 64 <div 65 class="flex scroll-mt-10 flex-row text-gray-400 *:flex *:flex-row dark:text-gray-500" 66 id={id} 67 > 68 <div class="sticky left-0 select-none border-gray-200 border-r bg-white *:flex dark:border-gray-700 dark:bg-gray-800"> 69 <Show 70 when={props.lineNumberOld} 71 fallback={ 72 <span class="float-right w-1/2 justify-end pr-1 pl-1.5"> 73 {props.filler} 74 </span> 75 } 76 > 77 <a 78 href={`#${id}`} 79 class="float-right w-1/2 justify-end pr-1 pl-1.5 hover:text-gray-700 hover:dark:text-gray-200" 80 > 81 {props.lineNumberOld} 82 </a> 83 </Show> 84 <Show 85 when={props.lineNumberNew} 86 fallback={ 87 <span class="float-right w-1/2 justify-end pr-1.5 pl-1"> 88 {props.filler} 89 </span> 90 } 91 > 92 <a 93 href={`#${id}`} 94 class="float-right w-1/2 justify-end pr-1.5 pl-1 hover:text-gray-700 hover:dark:text-gray-200" 95 > 96 {props.lineNumberNew} 97 </a> 98 </Show> 99 </div> 100 <Switch> 101 <Match when={props.line.Op === 0}> 102 <div class="w-full text-gray-500 dark:text-gray-500"> 103 <div class="select-none">{"   "}</div> 104 {props.line.Line} 105 </div> 106 </Match> 107 <Match when={props.line.Op === 2}> 108 <div class="w-full bg-green-100 text-green-700 dark:bg-green-800/30 dark:text-green-400"> 109 <div class="select-none">{" + "}</div> 110 {props.line.Line} 111 </div> 112 </Match> 113 <Match when={props.line.Op === 1}> 114 <div class="w-full bg-red-100 text-red-700 dark:bg-red-800/30 dark:text-red-400"> 115 <div class="select-none">{" - "}</div> 116 {props.line.Line} 117 </div> 118 </Match> 119 </Switch> 120 </div> 121 ); 122} 123 124function Fragment(props: { 125 file: string; 126 data: DiffTextFragment; 127 index: number; 128 numberSize: number; 129}) { 130 let lineNumber = props.data.NewPosition; 131 let iOld = props.data.OldPosition; 132 let iNew = props.data.NewPosition; 133 134 return ( 135 <> 136 <Show when={props.index !== 0}> 137 <div class="h-5 w-full select-none bg-gray-100 text-center font-mono text-gray-700 dark:bg-gray-700 dark:text-gray-300"> 138 ··· 139 </div> 140 </Show> 141 <div class="w-full whitespace-pre font-mono"> 142 <For each={props.data.Lines}> 143 {(line) => { 144 const lineNumberOld = 145 line.Op === 2 146 ? "" 147 : (iOld++).toString().padStart(props.numberSize, " "); 148 const lineNumberNew = 149 line.Op === 1 150 ? "" 151 : (iNew++).toString().padStart(props.numberSize, " "); 152 const filler = " ".repeat(props.numberSize); 153 return ( 154 <Line 155 file={props.file} 156 index={props.index} 157 line={line} 158 lineNumber={lineNumber++} 159 lineNumberNew={lineNumberNew} 160 lineNumberOld={lineNumberOld} 161 filler={filler} 162 /> 163 ); 164 }} 165 </For> 166 </div> 167 </> 168 ); 169} 170 171export function DiffView(props: { 172 diff: { 173 oldName: string; 174 newName: string; 175 textFragments: DiffTextFragment[]; 176 }[]; 177}) { 178 return ( 179 <For each={props.diff}> 180 {(diff) => { 181 const [show, setShow] = createSignal(true); 182 183 const [addedLines, removedLines] = diff.textFragments 184 ? diff.textFragments.reduce( 185 (acc, v) => [acc[0] + v.NewLines, acc[1] + v.LinesDeleted], 186 [0, 0], 187 ) 188 : [0, 0]; 189 190 const header = ( 191 <button 192 type="button" 193 class={`sticky top-0 z-10 flex cursor-default select-none flex-row items-center gap-2 bg-white p-2 hover:bg-gray-100 dark:bg-gray-800 hover:dark:bg-gray-700 ${show() ? "rounded-t border-gray-200 border-b dark:border-gray-700" : "rounded"}`} 194 onClick={() => setShow(!show())} 195 > 196 <div 197 class={`iconify ${show() ? "gravity-ui--chevron-down" : "gravity-ui--chevron-right"}`} 198 /> 199 <div class="flex h-6 select-text flex-row items-center overflow-hidden rounded font-mono text-xs *:h-full *:content-center *:px-1"> 200 <Show when={addedLines > 0}> 201 <div class="bg-green-100 text-green-700 dark:bg-green-800/30 dark:text-green-400">{`+${addedLines}`}</div> 202 </Show> 203 <Show when={removedLines > 0}> 204 <div class="bg-red-100 text-red-700 dark:bg-red-700/30 dark:text-red-400">{`-${removedLines}`}</div> 205 </Show> 206 </div> 207 <Show when={diff.oldName !== "" && diff.newName !== diff.oldName}> 208 <div class="select-text">{diff.oldName}</div> 209 <div class="iconify gravity-ui--arrow-right" /> 210 </Show> 211 <Show 212 when={diff.newName !== ""} 213 fallback={<div class="iconify gravity-ui--trash-bin" />} 214 > 215 <div class="select-text">{diff.newName}</div> 216 </Show> 217 </button> 218 ); 219 220 if (!diff.textFragments) 221 return ( 222 <div 223 id={`file-${encodeURI(diff.newName)}`} 224 class="not-last:mb-1 flex w-full flex-col rounded border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800" 225 > 226 {header} 227 <div 228 class={`flex select-text justify-center rounded-b bg-white py-2 text-gray-500 dark:bg-gray-800 dark:text-gray-300 ${show() ? "" : "hidden"}`} 229 > 230 This is a binary file and will not be displayed. 231 </div> 232 </div> 233 ); 234 235 const lastFrag = diff.textFragments[diff.textFragments.length - 1]; 236 const numberSize = Math.max( 237 2, 238 ( 239 Math.max(lastFrag.NewPosition, lastFrag.OldPosition) + 240 lastFrag.Lines.length 241 ).toString().length, 242 ); 243 244 return ( 245 <div 246 id={`file-${diff.newName}`} 247 class="not-last:mb-1 flex w-full flex-col rounded border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800" 248 > 249 {header} 250 <div 251 class={`select-text overflow-x-auto rounded-b bg-white dark:bg-gray-800 ${show() ? "" : "hidden"}`} 252 > 253 <div class="min-w-max"> 254 <For each={diff.textFragments}> 255 {(frag, i) => ( 256 <Fragment 257 file={diff.newName} 258 data={frag} 259 index={i()} 260 numberSize={numberSize} 261 /> 262 )} 263 </For> 264 </div> 265 </div> 266 </div> 267 ); 268 }} 269 </For> 270 ); 271} 272 273export function Sidebar(props: { 274 insertions: number; 275 deletions: number; 276 sidebar: TreeNode; 277}) { 278 return ( 279 <div class="flex overflow-y-auto p-1 max-md:pb-0 md:sticky md:top-0 md:max-h-screen md:w-50 md:pr-0"> 280 <div class="flex min-h-max w-full grow cursor-default flex-col rounded border border-gray-200 bg-white p-1 dark:border-gray-700 dark:bg-gray-800"> 281 <div class="flex flex-row items-center justify-between p-1"> 282 <div class="font-bold">CHANGED FILES</div> 283 <div class="flex h-6 select-text flex-row items-center overflow-clip rounded font-mono text-xs *:h-full *:content-center *:px-1"> 284 <Show when={props.insertions > 0}> 285 <div class="bg-green-100 text-green-700 dark:bg-green-800/30 dark:text-green-400">{`+${props.insertions}`}</div> 286 </Show> 287 <Show when={props.deletions > 0}> 288 <div class="bg-red-100 text-red-700 dark:bg-red-700/30 dark:text-red-400">{`-${props.deletions}`}</div> 289 </Show> 290 </div> 291 </div> 292 <div class="flex max-w-full flex-col overflow-x-auto text-nowrap"> 293 <RenderTree tree={props.sidebar} skip={true} /> 294 </div> 295 </div> 296 </div> 297 ); 298}