Skip to content

Commit 8cc11d1

Browse files
committed
Improve routing
1 parent dbc5c06 commit 8cc11d1

26 files changed

+288
-266
lines changed

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
"devDependencies": {
2323
"@coder/nbin": "^1.2.7",
2424
"@types/fs-extra": "^8.0.1",
25+
"@types/hookrouter": "^2.2.1",
2526
"@types/mocha": "^5.2.7",
2627
"@types/node": "^12.12.7",
2728
"@types/parcel-bundler": "^1.12.1",
2829
"@types/pem": "^1.9.5",
2930
"@types/react": "^16.9.18",
3031
"@types/react-dom": "^16.9.5",
31-
"@types/react-router-dom": "^5.1.3",
3232
"@types/safe-compare": "^1.1.0",
3333
"@types/tar-fs": "^1.16.1",
3434
"@types/tar-stream": "^1.6.1",
@@ -58,11 +58,11 @@
5858
"dependencies": {
5959
"@coder/logger": "1.1.11",
6060
"fs-extra": "^8.1.0",
61+
"hookrouter": "^1.2.3",
6162
"httpolyglot": "^0.1.2",
6263
"pem": "^1.14.2",
6364
"react": "^16.12.0",
6465
"react-dom": "^16.12.0",
65-
"react-router-dom": "^5.1.2",
6666
"safe-compare": "^1.1.4",
6767
"tar-fs": "^2.0.0",
6868
"tar-stream": "^2.1.0",

scripts/build.ts

+18
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,21 @@ class Builder {
173173
})
174174

175175
await this.copyDependencies("code-server", this.rootPath, this.buildPath)
176+
177+
await this.task("writing final code-server package.json", async () => {
178+
const json = JSON.parse(await fs.readFile(path.join(this.buildPath, "package.json"), "utf8"))
179+
return fs.writeFile(
180+
path.join(this.buildPath, "package.json"),
181+
JSON.stringify(
182+
{
183+
...json,
184+
commit,
185+
},
186+
null,
187+
2
188+
)
189+
)
190+
})
176191
}
177192

178193
private async buildVscode(commit: string): Promise<void> {
@@ -369,6 +384,9 @@ class Builder {
369384
bundler.on("buildEnd", () => {
370385
console.log("[parcel] bundled")
371386
})
387+
bundler.on("buildError", (error) => {
388+
console.error("[parcel]", error)
389+
})
372390

373391
vscode.stderr.on("data", (d) => process.stderr.write(d))
374392
tsc.stderr.on("data", (d) => process.stderr.write(d))

src/browser/app.css

+5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ iframe {
1212

1313
body {
1414
background: #272727;
15+
color: #f4f4f4;
1516
margin: 0;
1617
font-family: 'IBM Plex Sans', sans-serif;
1718
overflow: hidden;
1819
}
20+
21+
button {
22+
font-family: inherit;
23+
}

src/browser/app.tsx

+23-18
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,40 @@
1+
import { getBasepath, navigate } from "hookrouter"
12
import * as React from "react"
2-
import { Application } from "../common/api"
3-
import { Route, Switch } from "react-router-dom"
3+
import { Application, isExecutableApplication } from "../common/api"
44
import { HttpError } from "../common/http"
5+
import { normalize, Options } from "../common/util"
56
import { Modal } from "./components/modal"
6-
import { getOptions } from "../common/util"
77

8-
const App: React.FunctionComponent = () => {
9-
const [authed, setAuthed] = React.useState<boolean>(false)
10-
const [app, setApp] = React.useState<Application>()
8+
export interface AppProps {
9+
options: Options
10+
}
11+
12+
const App: React.FunctionComponent<AppProps> = (props) => {
13+
const [authed, setAuthed] = React.useState<boolean>(!!props.options.authed)
14+
const [app, setApp] = React.useState<Application | undefined>(props.options.app)
1115
const [error, setError] = React.useState<HttpError | Error | string>()
1216

1317
React.useEffect(() => {
14-
getOptions()
15-
}, [])
18+
if (app && !isExecutableApplication(app)) {
19+
navigate(normalize(`${getBasepath()}/${app.path}/`, true))
20+
}
21+
}, [app])
1622

1723
if (typeof window !== "undefined") {
1824
// eslint-disable-next-line @typescript-eslint/no-explicit-any
19-
;(window as any).setAuthed = setAuthed
25+
;(window as any).setAuthed = (a: boolean): void => {
26+
if (authed !== a) {
27+
setAuthed(a)
28+
}
29+
}
2030
}
2131

2232
return (
2333
<>
24-
<Switch>
25-
<Route path="/vscode" render={(): React.ReactElement => <iframe id="iframe" src="/vscode-embed"></iframe>} />
26-
<Route
27-
path="/"
28-
render={(): React.ReactElement => (
29-
<Modal app={app} setApp={setApp} authed={authed} error={error} setError={setError} />
30-
)}
31-
/>
32-
</Switch>
34+
<Modal app={app} setApp={setApp} authed={authed} error={error} setError={setError} />
35+
{authed && app && app.embedPath ? (
36+
<iframe id="iframe" src={normalize(`${getBasepath()}/${app.embedPath}/`, true)}></iframe>
37+
) : null}
3338
</>
3439
)
3540
}

src/browser/components/animate.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ export interface DelayProps {
55
readonly delay: number
66
}
77

8+
/**
9+
* Animate a component before unmounting (by delaying unmounting) or after
10+
* mounting.
11+
*/
812
export const Animate: React.FunctionComponent<DelayProps> = (props) => {
913
const [timer, setTimer] = React.useState<NodeJS.Timeout>()
1014
const [mount, setMount] = React.useState<boolean>(false)

src/browser/components/error.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { HttpError } from "../../common/http"
44
export interface ErrorProps {
55
error: HttpError | Error | string
66
onClose?: () => void
7+
onCloseText?: string
78
}
89

910
/**
@@ -15,7 +16,7 @@ export const RequestError: React.FunctionComponent<ErrorProps> = (props) => {
1516
<div className="error">{typeof props.error === "string" ? props.error : props.error.message}</div>
1617
{props.onClose ? (
1718
<button className="close" onClick={props.onClose}>
18-
Go Back
19+
{props.onCloseText || "Close"}
1920
</button>
2021
) : (
2122
undefined

src/browser/components/list.css

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
.app-row {
2222
color: #b6b6b6;
23-
cursor: pointer;
2423
display: flex;
2524
font-size: 1em;
2625
line-height: 1em;
@@ -106,3 +105,7 @@
106105
.app-list > .loader {
107106
color: #b6b6b6;
108107
}
108+
109+
.app-list > .app-row {
110+
cursor: pointer;
111+
}

src/browser/components/list.tsx

+37-5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { HttpError } from "../../common/http"
44
import { getSession, killSession } from "../api"
55
import { RequestError } from "../components/error"
66

7+
/**
8+
* An application's details (name and icon).
9+
*/
710
export const AppDetails: React.FunctionComponent<Application> = (props) => {
811
return (
912
<>
@@ -23,6 +26,9 @@ export interface AppRowProps {
2326
open(app: Application): void
2427
}
2528

29+
/**
30+
* A single application row. Can be killed if it's a running application.
31+
*/
2632
export const AppRow: React.FunctionComponent<AppRowProps> = (props) => {
2733
const [killing, setKilling] = React.useState<boolean>(false)
2834
const [error, setError] = React.useState<HttpError>()
@@ -65,6 +71,11 @@ export interface AppListProps {
6571
onKilled(app: Application): void
6672
}
6773

74+
/**
75+
* A list of applications. If undefined, show loading text. If empty, show a
76+
* message saying no items are found. Applications can be clicked and killed
77+
* (when applicable).
78+
*/
6879
export const AppList: React.FunctionComponent<AppListProps> = (props) => {
6980
return (
7081
<div className="app-list">
@@ -92,11 +103,12 @@ export interface AppLoaderProps {
92103
}
93104

94105
/**
95-
* Display provided applications or sessions and allow opening them.
106+
* Application sections/groups. Handles loading of the application
107+
* sections, errors, opening applications, and killing applications.
96108
*/
97109
export const AppLoader: React.FunctionComponent<AppLoaderProps> = (props) => {
98110
const [apps, setApps] = React.useState<ReadonlyArray<ApplicationSection>>()
99-
const [error, setError] = React.useState<HttpError>()
111+
const [error, setError] = React.useState<HttpError | Error>()
100112

101113
const refresh = (): void => {
102114
props
@@ -105,33 +117,51 @@ export const AppLoader: React.FunctionComponent<AppLoaderProps> = (props) => {
105117
.catch((e) => setError(e.message))
106118
}
107119

120+
// Every time the component loads go ahead and refresh the list.
108121
React.useEffect(() => {
109122
refresh()
110123
}, [props])
111124

125+
/**
126+
* Open an application if not already open. For executable applications create
127+
* a session first.
128+
*/
112129
function open(app: Application): void {
130+
if (props.app && props.app.name === app.name) {
131+
return setError(new Error(`${app.name} is already open`))
132+
}
113133
props.setApp(app)
114134
if (!isRunningApplication(app) && isExecutableApplication(app)) {
115135
getSession(app)
116136
.then((session) => {
117137
props.setApp({ ...app, ...session })
118138
})
119-
.catch(setError)
139+
.catch((error) => {
140+
props.setApp(undefined)
141+
setError(error)
142+
})
120143
}
121144
}
122145

146+
// In the case of an error fetching the apps, have the ability to try again.
147+
// In the case of failing to load an app, have the ability to go back to the
148+
// list (where the user can try again if they wish).
123149
if (error) {
124-
props.setApp(undefined)
125150
return (
126151
<RequestError
127152
error={error}
153+
onCloseText={props.app ? "Go Back" : "Try Again"}
128154
onClose={(): void => {
129155
setError(undefined)
156+
if (!props.app) {
157+
refresh()
158+
}
130159
}}
131160
/>
132161
)
133162
}
134163

164+
// If an app is currently loading, provide the option to cancel.
135165
if (props.app && !props.app.loaded) {
136166
return (
137167
<div className="app-loader">
@@ -151,14 +181,16 @@ export const AppLoader: React.FunctionComponent<AppLoaderProps> = (props) => {
151181
)
152182
}
153183

184+
// Apps are currently loading.
154185
if (!apps) {
155186
return (
156187
<div className="app-loader">
157-
<div className="loader">loading</div>
188+
<div className="loader">loading...</div>
158189
</div>
159190
)
160191
}
161192

193+
// Apps have loaded.
162194
return (
163195
<>
164196
{apps.map((section, i) => (

src/browser/components/modal.css

+15-10
Original file line numberDiff line numberDiff line change
@@ -100,19 +100,22 @@
100100
width: 100%;
101101
}
102102

103-
.modal-container > .modal > .sidebar {
104-
border-right: 1.5px solid rgba(0, 0, 0, 0.37);
103+
.sidebar-nav {
105104
display: flex;
106105
flex-direction: column;
107106
justify-content: space-between;
107+
min-width: 145px;
108108
}
109109

110-
.modal-container > .modal > .sidebar > .links {
110+
.sidebar-nav > .links {
111111
display: flex;
112112
flex-direction: column;
113113
}
114114

115-
.modal-container > .modal > .sidebar > .links > .link {
115+
.sidebar-nav > .links > .link {
116+
background-color: transparent;
117+
border: none;
118+
cursor: pointer;
116119
color: rgba(0, 0, 0, 0.37);
117120
font-size: 1.4em;
118121
height: 31px;
@@ -122,26 +125,28 @@
122125
transition: 150ms color ease, 150ms height ease, 150ms margin-bottom ease;
123126
}
124127

125-
.modal-container > .modal > .sidebar > .footer > .close {
128+
.sidebar-nav > .footer > .close {
126129
background: transparent;
127130
border: none;
128131
color: #b6b6b6;
129132
cursor: pointer;
130133
width: 100%;
131134
}
132135

133-
.modal-container > .modal > .sidebar > .footer > .close:hover {
136+
.sidebar-nav > .links > .link[aria-current="page"],
137+
.sidebar-nav > .links > .link:hover,
138+
.sidebar-nav > .footer > .close:hover {
134139
color: #000;
135140
}
136141

137-
.modal-container > .modal > .links > .link[aria-current="page"] {
138-
color: rgba(0, 0, 0, 1);
139-
}
140-
141142
.modal-container > .modal > .content {
142143
display: flex;
143144
flex: 1;
144145
flex-direction: column;
145146
overflow: auto;
146147
padding: 0 20px;
147148
}
149+
150+
.modal-container > .modal > .sidebar-nav {
151+
border-right: 1.5px solid rgba(0, 0, 0, 0.37);
152+
}

0 commit comments

Comments
 (0)