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 27, 2018
1 parent 76ab646 commit de9c12b
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 25 deletions.
23 changes: 23 additions & 0 deletions website/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface Database {
updateDoc(nbId: string, doc: NotebookDoc): Promise<void>;
clone(existingDoc: NotebookDoc): Promise<string>;
create(): Promise<string>;
queryProfile(uid: string, limit: number): Promise<NbInfo[]>;
queryLatest(): Promise<NbInfo[]>;
signIn(): void;
signOut(): void;
Expand Down Expand Up @@ -167,6 +168,19 @@ class DatabaseFB implements Database {
return out.reverse();
}

async queryProfile(uid: string, limit: number): Promise<NbInfo[]> {
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();
Expand Down Expand Up @@ -224,6 +238,15 @@ export class DatabaseMock implements Database {
return "createdNbId";
}

async queryProfile(uid: string, limit: number): 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
128 changes: 103 additions & 25 deletions website/nb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,7 @@ export interface NotebookRootProps {

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

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

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

this.state = { nbId, profileUid };
}

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

return h("div", { "class": "notebook" },
Expand All @@ -474,26 +484,29 @@ 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});
}
// TODO potentially these two queries can be combined into one.
const latest = await db.active.queryLatest();
const own = await db.active.queryProfile(this.props.uid, 3);
this.setState({latest, own});
}

async onCreate() {
Expand All @@ -504,31 +517,78 @@ 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 = 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),
),
);
});

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)),
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<ProfileProps, ProfileState> {
async componentWillMount() {
const latest = await db.active.queryProfile(this.props.profileUid, 100);
this.setState({ latest });
}

render() {
if (!this.state.latest || 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", { "class": "notebook-container"},
h("div", {"class": "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 @@ -595,7 +655,7 @@ export class Notebook extends Component<NotebookProps, NotebookState> {
this.setState({ editingTitle: false });
doc.title = this.titleInput.value;
this.update(doc);
}
}

async onDelete(i) {
const doc = this.state.doc;
Expand Down Expand Up @@ -719,6 +779,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 @@ -728,12 +801,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 @@ -201,6 +201,28 @@ testBrowser(async function notebook_progressBar() {
assert(percent() === 0);
});

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 }));

});

// Call this to ensure that the DOM has been updated after events.
function flush(): Promise<void> {
rerender();
Expand Down

0 comments on commit de9c12b

Please sign in to comment.