diff --git a/website/db.ts b/website/db.ts index 8078fb12..f3e463ef 100644 --- a/website/db.ts +++ b/website/db.ts @@ -25,6 +25,7 @@ export interface Database { updateDoc(nbId: string, doc: NotebookDoc): Promise; clone(existingDoc: NotebookDoc): Promise; create(): Promise; + queryProfile(uid: string): Promise; queryLatest(): Promise; signIn(): void; signOut(): void; @@ -167,6 +168,19 @@ class DatabaseFB implements Database { return out.reverse(); } + async queryProfile(uid): Promise { + 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.unshift({ nbId, doc }); + }); + return out.reverse(); + } + signIn() { lazyInit(); const provider = new firebase.auth.GithubAuthProvider(); @@ -218,6 +232,11 @@ export class DatabaseMock implements Database { return "createdNbId"; } + async queryProfile(uid: string): Promise { + this.inc("queryProfile"); + return []; + } + async queryLatest(): Promise { this.inc("queryLatest"); return []; diff --git a/website/main.scss b/website/main.scss index e9a88aaf..d569055b 100644 --- a/website/main.scss +++ b/website/main.scss @@ -478,6 +478,26 @@ header { } } +.blurb { + background-color: #fff; + box-shadow: inset 0 1px 0 #d8d8d8; + font-size: 1.2rem; + letter-spacing: 0.5px; + display: flex; + margin-top: 8px; + + img { + margin-right: $size0; + } + + a { + font-size: $size1; + line-height: $size2; + color: $textColor; + margin: $size1; + } +} + .propel-logo { display: flex; flex-direction: row; @@ -610,20 +630,20 @@ header { @include buttonHover; } -.blurb { - background-color: #fff; - box-shadow: inset 0 1px 0 #d8d8d8; - font-size: 1.2rem; - letter-spacing: 0.5px; - display: flex; - margin-top: 8px; +.notebook-container { + max-width: 960px; + margin: 0 auto; + padding: 0 8px; - img { - margin-right: $size0; + .blurb { + box-shadow: inset 0 -1px 0 $borderColor; + background-color: #fff; + margin-bottom: 16px; + font-size: 18px; } } -.notebook-container { +.profile-blurb { max-width: 960px; margin: 0 auto; padding: 0 8px; @@ -633,6 +653,11 @@ header { background-color: #fff; margin-bottom: 16px; } + + p { + font-size: 18px; + font-weight: 100; + } } .most-recent { diff --git a/website/nb.ts b/website/nb.ts index e2c59102..ee1deb1f 100644 --- a/website/nb.ts +++ b/website/nb.ts @@ -403,10 +403,12 @@ export class FixedCell extends Component { export interface NotebookRootProps { userInfo?: db.UserInfo; nbId?: string; + uid?: string; } export interface NotebookRootState { nbId?: string; + uid?: string; } export class NotebookRoot extends Component { }, "+ 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 ProfileState { + latest: db.NbInfo[]; + profile: string; +} + +export class Profile extends Component { + 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.queryProfile(this.props.uid); + this.setState({ latest }); + } + } + + render() { + if (!this.state.latest) { + return h(Loading, null); + } + 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)), + ) ); } } @@ -567,11 +631,11 @@ export class Notebook extends Component { 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; @@ -687,6 +751,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" }, @@ -696,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 ]);