Skip to content
This repository has been archived by the owner on May 30, 2019. It is now read-only.

Commit

Permalink
profile pages
Browse files Browse the repository at this point in the history
  • Loading branch information
alaq committed Mar 26, 2018
1 parent 1f92a57 commit 0d8b11b
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 29 deletions.
23 changes: 23 additions & 0 deletions website/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface Database {
updateDoc(nbId: string, doc: NotebookDoc): Promise<void>;
clone(existingDoc: NotebookDoc): Promise<string>;
create(): Promise<string>;
queryProfile(uid: string): Promise<NbInfo[]>;
queryLatest(): Promise<NbInfo[]>;
signIn(): void;
signOut(): void;
Expand Down Expand Up @@ -160,6 +161,19 @@ class DatabaseFB implements Database {
return out.reverse();
}

async queryProfile(uid: string): Promise<NbInfo[]> {
lazyInit();
const query = nbCollection.where("owner.uid", "==", uid);
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();
Expand Down Expand Up @@ -211,6 +225,15 @@ export class DatabaseMock implements Database {
return "createdNbId";
}

async queryProfile(uid: string): Promise<NbInfo[]> {
this.inc("queryProfile");
if (uid === defaultOwner.uid) {
return [{ nbId: "default", doc: defaultDoc }];
} else {
return [];
}
}

async queryLatest(): Promise<NbInfo[]> {
this.inc("queryLatest");
return [];
Expand Down
139 changes: 110 additions & 29 deletions website/nb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,10 +403,12 @@ export class FixedCell extends Component<FixedProps, CellState> {
export interface NotebookRootProps {
userInfo?: db.UserInfo;
nbId?: string;
uid?: string;
}

export interface NotebookRootState {
nbId?: string;
uid?: string;
}

export class NotebookRoot extends Component<NotebookRootProps,
Expand All @@ -422,7 +424,15 @@ export class NotebookRoot extends Component<NotebookRootProps,
nbId = matches ? matches[1] : null;
}

this.state = { nbId };
let uid;
if (this.props.uid) {
uid = this.props.uid;
} else {
const matches = window.location.search.match(/profile=(\w+)/);
uid = matches ? matches[1] : null;
}

this.state = { nbId, uid };
}

render() {
Expand All @@ -432,8 +442,15 @@ export class NotebookRoot extends Component<NotebookRootProps,
nbId: this.state.nbId,
userInfo: this.props.userInfo,
});
} else if (this.state.uid) {
body = h(Profile, {
uid: this.state.uid,
userInfo: this.props.userInfo,
});
} else {
body = h(MostRecent, null);
body = h(MostRecent, {
uid: this.props.userInfo ? this.props.userInfo.uid : "",
});
}

return h("div", { "class": "notebook" },
Expand All @@ -446,26 +463,28 @@ export class NotebookRoot extends Component<NotebookRootProps,
}
}

export interface MostRecentProps {
uid: string;
}

export interface MostRecentState {
latest: db.NbInfo[];
own: db.NbInfo[];
}

function nbUrl(nbId: string): string {
// Careful, S3 is finicy about what URLs it serves. So
// Careful, S3 is finicky about what URLs it serves. So
// /notebook?nbId=blah will get redirect to /notebook/
// because it is a directory with an index.html in it.
const u = window.location.origin + "/notebook/?nbId=" + nbId;
return u;
}

export class MostRecent extends Component<any, MostRecentState> {
export class MostRecent extends Component<MostRecentProps, MostRecentState> {
async componentWillMount() {
// Only query firebase when in the browser.
// This is to avoiding calling into firebase during static HTML generation.
if (IS_WEB) {
const latest = await db.active.queryLatest();
this.setState({latest});
}
const latest = await db.active.queryLatest();
const own = await db.active.queryProfile(this.props.uid);
this.setState({latest, own});
}

async onCreate() {
Expand All @@ -476,33 +495,77 @@ export class MostRecent extends Component<any, MostRecentState> {
}

render() {
if (!this.state.latest) {
if (!this.state.latest || !this.state.own) {
return h(Loading, null);
}
const notebookList = this.state.latest.map(info => {
const snippit = info.doc.cells.map(normalizeCode)
.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),
),
);
});
const empty = !this.state.own ?
h("p", {"class": "most-recent-header"}, "Nothing to show yet.") : "";
return h("div", { "class": "most-recent" },
h("div", {"class": "most-recent-header"},
h("div", {"class": "most-recent-header-title"},
h("h2", null, "Recently Updated"),
h("h2", null, "Your Most Recent Notebooks"),
),
h("div", {"class": "most-recent-header-cta"},
h("button", { "class": "create-notebook",
"onClick": () => this.onCreate(),
}, "+ New Notebook"),
),
),
h("ol", null, ...notebookList),
empty,
h("ol", null, ...notebookList(this.state.own.slice(0, 3))),
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 {
uid: string;
}

export interface ProfileState {
latest: db.NbInfo[];
}

export class Profile extends Component<ProfileProps, ProfileState> {
async componentWillMount() {
const latest = await db.active.queryProfile(this.props.uid);
this.setState({ latest });
}

render() {
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)),
)
);
}
}
Expand Down Expand Up @@ -569,11 +632,11 @@ export class Notebook extends Component<NotebookProps, NotebookState> {
doc.title = this.state.typedTitle;
this.setState({ ...doc, editingTitle: false });
this.update(doc);
}
}

async onTypedTitle(event) {
this.setState({ typedTitle: event.target.value });
}
this.setState({ typedTitle: event.target.value });
}

async onDelete(i) {
const doc = this.state.doc;
Expand Down Expand Up @@ -688,6 +751,19 @@ export class Notebook extends Component<NotebookProps, NotebookState> {
}
}

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" },
Expand All @@ -697,12 +773,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
]);
Expand Down
22 changes: 22 additions & 0 deletions website/nb_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,25 @@ testBrowser(async function notebook_NotebookLoggedIn() {
*/

});

testBrowser(async function notebook_profileSmoke() {
let mdb = db.enableMock();
resetPage();
let el = h(nb.Profile, { uid: "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, { uid: db.defaultOwner.uid });
render(el, document.body);
await Promise.resolve(); // Wait for promise queue to flush.
profileBlurbs = document.querySelectorAll(".profile-blurb");
assert(profileBlurbs.length === 1);
assert(objectsEqual(mdb.counts, { queryProfile: 1 }));

});

0 comments on commit 0d8b11b

Please sign in to comment.