feat: patch review file list
This commit is contained in:
parent
a7a794a117
commit
d57f46e435
14
public/icons.svg
Normal file
14
public/icons.svg
Normal 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 |
37
src/App.tsx
37
src/App.tsx
@ -1,11 +1,12 @@
|
|||||||
import { useMemo, useState, useSyncExternalStore } from 'react';
|
import { useEffect, useMemo, useState, useSyncExternalStore } from 'react';
|
||||||
import './App.css';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { RelayPool } from "nostr-relaypool";
|
import { RelayPool } from "nostr-relaypool";
|
||||||
|
|
||||||
|
import './App.css';
|
||||||
import { PatchCache } from './Cache/PatchCache';
|
import { PatchCache } from './Cache/PatchCache';
|
||||||
import { PatchstrDb } from './Db';
|
import { PatchstrDb } from './Db';
|
||||||
import { PatchRow } from './PatchRow';
|
import { PatchRow } from './PatchRow';
|
||||||
import { parseDiffEvent } from './Diff';
|
import { parseDiffEvent } from './Diff';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
const relays = [
|
const relays = [
|
||||||
"wss://relay.damus.io",
|
"wss://relay.damus.io",
|
||||||
@ -17,19 +18,6 @@ export const PatchKind = 19691228;
|
|||||||
|
|
||||||
const Store = new PatchCache("Patches", PatchstrDb.events);
|
const Store = new PatchCache("Patches", PatchstrDb.events);
|
||||||
export const Nostr = new RelayPool(relays);
|
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() {
|
function usePatchStore() {
|
||||||
return useSyncExternalStore(a => Store.hook(a, "*"), () => Store.snapshot());
|
return useSyncExternalStore(a => Store.hook(a, "*"), () => Store.snapshot());
|
||||||
@ -40,6 +28,23 @@ export function App() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [tag, setTag] = useState<string>();
|
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(() => {
|
const patches = useMemo(() => {
|
||||||
return [...store.filter(a => tag === undefined || a.tag === tag)].sort(a => -a.created);
|
return [...store.filter(a => tag === undefined || a.tag === tag)].sort(a => -a.created);
|
||||||
}, [tag, store]);
|
}, [tag, store]);
|
||||||
|
21
src/Icon.tsx
Normal file
21
src/Icon.tsx
Normal 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
19
src/PatchFileList.css
Normal 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
78
src/PatchFileList.tsx
Normal 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>
|
||||||
|
}
|
@ -1,47 +1,8 @@
|
|||||||
.file {
|
.patch-review {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
border: 1px solid;
|
|
||||||
margin: 10px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file>.header {
|
.patch-review>div:nth-child(1) {
|
||||||
display: flex;
|
width: 200px;
|
||||||
flex-direction: row;
|
padding: 10px;
|
||||||
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;
|
|
||||||
}
|
}
|
@ -3,6 +3,7 @@ import { useLocation } from "react-router-dom";
|
|||||||
import "./PatchReview.css";
|
import "./PatchReview.css";
|
||||||
import { ParsedPatch } from "./Diff";
|
import { ParsedPatch } from "./Diff";
|
||||||
import PatchView from "./PathView";
|
import PatchView from "./PathView";
|
||||||
|
import PatchFileList from "./PatchFileList";
|
||||||
|
|
||||||
export default function PatchReview() {
|
export default function PatchReview() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -11,5 +12,12 @@ export default function PatchReview() {
|
|||||||
return <b>Missing route data</b>
|
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
56
src/PatchView.css
Normal 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;
|
||||||
|
}
|
@ -2,7 +2,9 @@ import { File, Chunk } from "parse-diff";
|
|||||||
import hljs from "highlight.js";
|
import hljs from "highlight.js";
|
||||||
import 'highlight.js/styles/dark.css';
|
import 'highlight.js/styles/dark.css';
|
||||||
|
|
||||||
|
import "./PatchView.css";
|
||||||
import { ParsedPatch } from "./Diff";
|
import { ParsedPatch } from "./Diff";
|
||||||
|
import Icon from "./Icon";
|
||||||
|
|
||||||
export default function PatchView({ patch }: { patch: ParsedPatch }) {
|
export default function PatchView({ patch }: { patch: ParsedPatch }) {
|
||||||
|
|
||||||
@ -11,6 +13,7 @@ export default function PatchView({ patch }: { patch: ParsedPatch }) {
|
|||||||
var newY = c.newStart;
|
var newY = c.newStart;
|
||||||
return <>
|
return <>
|
||||||
<div className="diff chunk">
|
<div className="diff chunk">
|
||||||
|
<div></div>
|
||||||
<div></div>
|
<div></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}`}>
|
{c.changes.map(v => <div className={`diff ${v.type}`}>
|
||||||
<div>{v.type === "del" || v.type === "normal" ? ++oldY : ""}</div>
|
<div>{v.type === "del" || v.type === "normal" ? ++oldY : ""}</div>
|
||||||
<div>{v.type === "add" || v.type === "normal" ? ++newY : ""}</div>
|
<div>{v.type === "add" || v.type === "normal" ? ++newY : ""}</div>
|
||||||
|
<div>
|
||||||
|
<Icon name="annotation" size={16}/>
|
||||||
|
</div>
|
||||||
<div dangerouslySetInnerHTML={{
|
<div dangerouslySetInnerHTML={{
|
||||||
__html: hljs.highlightAuto(v.content, [""]).value
|
__html: hljs.highlightAuto(v.content, [""]).value
|
||||||
}}>
|
}}>
|
||||||
|
Loading…
Reference in New Issue
Block a user