Skip to content

Commit b519a2f

Browse files
christian-schillingLMG
authored andcommitted
Add change view
Change-Id: change-view
1 parent 7ac17f3 commit b519a2f

File tree

8 files changed

+364
-11
lines changed

8 files changed

+364
-11
lines changed

josh-proxy/src/bin/josh-proxy.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,8 @@ async fn handle_ui_request(
377377
|| resource_path == "/select"
378378
|| resource_path == "/browse"
379379
|| resource_path == "/view"
380+
|| resource_path == "/diff"
381+
|| resource_path == "/change"
380382
|| resource_path == "/history";
381383

382384
let resolve_path = if is_app_route {

josh-ui/src/App.scss

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ $color-link-visited-hover: #ffffaa;
2020

2121
&:hover {
2222
color: $color-link-hover;
23-
text-decoration: underline;
2423
}
2524

2625
&:visited {
@@ -132,19 +131,48 @@ nav {
132131
}
133132
}
134133

135-
.commit-list-entry {
134+
.file-browser-list-entry {
136135
@include ui-link-clickable;
137136
padding: .4em .4em;
138137

139138
&:hover {
140139
background: $color-background-highlight;
141140
}
141+
}
142+
143+
.commit-list-entry-dir {
144+
@include ui-link-clickable;
145+
&:hover {
146+
background: $color-background-highlight;
147+
}
148+
}
149+
150+
.commit-list-entry-browse {
151+
@include ui-link-clickable;
152+
&:hover {
153+
background: $color-background-highlight;
154+
}
155+
}
156+
157+
158+
.commit-list-entry {
159+
padding: .4em .4em;
142160

143161
span.hash {
144162
margin: 0 0.7em 0 0;
145163
color: $color-highlight;
146164
font-weight: bolder;
147165
}
166+
span.authorEmail {
167+
margin: 0 0.7em 0 0;
168+
color: $color-highlight;
169+
font-weight: bolder;
170+
}
171+
span.summary {
172+
display: block;
173+
margin: 0 0.7em 0 0;
174+
font-weight: bolder;
175+
}
148176
}
149177

150178
.ui-button {

josh-ui/src/App.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {RepoSelector} from './RepoSelector';
1919
import {NavigateCallback, NavigateTarget, NavigateTargetType} from "./Navigation";
2020
import {match} from "ts-pattern";
2121
import {FileViewer} from "./FileViewer";
22+
import {DiffViewer} from "./DiffViewer";
23+
import {ChangeViewer} from "./ChangeViewer";
2224
import {HistoryList} from "./History";
2325
import {Breadcrumbs} from "./Breadcrumbs";
2426
import {DEFAULT_FILTER} from "./Josh";
@@ -42,7 +44,9 @@ function useNavigateCallback(): NavigateCallback {
4244
const pathname = match(targetType)
4345
.with(NavigateTargetType.History, () => '/history')
4446
.with(NavigateTargetType.Directory, () => '/browse')
47+
.with(NavigateTargetType.Change, () => '/change')
4548
.with(NavigateTargetType.File, () => '/view')
49+
.with(NavigateTargetType.Diff, () => '/diff')
4650
.run()
4751

4852
navigate({
@@ -152,6 +156,27 @@ function Browse() {
152156
</div>
153157
}
154158

159+
function ChangeView() {
160+
const param = useStrictGetSearchParam()
161+
162+
useEffect(() => {
163+
document.title = `/${param('path')} - ${param('repo')} - Josh`
164+
});
165+
166+
return <div>
167+
<TopNav
168+
repo={param('repo')}
169+
filter={param('filter')} />
170+
171+
<ChangeViewer
172+
repo={param('repo')}
173+
filter={param('filter')}
174+
rev={param('rev')}
175+
navigateCallback={useNavigateCallback()}
176+
/>
177+
</div>
178+
}
179+
155180
function History() {
156181
const param = useStrictGetSearchParam()
157182

@@ -201,6 +226,38 @@ function View() {
201226
)
202227
}
203228

229+
function DiffView() {
230+
const param = useStrictGetSearchParam()
231+
232+
useEffect(() => {
233+
document.title = `${param('path')} - ${param('repo')} - Josh`
234+
});
235+
236+
return (
237+
<div>
238+
<TopNav
239+
repo={param('repo')}
240+
filter={param('filter')} />
241+
242+
<Breadcrumbs
243+
repo={param('repo')}
244+
path={param('path')}
245+
filter={param('filter')}
246+
rev={param('rev')}
247+
navigateCallback={useNavigateCallback()} />
248+
249+
<DiffViewer
250+
repo={param('repo')}
251+
path={param('path')}
252+
filter={param('filter')}
253+
rev={param('rev')}
254+
navigateCallback={useNavigateCallback()}
255+
/>
256+
</div>
257+
)
258+
}
259+
260+
204261
function App() {
205262
return (
206263
<BrowserRouter basename={'/~/ui'}>
@@ -210,6 +267,8 @@ function App() {
210267
<Route path='/browse' element={<Browse />} />
211268
<Route path='/history' element={<History />} />
212269
<Route path='/view' element={<View />} />
270+
<Route path='/diff' element={<DiffView />} />
271+
<Route path='/change' element={<ChangeView />} />
213272
</Routes>
214273
</BrowserRouter>
215274
);

josh-ui/src/ChangeViewer.tsx

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import React from "react";
2+
import {GraphQLClient} from 'graphql-request'
3+
import {getServer} from "./Server";
4+
import {NavigateCallback, NavigateTargetType, QUERY_CHANGE} from "./Navigation";
5+
import {match} from "ts-pattern";
6+
7+
export type ChangeViewProps = {
8+
repo: string
9+
filter: string
10+
rev: string
11+
navigateCallback: NavigateCallback
12+
}
13+
14+
type Path = {
15+
path: string
16+
}
17+
18+
type ChangedFile = {
19+
from: Path
20+
to: Path
21+
}
22+
23+
type State = {
24+
summary: string
25+
files: ChangedFile[]
26+
client: GraphQLClient
27+
}
28+
29+
export class ChangeViewer extends React.Component<ChangeViewProps, State> {
30+
state: State = {
31+
summary: "",
32+
files: [],
33+
client: new GraphQLClient(`${getServer()}/~/graphql/${this.props.repo}`, {
34+
mode: 'cors'
35+
}),
36+
};
37+
38+
startRequest() {
39+
this.state.client.rawRequest(QUERY_CHANGE, {
40+
rev: this.props.rev,
41+
filter: this.props.filter,
42+
}).then((d) => {
43+
const data = d.data.rev
44+
45+
this.setState({
46+
summary: data.summary,
47+
files: data.changedFiles,
48+
})
49+
})
50+
}
51+
52+
componentDidMount() {
53+
this.startRequest()
54+
}
55+
56+
componentDidUpdate(prevProps: Readonly<ChangeViewProps>, prevState: Readonly<State>, snapshot?: any) {
57+
if (prevProps !== this.props) {
58+
this.setState({
59+
files: [],
60+
})
61+
62+
this.startRequest()
63+
}
64+
}
65+
66+
componentWillUnmount() {
67+
// TODO cancel request?
68+
}
69+
70+
renderList(values: ChangedFile[], target: NavigateTargetType) {
71+
const classNameSuffix = match(target)
72+
.with(NavigateTargetType.Diff, () => 'file')
73+
.run()
74+
75+
const navigate = (path: string, e: React.MouseEvent<HTMLDivElement>) => {
76+
this.props.navigateCallback(target, {
77+
repo: this.props.repo,
78+
filter: this.props.filter,
79+
path: path,
80+
rev: this.props.rev
81+
})
82+
}
83+
84+
return values.map((entry) => {
85+
const className = `file-browser-list-entry file-browser-list-entry-${classNameSuffix}`
86+
let path = "";
87+
let prefix = "M";
88+
if (!entry.from) {
89+
prefix = "A";
90+
path = entry.to.path;
91+
}
92+
else if (!entry.to) {
93+
prefix = "D";
94+
path = entry.from.path;
95+
}
96+
else {
97+
path = entry.from.path;
98+
}
99+
100+
return <div className={className} key={path} onClick={navigate.bind(this,path)}>
101+
<span>{prefix}</span>{path}
102+
</div>
103+
})
104+
}
105+
106+
render() {
107+
if (this.state.files.length === 0) {
108+
return <div className={'file-browser-loading'}>Loading...</div>
109+
} else {
110+
return <div>
111+
<div>
112+
{this.state.summary}
113+
</div>
114+
<div className={'file-browser-list'}>
115+
{this.renderList(this.state.files, NavigateTargetType.Diff)}
116+
</div>
117+
</div>
118+
}
119+
}
120+
}

josh-ui/src/DiffViewer.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React from "react";
2+
import {DiffEditor} from "@monaco-editor/react";
3+
import {NavigateCallback, QUERY_FILE_DIFF} from "./Navigation";
4+
import {GraphQLClient} from "graphql-request";
5+
import {getServer} from "./Server";
6+
import {match} from "ts-pattern";
7+
8+
export type DiffViewerProps = {
9+
repo: string
10+
path: string
11+
filter: string
12+
rev: string
13+
navigateCallback: NavigateCallback
14+
}
15+
16+
type State = {
17+
content_a?: string
18+
content_b?: string
19+
client: GraphQLClient
20+
}
21+
22+
function mapLanguage(path: string) {
23+
const extension = path.split('.').pop()
24+
25+
return match(extension)
26+
.with('css', () => 'css')
27+
.with('html', 'htm', 'xhtml', () => 'html')
28+
.with('json', () => 'json')
29+
.with('ts', 'ts.d', 'tsx', () => 'typescript')
30+
.with('md', () => 'markdown')
31+
.with('rs', () => 'rust')
32+
.with('Dockerfile', () => 'dockerfile')
33+
.otherwise(() => undefined)
34+
}
35+
36+
export class DiffViewer extends React.Component<DiffViewerProps, State> {
37+
state = {
38+
content_a: undefined,
39+
content_b: undefined,
40+
client: new GraphQLClient(`${getServer()}/~/graphql/${this.props.repo}`, {
41+
mode: 'cors',
42+
errorPolicy: 'all'
43+
}),
44+
}
45+
46+
componentDidMount() {
47+
this.state.client.rawRequest(QUERY_FILE_DIFF, {
48+
rev: this.props.rev,
49+
filter: this.props.filter,
50+
path: this.props.path,
51+
}).then((d) => {
52+
const data = d.data.rev
53+
54+
let content_a = "";
55+
let content_b = "";
56+
57+
if (data.history[1].file) {
58+
content_a = data.history[1].file.text
59+
}
60+
61+
if (data.history[0].file) {
62+
content_b = data.history[0].file.text
63+
}
64+
65+
this.setState({
66+
content_a: content_a,
67+
content_b: content_b
68+
})
69+
})
70+
}
71+
72+
render() {
73+
if (this.state.content_a !== undefined
74+
&& this.state.content_b !== undefined) {
75+
return <DiffEditor
76+
modified={this.state.content_b}
77+
original={this.state.content_a}
78+
language={mapLanguage(this.props.path)}
79+
height='80vh'
80+
theme='vs-dark'
81+
options={{
82+
readOnly: true,
83+
domReadOnly: true,
84+
cursorBlinking: 'solid',
85+
}}
86+
/>
87+
} else
88+
{
89+
return <div>Loading...</div>
90+
}
91+
}
92+
}

josh-ui/src/FileViewer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ export class FileViewer extends React.Component<FileViewerProps, State> {
5555
}
5656

5757
render() {
58-
if (this.state.content !== undefined) {
58+
//if (this.state.content !== undefined) {
59+
if (true) {
5960
return <Editor
6061
value={this.state.content}
6162
language={mapLanguage(this.props.path)}

0 commit comments

Comments
 (0)