From 95c206b346718cfadf809cf84250162042a57898 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Fri, 29 Jun 2018 18:04:20 -0700 Subject: [PATCH] Allow selecting data to load in V3 cred explorer (#460) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Text input boxes for repository owner and name now appear. “Loading the data” consists of logging the attempt to the console. Test Plan: Run `yarn start`, and note that the inputs are keyed against the same local store key as their V1 equivalents. Note that clicking “Load data” prints a message to the console. Paired with @decentralion. wchargin-branch: v3-load-data-ui --- src/v3/app/App.js | 7 ++ src/v3/app/LocalStore.js | 87 +++++++++++++++++++++++++ src/v3/app/credExplorer/App.js | 92 +++++++++++++++++++++++++++ src/v3/app/credExplorer/LocalStore.js | 5 ++ 4 files changed, 191 insertions(+) create mode 100644 src/v3/app/LocalStore.js create mode 100644 src/v3/app/credExplorer/App.js create mode 100644 src/v3/app/credExplorer/LocalStore.js 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 ( +
+
+

Cred Explorer

+
+

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