diff --git a/src/v3/app/App.js b/src/v3/app/App.js
index 39141f0..93e1cbf 100644
--- a/src/v3/app/App.js
+++ b/src/v3/app/App.js
@@ -3,9 +3,12 @@
import React from "react";
import {Route, NavLink, type Match} from "react-router-dom";
+import CredExplorer from "./credExplorer/App";
+
export default class App extends React.Component<{match: Match}> {
render() {
const {match} = this.props;
+ const CRED_EXPLORER_ROUTE = match.url + "/explorer";
return (
+
);
}
diff --git a/src/v3/app/LocalStore.js b/src/v3/app/LocalStore.js
new file mode 100644
index 0000000..99c8b96
--- /dev/null
+++ b/src/v3/app/LocalStore.js
@@ -0,0 +1,87 @@
+// @flow
+
+/*
+ * A simple abstraction over 'localStorage' to provide transparent JSON
+ * serialization and deserialization.
+ *
+ * The implementation is borrowed heavily from Khan Academy's LocalStore
+ * module, and also KaVideoPlayer's SafeLocalStore module.
+ */
+export default class LocalStore {
+ version: string;
+ keyPrefix: string;
+
+ constructor({version, keyPrefix}: {|+version: string, +keyPrefix: string|}) {
+ this.version = version;
+ this.keyPrefix = keyPrefix;
+ }
+
+ cacheKey(key: string): string {
+ if (!key) {
+ throw new Error("Falsy key provided to cacheKey: " + key);
+ }
+ return [this.keyPrefix, this.version, key].join(":");
+ }
+
+ get(key: string, whenUnavailable: any): any {
+ if (!this.isEnabled()) {
+ return whenUnavailable;
+ }
+ try {
+ const data = window.localStorage[this.cacheKey(key)];
+ if (data) {
+ return JSON.parse(data);
+ } else {
+ return whenUnavailable;
+ }
+ } catch (e) {
+ // If we had trouble retrieving, like FF's NS_FILE_CORRUPTED:
+ // http://stackoverflow.com/q/18877643/
+ return whenUnavailable;
+ }
+ }
+
+ set(key: string, data: any): void {
+ if (!this.isEnabled()) {
+ return;
+ }
+ const stringified = JSON.stringify(data);
+
+ try {
+ window.localStorage[this.cacheKey(key)] = stringified;
+ } catch (e) {
+ // Probably went over the storage limit... that's not good.
+ throw e;
+ }
+ }
+
+ /*
+ * Delete whatever data was associated with the given key.
+ */
+ del(key: string): void {
+ if (!this.isEnabled()) {
+ return;
+ }
+ const cacheKey = this.cacheKey(key);
+ if (cacheKey in window.localStorage) {
+ // (IE throws when deleting a non-existent entry.)
+ delete window.localStorage[cacheKey];
+ }
+ }
+
+ /*
+ * Local storage might be disabled in old browsers or in Safari's
+ * private browsing mode. Don't die.
+ */
+ isEnabled(): boolean {
+ const uid = String(+new Date());
+ try {
+ window.sessionStorage[uid] = uid;
+ const enabled = window.sessionStorage[uid] === uid;
+ window.sessionStorage.removeItem(uid);
+ return enabled;
+ } catch (e) {
+ return false;
+ }
+ }
+}
diff --git a/src/v3/app/credExplorer/App.js b/src/v3/app/credExplorer/App.js
new file mode 100644
index 0000000..c2352b7
--- /dev/null
+++ b/src/v3/app/credExplorer/App.js
@@ -0,0 +1,92 @@
+// @flow
+
+import React from "react";
+import {StyleSheet, css} from "aphrodite/no-important";
+
+import LocalStore from "./LocalStore";
+
+type Props = {};
+type State = {
+ repoOwner: string,
+ repoName: string,
+};
+
+const REPO_OWNER_KEY = "repoOwner";
+const REPO_NAME_KEY = "repoName";
+
+export default class App extends React.Component {
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ repoOwner: "",
+ repoName: "",
+ };
+ }
+
+ componentDidMount() {
+ this.setState((state) => ({
+ repoOwner: LocalStore.get(REPO_OWNER_KEY, state.repoOwner),
+ repoName: LocalStore.get(REPO_NAME_KEY, state.repoName),
+ }));
+ }
+
+ render() {
+ return (
+
+
+
Welcome to the SourceCred Explorer!
+
+
+
+
+
+
+
+
+ );
+ }
+
+ loadData() {
+ const validRe = /^[A-Za-z0-9_-]+$/;
+ const {repoOwner, repoName} = this.state;
+ if (!repoOwner.match(validRe)) {
+ console.error(`Invalid repository owner: ${JSON.stringify(repoOwner)}`);
+ return;
+ }
+ if (!repoName.match(validRe)) {
+ console.error(`Invalid repository name: ${JSON.stringify(repoName)}`);
+ return;
+ }
+ console.log(`Would load data for: ${repoOwner}/${repoName}.`);
+ }
+}
+
+const styles = StyleSheet.create({
+ header: {
+ color: "#090",
+ },
+});
diff --git a/src/v3/app/credExplorer/LocalStore.js b/src/v3/app/credExplorer/LocalStore.js
new file mode 100644
index 0000000..872e7c2
--- /dev/null
+++ b/src/v3/app/credExplorer/LocalStore.js
@@ -0,0 +1,5 @@
+// @flow
+
+import LocalStore from "../LocalStore";
+
+export default new LocalStore({version: "1", keyPrefix: "cred-explorer"});