feat: patch review file list

This commit is contained in:
Kieran 2023-05-03 10:02:53 +01:00
parent a7a794a117
commit d57f46e435
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
9 changed files with 228 additions and 60 deletions

14
public/icons.svg Normal file
View File

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="folder" viewBox="0 0 22 20">
<path d="M12 5L10.8845 2.76892C10.5634 2.1268 10.4029 1.80573 10.1634 1.57116C9.95158 1.36373 9.69632 1.20597 9.41607 1.10931C9.09916 1 8.74021 1 8.02229 1H4.2C3.0799 1 2.51984 1 2.09202 1.21799C1.71569 1.40973 1.40973 1.71569 1.21799 2.09202C1 2.51984 1 3.0799 1 4.2V5M1 5H16.2C17.8802 5 18.7202 5 19.362 5.32698C19.9265 5.6146 20.3854 6.07354 20.673 6.63803C21 7.27976 21 8.11984 21 9.8V14.2C21 15.8802 21 16.7202 20.673 17.362C20.3854 17.9265 19.9265 18.3854 19.362 18.673C18.7202 19 17.8802 19 16.2 19H5.8C4.11984 19 3.27976 19 2.63803 18.673C2.07354 18.3854 1.6146 17.9265 1.32698 17.362C1 16.7202 1 15.8802 1 14.2V5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="file" viewBox="0 0 18 22">
<path d="M11 1.26946V5.4C11 5.96005 11 6.24008 11.109 6.45399C11.2049 6.64215 11.3578 6.79513 11.546 6.89101C11.7599 7 12.0399 7 12.6 7H16.7305M17 8.98822V16.2C17 17.8802 17 18.7202 16.673 19.362C16.3854 19.9265 15.9265 20.3854 15.362 20.673C14.7202 21 13.8802 21 12.2 21H5.8C4.11984 21 3.27976 21 2.63803 20.673C2.07354 20.3854 1.6146 19.9265 1.32698 19.362C1 18.7202 1 17.8802 1 16.2V5.8C1 4.11984 1 3.27976 1.32698 2.63803C1.6146 2.07354 2.07354 1.6146 2.63803 1.32698C3.27976 1 4.11984 1 5.8 1H9.01178C9.74555 1 10.1124 1 10.4577 1.08289C10.7638 1.15638 11.0564 1.27759 11.3249 1.44208C11.6276 1.6276 11.887 1.88703 12.4059 2.40589L15.5941 5.59411C16.113 6.11297 16.3724 6.3724 16.5579 6.67515C16.7224 6.94356 16.8436 7.2362 16.9171 7.5423C17 7.88757 17 8.25445 17 8.98822Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
<symbol id="annotation" viewBox="0 0 20 21">
<path d="M10 11.5V5.5M7 8.5H13M7.9 17.2L9.36 19.1467C9.57712 19.4362 9.68568 19.5809 9.81876 19.6327C9.93534 19.678 10.0647 19.678 10.1812 19.6327C10.3143 19.5809 10.4229 19.4362 10.64 19.1467L12.1 17.2C12.3931 16.8091 12.5397 16.6137 12.7185 16.4645C12.9569 16.2656 13.2383 16.1248 13.5405 16.0535C13.7671 16 14.0114 16 14.5 16C15.8978 16 16.5967 16 17.1481 15.7716C17.8831 15.4672 18.4672 14.8831 18.7716 14.1481C19 13.5967 19 12.8978 19 11.5V5.8C19 4.11984 19 3.27976 18.673 2.63803C18.3854 2.07354 17.9265 1.6146 17.362 1.32698C16.7202 1 15.8802 1 14.2 1H5.8C4.11984 1 3.27976 1 2.63803 1.32698C2.07354 1.6146 1.6146 2.07354 1.32698 2.63803C1 3.27976 1 4.11984 1 5.8V11.5C1 12.8978 1 13.5967 1.22836 14.1481C1.53284 14.8831 2.11687 15.4672 2.85195 15.7716C3.40326 16 4.10218 16 5.5 16C5.98858 16 6.23287 16 6.45951 16.0535C6.76169 16.1248 7.04312 16.2656 7.2815 16.4645C7.46028 16.6137 7.60685 16.8091 7.9 17.2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,11 +1,12 @@
import { useMemo, useState, useSyncExternalStore } from 'react';
import './App.css';
import { useEffect, useMemo, useState, useSyncExternalStore } from 'react';
import { useNavigate } from 'react-router-dom';
import { RelayPool } from "nostr-relaypool";
import './App.css';
import { PatchCache } from './Cache/PatchCache';
import { PatchstrDb } from './Db';
import { PatchRow } from './PatchRow';
import { parseDiffEvent } from './Diff';
import { useNavigate } from 'react-router-dom';
const relays = [
"wss://relay.damus.io",
@ -17,19 +18,6 @@ export const PatchKind = 19691228;
const Store = new PatchCache("Patches", PatchstrDb.events);
export const Nostr = new RelayPool(relays);
const sub = Nostr.subscribe([
{
kinds: [PatchKind],
limit: 200
}
], relays,
async (e) => {
const p = parseDiffEvent(e);
if (p.tag) {
await Store.set(p);
}
}
);
function usePatchStore() {
return useSyncExternalStore(a => Store.hook(a, "*"), () => Store.snapshot());
@ -40,6 +28,23 @@ export function App() {
const navigate = useNavigate();
const [tag, setTag] = useState<string>();
useEffect(() => {
const sub = Nostr.subscribe([
{
kinds: [PatchKind],
limit: 200
}
], relays,
async (e) => {
const p = parseDiffEvent(e);
if (p.tag) {
await Store.set(p);
}
}
);
return sub;
}, []);
const patches = useMemo(() => {
return [...store.filter(a => tag === undefined || a.tag === tag)].sort(a => -a.created);
}, [tag, store]);

21
src/Icon.tsx Normal file
View File

@ -0,0 +1,21 @@
import { MouseEventHandler } from "react";
type Props = {
name: string;
size?: number;
className?: string;
onClick?: MouseEventHandler<SVGSVGElement>;
};
const Icon = (props: Props) => {
const size = props.size;
const href = "/icons.svg#" + props.name;
return (
<svg width={size} height={size} className={props.className} onClick={props.onClick}>
<use href={href} />
</svg>
);
};
export default Icon;

19
src/PatchFileList.css Normal file
View File

@ -0,0 +1,19 @@
.tree svg {
height: 1em;
width: 28px;
}
.tree-dir {
padding-left: 10px;
}
.tree-item {
display: flex;
align-content: center;
padding: 2px 0;
user-select: none;
}
.tree-item:hover {
cursor: pointer;
}

78
src/PatchFileList.tsx Normal file
View File

@ -0,0 +1,78 @@
import { useMemo } from "react";
import "./PatchFileList.css";
import { ParsedPatch } from "./Diff";
import Icon from "./Icon";
interface NodeTree {
isDir: boolean
name: string
children: NodeTree[]
}
export default function PatchFileList({ patch }: { patch: ParsedPatch }) {
const tree = useMemo(() => {
const ret = {
isDir: true,
name: "/",
children: []
} as NodeTree;
function addAndRecurse(a: string[], atNode: NodeTree) {
if (a.length > 1) {
const newdir = a.shift()!;
let existingNode = atNode.children.find(a => a.name === newdir);
if (!existingNode) {
existingNode = {
isDir: true,
name: newdir,
children: []
};
atNode.children.push(existingNode);
}
addAndRecurse(a, existingNode);
} else {
atNode.children.push({
isDir: false,
name: a[0],
children: []
});
}
}
const split = patch.diff
.map(a => (a.to ?? a.from ?? ".")?.split("/"))
.sort((a, b) => a.length - b.length);
split.forEach(a => addAndRecurse(a, ret));
return ret;
}, [patch]);
function renderNode(n: NodeTree): React.ReactNode {
if (n.isDir && n.name === "/") {
// skip first node and just render children
return <>
{n.children.sort(a => a.isDir ? -1 : 1).map(b => renderNode(b))}
</>
} else if (n.isDir) {
return <>
<div className="tree-item">
<Icon name="folder"/>
{n.name}
</div>
<div className="tree-dir">
{n.children.sort(a => a.isDir ? -1 : 1).map(b => renderNode(b))}
</div>
</>
} else {
return <div className="tree-item">
<Icon name="file" />
{n.name}
</div>
}
}
return <div className="tree">
{renderNode(tree)}
</div>
}

View File

@ -1,47 +1,8 @@
.file {
.patch-review {
display: flex;
flex-direction: column;
border: 1px solid;
margin: 10px;
overflow: auto;
}
.file>.header {
display: flex;
flex-direction: row;
padding: 5px;
background-color: var(--primary-color);
}
.file>.header>div:nth-child(1) {
flex-grow: 1;
}
.file>.header>div:nth-child(2) {
display: flex;
flex-direction: row;
}
.file>.header>div:nth-child(2)>div {
min-width: 20px;
}
.file>.body {
display: flex;
flex-direction: column;
}
.diff {
display: grid;
grid-template-columns: 50px 50px auto;
grid-auto-flow: row;
white-space: pre;
}
.diff>div:nth-child(1), .diff>div:nth-child(2) {
text-align: center;
}
.diff.chunk {
line-height: 2em;
.patch-review>div:nth-child(1) {
width: 200px;
padding: 10px;
}

View File

@ -3,6 +3,7 @@ import { useLocation } from "react-router-dom";
import "./PatchReview.css";
import { ParsedPatch } from "./Diff";
import PatchView from "./PathView";
import PatchFileList from "./PatchFileList";
export default function PatchReview() {
const location = useLocation();
@ -11,5 +12,12 @@ export default function PatchReview() {
return <b>Missing route data</b>
}
return <PatchView patch={location.state as ParsedPatch} />
return <div className="patch-review">
<div>
<PatchFileList patch={location.state as ParsedPatch} />
</div>
<div>
<PatchView patch={location.state as ParsedPatch} />
</div>
</div>
}

56
src/PatchView.css Normal file
View File

@ -0,0 +1,56 @@
.file {
display: flex;
flex-direction: column;
border: 1px solid;
margin: 10px;
overflow: auto;
}
.file>.header {
display: flex;
flex-direction: row;
padding: 5px;
background-color: var(--primary-color);
}
.file>.header>div:nth-child(1) {
flex-grow: 1;
}
.file>.header>div:nth-child(2) {
display: flex;
flex-direction: row;
}
.file>.header>div:nth-child(2)>div {
min-width: 20px;
}
.file>.body {
display: flex;
flex-direction: column;
}
.diff {
display: grid;
grid-template-columns: 50px 50px 20px auto;
grid-auto-flow: row;
white-space: pre;
}
.diff>div:nth-child(1), .diff>div:nth-child(2), .diff>div:nth-child(3) {
text-align: center;
}
.diff>div:nth-child(3) {
visibility: hidden;
cursor: pointer;
}
.diff:hover>div:nth-child(3) {
visibility: visible;
}
.diff.chunk {
line-height: 2em;
}

View File

@ -2,7 +2,9 @@ import { File, Chunk } from "parse-diff";
import hljs from "highlight.js";
import 'highlight.js/styles/dark.css';
import "./PatchView.css";
import { ParsedPatch } from "./Diff";
import Icon from "./Icon";
export default function PatchView({ patch }: { patch: ParsedPatch }) {
@ -11,6 +13,7 @@ export default function PatchView({ patch }: { patch: ParsedPatch }) {
var newY = c.newStart;
return <>
<div className="diff chunk">
<div></div>
<div></div>
<div></div>
<div>
@ -20,6 +23,9 @@ export default function PatchView({ patch }: { patch: ParsedPatch }) {
{c.changes.map(v => <div className={`diff ${v.type}`}>
<div>{v.type === "del" || v.type === "normal" ? ++oldY : ""}</div>
<div>{v.type === "add" || v.type === "normal" ? ++newY : ""}</div>
<div>
<Icon name="annotation" size={16}/>
</div>
<div dangerouslySetInnerHTML={{
__html: hljs.highlightAuto(v.content, [""]).value
}}>