Skip to content

Commit 48c1c7b

Browse files
authored
use react router (#308)
* wip: add react router * remove file * fix routes * fix To links * fix lint * fix docs * fix breadcrumbs * fix login redirects * fix tests * fix breadcrumb * small fixes
1 parent fe7960a commit 48c1c7b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1027
-2315
lines changed

package-lock.json

Lines changed: 67 additions & 1385 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/editor/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"@types/uuid": "^8.3.0",
5050
"@typescript/vfs": "^1.3.4",
5151
"classnames": "^2.3.1",
52-
"filebridge-client": "^0.1.1",
52+
"filebridge-client": "^0.1.5",
5353
"fractional-indexing": "^2.0.0",
5454
"frontend-collective-react-dnd-scrollzone": "1.0.2",
5555
"lodash": "^4.17.21",
@@ -98,7 +98,8 @@
9898
"y-webrtc": "^10.1.8",
9999
"y-protocols": "^1.0.5",
100100
"yjs": "^13.5.16",
101-
"zxcvbn": "^4.4.2"
101+
"zxcvbn": "^4.4.2",
102+
"react-router-dom": "^6.2.2"
102103
},
103104
"scripts": {
104105
"copytypes:self": "rimraf public/types && tsc --declaration --stripInternal --emitDeclarationOnly --noEmit false --declarationDir public/types/@typecell-org/editor",

packages/editor/src/app/App.tsx

Lines changed: 30 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,44 @@
11
import { observer } from "mobx-react-lite";
2-
import qs from "qs";
32
import React from "react";
4-
import { MATRIX_CONFIG } from "../config/config";
3+
import { BrowserRouter, Route, Routes } from "react-router-dom";
54
import { getStoreService } from "../store/local/stores";
5+
import { StartScreen } from "./main/components/startscreen/StartScreen";
66
import Main from "./main/Main";
7-
import LoginComponent from "./matrix-auth/auth/Login";
8-
import Registration from "./matrix-auth/auth/Registration";
97
import { ValidatedServerConfig } from "./matrix-auth/auth/util/AutoDiscoveryUtils";
10-
11-
function makeRegistrationUrl(params: any) {
12-
let url =
13-
window.location.protocol + "//" + window.location.host + "/register";
14-
15-
let keys = Object.keys(params);
16-
17-
// if any of the params (in our case, is_url) is undefined, don't include it in url
18-
keys = keys.filter((key) => params[key] !== undefined);
19-
20-
for (let i = 0; i < keys.length; ++i) {
21-
if (i === 0) {
22-
url += "?";
23-
} else {
24-
url += "&";
25-
}
26-
const k = keys[i];
27-
url += k + "=" + encodeURIComponent(params[k]);
28-
}
29-
return url;
30-
}
8+
import { DocumentRoute } from "./routes/document";
9+
import { DynamicRoute } from "./routes/dynamic";
10+
import { Login } from "./routes/login";
11+
import { ProfileRoute } from "./routes/profile";
12+
import { Register } from "./routes/register";
3113

3214
export const App = observer((props: { config: ValidatedServerConfig }) => {
33-
const { sessionStore, matrixAuthStore, navigationStore } = getStoreService();
15+
const { sessionStore } = getStoreService();
3416
if (sessionStore.user === "loading") {
3517
return <div>Loading</div>;
36-
} else if (navigationStore.currentPage.page === "login") {
37-
let pageAfterLogin = window.history.state?.prevUrl || "";
38-
39-
return (
40-
<LoginComponent
41-
serverConfig={props.config}
42-
onLoggedIn={matrixAuthStore.onUserCompletedLoginFlow}
43-
onRegisterClick={() => {
44-
navigationStore.showRegisterScreen();
45-
}}
46-
onServerConfigChange={() => {
47-
// TODO
48-
console.log("config change (not implemented)");
49-
}}
50-
// TODO: does this work correctly after SSO login is declined?
51-
pageAfterLogin={pageAfterLogin}
52-
onForgotPasswordClick={() => navigationStore.showForgotPassword()}
53-
/>
54-
);
55-
} else if (navigationStore.currentPage.page === "register") {
56-
const params = qs.parse(window.location.search);
57-
58-
if (params.hs_url && params.hs_url !== MATRIX_CONFIG.hsUrl) {
59-
throw new Error("different homeserver not supported");
60-
}
61-
62-
if (params.is_url && params.is_url !== MATRIX_CONFIG.isUrl) {
63-
throw new Error("different identity server not supported");
64-
}
65-
66-
let pageAfterLogin = window.history.state?.prevUrl || "";
67-
// const email = ThreepidInviteStore.instance.pickBestInvite()?.toEmail;
18+
} else if (sessionStore.user === "offlineNoUser") {
19+
return <div>Offline</div>;
20+
} else {
6821
return (
69-
<Registration
70-
clientSecret={params.client_secret as string | undefined}
71-
sessionId={params.session_id as string | undefined}
72-
idSid={params.sid as string | undefined}
73-
email={undefined}
74-
brand={"TypeCell"}
75-
makeRegistrationUrl={makeRegistrationUrl}
76-
onLoggedIn={matrixAuthStore.onUserCompletedLoginFlow}
77-
onLoginClick={() => {
78-
navigationStore.showLoginScreen();
79-
}}
80-
onServerConfigChange={() => {
81-
// TODO
82-
console.log("config change (not implemented)");
83-
}}
84-
defaultDeviceDisplayName={"TypeCell web"}
85-
// TODO: does this work correctly after SSO login is declined?
86-
pageAfterLogin={pageAfterLogin}
87-
serverConfig={props.config}
88-
/>
22+
<BrowserRouter>
23+
<Routes>
24+
<Route path="/" element={<Main />}>
25+
<Route path="@:userParam" element={<ProfileRoute />}></Route>
26+
<Route
27+
path="@:userParam/:documentParam"
28+
element={<DocumentRoute />}></Route>
29+
<Route index element={<StartScreen></StartScreen>}></Route>
30+
<Route path="*" element={<DynamicRoute />} />
31+
</Route>
32+
<Route
33+
path="/register"
34+
element={<Register config={props.config} />}
35+
/>
36+
<Route path="/recover" element={<div>Not implemented yet</div>} />
37+
<Route path="/login" element={<Login config={props.config} />} />
38+
{/* todo: notfound? */}
39+
</Routes>
40+
</BrowserRouter>
8941
);
90-
} else if (navigationStore.currentPage.page === "recover") {
91-
return <div>Not implemented yet</div>;
92-
} else {
93-
return <Main currentPage={navigationStore.currentPage} />;
9442
}
9543
});
9644
export default App;

packages/editor/src/app/documentRenderers/DocumentView.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ import { DocConnection } from "../../store/DocConnection";
66
import PluginResource from "../../store/PluginResource";
77
import ProjectResource from "../../store/ProjectResource";
88
import DocumentMenu from "../main/components/documentMenu";
9+
import { Breadcrumb } from "../main/components/documentMenu/Breadcrumb";
10+
import { MenuBar } from "../main/components/menuBar/MenuBar";
11+
// import RichTextRenderer from "./richtext/RichTextRenderer";
12+
import styles from "./DocumentView.module.css";
913
// import { CustomRenderer } from "./custom/CustomRenderer";
1014
import NotebookRenderer from "./notebook/NotebookRenderer";
1115
import PluginRenderer from "./plugin/PluginRenderer";
16+
import ProjectContainer from "./project/ProjectContainer";
1217
import ProjectRenderer from "./project/ProjectRenderer";
13-
// import RichTextRenderer from "./richtext/RichTextRenderer";
14-
import styles from "./DocumentView.module.css";
1518

1619
type Props = {
1720
id: Identifier;
@@ -38,13 +41,14 @@ const DocumentView = observer((props: Props) => {
3841
newConnection.dispose();
3942
setConnection(undefined);
4043
};
41-
}, [props.id]);
44+
// eslint-disable-next-line react-hooks/exhaustive-deps
45+
}, [props.id.toString()]);
4246

4347
if (!connection) {
4448
return null;
4549
}
4650
if (connection.doc === "loading") {
47-
return <div>Loading</div>;
51+
return <div>Loading doc</div>;
4852
} else if (connection.doc === "not-found") {
4953
return <div>Not found</div>;
5054
}
@@ -68,9 +72,11 @@ const DocumentView = observer((props: Props) => {
6872
return (
6973
<div className={styles.view}>
7074
{!props.hideDocumentMenu && (
71-
<DocumentMenu document={connection.doc}></DocumentMenu>
75+
<MenuBar>
76+
<Breadcrumb identifier={props.id} />
77+
</MenuBar>
7278
)}
73-
<ProjectRenderer
79+
<ProjectContainer
7480
isNested={true}
7581
key={connection.doc.id}
7682
project={connection.doc.getSpecificType(ProjectResource)!}
@@ -113,7 +119,7 @@ const DocumentView = observer((props: Props) => {
113119
}
114120
});
115121

116-
export default DocumentView;
122+
export default React.memo(DocumentView);
117123

118124
/*
119125
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { observer } from "mobx-react-lite";
2+
import React from "react";
3+
import { Outlet, useLocation, useNavigate } from "react-router-dom";
4+
import { path } from "vscode-lib";
5+
import { parseIdentifier } from "../../../identifiers";
6+
import ProjectResource from "../../../store/ProjectResource";
7+
import DocumentView from "../DocumentView";
8+
import FolderView from "./directoryNavigation/FolderView";
9+
import SidebarTree from "./directoryNavigation/SidebarTree";
10+
import { filesToTreeNodes } from "./directoryNavigation/treeNodeUtil";
11+
import styles from "./ProjectRenderer.module.css";
12+
13+
type Props = {
14+
project: ProjectResource;
15+
isNested?: boolean;
16+
};
17+
18+
const ProjectContainer = observer((props: Props) => {
19+
const location = useLocation();
20+
const navigate = useNavigate();
21+
const files = Array.from(props.project.files.keys()).sort();
22+
23+
const tree = filesToTreeNodes(
24+
files.map((f) => ({
25+
fileName: f,
26+
}))
27+
);
28+
29+
const onClick = (item: string) => {
30+
const isDocs = location.pathname.startsWith("/docs");
31+
32+
navigate({
33+
pathname: props.isNested
34+
? path.join(location.pathname, "/", item)
35+
: isDocs
36+
? item
37+
: ":/" + item,
38+
});
39+
};
40+
let defaultFile = files.find((f) => f === "README.md");
41+
let defaultFileContent = <></>;
42+
if (defaultFile) {
43+
// TODO: cleanup?
44+
// Directory listing with a default file
45+
let idTemp = parseIdentifier(props.project.identifier.uri.toString());
46+
idTemp.subPath = defaultFile;
47+
let documentIdentifier = parseIdentifier(
48+
idTemp.fullUriOfSubPath()!.toString()
49+
);
50+
defaultFileContent = (
51+
<DocumentView
52+
hideDocumentMenu={true}
53+
id={documentIdentifier}
54+
isNested={true}
55+
/>
56+
);
57+
}
58+
59+
if (props.isNested) {
60+
return (
61+
<div>
62+
<div className={styles.folderContainer}>
63+
<FolderView onClick={onClick} tree={tree} />
64+
</div>
65+
{defaultFileContent}
66+
</div>
67+
);
68+
} else {
69+
return (
70+
<div className={styles.projectContainer}>
71+
<div className={styles.sidebarContainer}>
72+
<SidebarTree onClick={onClick} tree={tree} />
73+
</div>
74+
{/* {defaultFileContent} */}
75+
<Outlet
76+
context={{
77+
defaultFileContent,
78+
parentIdentifier: props.project.identifier,
79+
}}
80+
/>
81+
</div>
82+
);
83+
}
84+
});
85+
86+
export default ProjectContainer;

0 commit comments

Comments
 (0)