From 3581e8a705177ba47a905febb3e2a63d26e69d9b Mon Sep 17 00:00:00 2001 From: Adrien Lacquemant Date: Tue, 27 Mar 2018 14:00:46 -0400 Subject: [PATCH] profile pages --- website/db.ts | 23 ++++++++ website/nb.ts | 130 ++++++++++++++++++++++++++++++++++++--------- website/nb_test.ts | 23 ++++++++ 3 files changed, 151 insertions(+), 25 deletions(-) diff --git a/website/db.ts b/website/db.ts index d3545da6..1533abe3 100644 --- a/website/db.ts +++ b/website/db.ts @@ -26,6 +26,7 @@ export interface Database { updateDoc(nbId: string, doc: NotebookDoc): Promise; clone(existingDoc: NotebookDoc): Promise; create(): Promise; + queryProfile(uid: string, limit: number): Promise; queryLatest(): Promise; signIn(): void; signOut(): void; @@ -167,6 +168,19 @@ class DatabaseFB implements Database { return out.reverse(); } + async queryProfile(uid: string, limit: number): Promise { + lazyInit(); + const query = nbCollection.where("owner.uid", "==", uid).limit(limit); + const snapshots = await query.get(); + const out = []; + snapshots.forEach(snap => { + const nbId = snap.id; + const doc = snap.data(); + out.push({ nbId, doc }); + }); + return out; + } + signIn() { lazyInit(); const provider = new firebase.auth.GithubAuthProvider(); @@ -224,6 +238,15 @@ export class DatabaseMock implements Database { return "createdNbId"; } + async queryProfile(uid: string, limit: number): Promise { + this.inc("queryProfile"); + if (uid === defaultOwner.uid) { + return [{ nbId: "default", doc: defaultDoc }]; + } else { + return []; + } + } + async queryLatest(): Promise { this.inc("queryLatest"); return []; diff --git a/website/nb.ts b/website/nb.ts index 642924ae..94b3ac5f 100644 --- a/website/nb.ts +++ b/website/nb.ts @@ -435,6 +435,7 @@ export interface NotebookRootProps { export interface NotebookRootState { nbId?: string; + profileUid?: string; } export class NotebookRoot extends Component { }, "+ New Notebook"), ), ), - h("ol", null, ...notebookList), + empty, + h("ol", null, ...notebookList(this.state.own)), + h("div", {"class": "most-recent-header"}, + h("div", {"class": "most-recent-header-title"}, + h("h2", null, "Recently Updated"), + ), + ), + h("ol", null, ...notebookList(this.state.latest)), + ); + } +} + +export interface ProfileProps { + profileUid: string; +} + +export interface ProfileState { + latest: db.NbInfo[]; +} + +export class Profile extends Component { + async componentWillMount() { + const latest = await db.active.queryProfile(this.props.profileUid, 100); + this.setState({ latest }); + } + + render() { + if (!this.state.latest) { + return h(Loading, null) + } else if (this.state.latest.length === 0) { + return h("h1", null, "User has no notebooks"); + } + const doc = this.state.latest[0].doc; + const profileBlurb = h("div", { "class": "blurb" }, null, [ + h("div", { "class": "blurb-avatar" }, + h(Avatar, { userInfo: doc.owner }), + ), + h("div", { "class": "blurb-name" }, + h("p", { "class": "displayName" }, doc.owner.displayName), + ), + h("div", { "class": "date-created" }, + h("p", { "class": "created" }, + `Most Recent Update ${fmtDate(doc.updated)}.`), + ), + ]); + + return h("div", null, + h("div", {"class": "profile-blurb"}, profileBlurb), + h("div", { "class": "most-recent" }, + h("div", {"class": "most-recent-header"}, + h("div", {"class": "most-recent-header-title"}, + h("h2", null, + doc.owner.displayName + "'s Recently Updated Notebooks") + ), + ), + h("ol", null, ...notebookList(this.state.latest)), + ) ); } } @@ -595,7 +657,7 @@ export class Notebook extends Component { this.setState({ editingTitle: false }); doc.title = this.titleInput.value; this.update(doc); - } + } async onDelete(i) { const doc = this.state.doc; @@ -719,6 +781,19 @@ export class Notebook extends Component { } } +function notebookList(notebooks: db.NbInfo[]): JSX.Element[] { + return notebooks.map(info => { + const snippit = db.getInputCodes(info.doc).join("\n").slice(0, 100); + const href = nbUrl(info.nbId); + return h("a", { href }, + h("li", null, + h("div", { class: "code-snippit" }, snippit), + notebookBlurb(info.doc, false) + ) + ); + }); +} + function notebookBlurb(doc: db.NotebookDoc, showDates = true): JSX.Element { const dates = !showDates ? [] : [ h("div", { "class": "date-created" }, @@ -728,12 +803,17 @@ function notebookBlurb(doc: db.NotebookDoc, showDates = true): JSX.Element { h("p", { "class": "updated" }, `Updated ${fmtDate(doc.updated)}.`), ), ]; + const profileUrl = window.location.origin + "/notebook/?profile=" + + doc.owner.uid; return h("div", { "class": "blurb" }, null, [ h("div", { "class": "blurb-avatar" }, h(Avatar, { userInfo: doc.owner }), ), h("div", { "class": "blurb-name" }, - h("p", { "class": "displayName" }, doc.owner.displayName), + h("a", { + "class": "displayName", + "href": profileUrl + }, doc.owner.displayName), ), ...dates ]); diff --git a/website/nb_test.ts b/website/nb_test.ts index 48b3420b..cf8383fe 100644 --- a/website/nb_test.ts +++ b/website/nb_test.ts @@ -201,6 +201,29 @@ testBrowser(async function notebook_progressBar() { assert(percent() === 0); }); +testBrowser(async function notebook_profileSmoke() { + let mdb = db.enableMock(); + resetPage(); + let el = h(nb.Profile, { profileUid: "non-existant" }); + render(el, document.body); + await Promise.resolve(); // Wait for promise queue to flush. + let profileBlurbs = document.querySelectorAll(".profile-blurb"); + assert(profileBlurbs.length === 0); + assert(objectsEqual(mdb.counts, { queryProfile: 1 })); + + // Try again with a real uid. + mdb = db.enableMock(); + resetPage(); + el = h(nb.Profile, { profileUid: db.defaultOwner.uid }); + render(el, document.body); + await Promise.resolve(); // Wait for promise queue to flush. + profileBlurbs = document.querySelectorAll(".profile-blurb"); + console.log(profileBlurbs); + assert(profileBlurbs.length === 1); + assert(objectsEqual(mdb.counts, { queryProfile: 1 })); + +}); + // Call this to ensure that the DOM has been updated after events. function flush(): Promise { rerender();