diff --git a/src/Cell.js b/src/Cell.js index 30c170bd..f08dadbc 100644 --- a/src/Cell.js +++ b/src/Cell.js @@ -14,7 +14,8 @@ const Cell = React.memo(props => { skin, requestToken, setSelectedSkin, - requestUnloadedSkin + requestUnloadedSkin, + permalinkUrl } = props; const { width, height } = data; React.useEffect(() => { @@ -51,7 +52,8 @@ const Cell = React.memo(props => { width={width} selectSkin={setSelectedSkin} color={color} - permalink={Utils.getPermalinkUrlFromHashAndFilename(hash, skin.fileName)} + // TODO: This is werid because there is an implicit assumption that this is always avaliable if we have the skin + permalink={permalinkUrl} /> ); }); @@ -65,9 +67,13 @@ const mapStateToProps = (state, ownProps) => { rowIndex, columnIndex }); + const getPermalinkUrlFromHash = Selectors.getPermalinkUrlFromHashGetter( + state + ); return { requestToken, - skin + skin, + permalinkUrl: skin == null ? null : getPermalinkUrlFromHash(skin.hash) }; }; diff --git a/src/FocusedSkin.js b/src/FocusedSkin.js index d50f2ec2..b526cb5e 100644 --- a/src/FocusedSkin.js +++ b/src/FocusedSkin.js @@ -6,10 +6,11 @@ import DownloadLink from "./DownloadLink"; import * as Utils from "./utils"; import * as Selectors from "./redux/selectors"; import * as Actions from "./redux/actionCreators"; -import { SCREENSHOT_HEIGHT, SCREENSHOT_WIDTH, SKIN_WIDTH } from "./constants"; +import { SCREENSHOT_HEIGHT, SCREENSHOT_WIDTH } from "./constants"; import { delay } from "rxjs/operators"; -import { Subject, combineLatest, timer, fromEvent } from "rxjs"; +import { Subject, combineLatest, timer, fromEvent, from } from "rxjs"; import Disposable from "./Disposable"; +import { search } from "./algolia"; class FocusedSkin extends React.Component { constructor(props) { @@ -64,7 +65,40 @@ class FocusedSkin extends React.Component { ); } + _fetchSkinData() { + if (this.props.skinData != null) { + return; + } + // OMG Giant hack. Kill this please. We should be able to get this data from our own server. + from( + search(this.props.hash, { hitsPerPage: 2, typoTolerance: 0 }) + ).subscribe(results => { + if (results.nbHits.length === 1) { + console.error( + "Failed to get skin data for hash", + this.props.hash, + results + ); + return; + } + if (results.nbHits.length > 1) { + console.error( + "Failed to uniquely get skin data for hash", + this.props.hash + ); + return; + } + const { fileName, color } = results.hits[0]; + this.props.gotSkinData(this.props.hash, { + md5: this.props.hash, + fileName, + color + }); + }); + } + componentDidMount() { + this._fetchSkinData(); this._disposable.add( fromEvent(window.document, "keydown").subscribe(e => { if (e.key === "ArrowRight") { @@ -183,7 +217,7 @@ class FocusedSkin extends React.Component { }} onLoad={() => this.setState({ previewLoaded: true })} src={Utils.screenshotUrlFromHash(this.props.hash)} - alt={this.props.fileName} + alt={this.props.skinData && this.props.skinData.fileName} /> )} @@ -204,7 +238,7 @@ class FocusedSkin extends React.Component { e.target.setSelectionRange(0, e.target.value.length) } className="permalink-input" - value={Utils.getAbsolutePermalinkUrlFromHash(this.props.hash)} + value={this.props.absolutePermalink} readOnly autoFocus /> @@ -220,7 +254,9 @@ class FocusedSkin extends React.Component { )} - {this.props.fileName} + {this.props.skinData + ? this.props.skinData.fileName + : "Filename loading..."} {" ["} {"] ["} { this.setState({ showLink: !this.state.showLink }); e.preventDefault(); @@ -264,14 +300,22 @@ class FocusedSkin extends React.Component { } } -const mapStateToProps = (state, ownProps) => ({ - hash: Selectors.getSelectedSkinHash(state), - initialPosition: Selectors.getSelectedSkinPosition(state), - fileExplorerOpen: Selectors.getFileExplorerOpen(state), - fileName: state.skins[ownProps.hash].fileName -}); +const mapStateToProps = (state, ownProps) => { + return { + hash: Selectors.getSelectedSkinHash(state), + initialPosition: Selectors.getSelectedSkinPosition(state), + fileExplorerOpen: Selectors.getFileExplorerOpen(state), + skinData: state.skins[ownProps.hash] || null, + absolutePermalink: Selectors.getAbsolutePermalinkUrlFromHashGetter(state)( + ownProps.hash + ) + }; +}; const mapDispatchToProps = dispatch => ({ + gotSkinData(hash, data) { + dispatch({ type: "GOT_SKIN_DATA", hash, data }); + }, selectRelativeSkin(offset) { dispatch(Actions.selectRelativeSkin(offset)); }, diff --git a/src/algolia.js b/src/algolia.js index 84365fe8..3a1d371d 100644 --- a/src/algolia.js +++ b/src/algolia.js @@ -3,7 +3,7 @@ var client = algoliasearch("HQ9I5Z6IM5", "6466695ec3f624a5fccf46ec49680e51"); var index = client.initIndex("Skins"); -export function search(query) { +export function search(query, options = {}) { return new Promise((resolve, reject) => { index.search( { @@ -13,7 +13,8 @@ export function search(query) { hitsPerPage: 1000, // https://www.algolia.com/doc/api-reference/api-parameters/typoTolerance/ // min: Retrieve records with the smallest number of typos. - typoTolerance: "min" + typoTolerance: "min", + ...options }, (err, content) => { if (err != null) { diff --git a/src/redux/epics.js b/src/redux/epics.js index 753bb726..0989e544 100644 --- a/src/redux/epics.js +++ b/src/redux/epics.js @@ -3,14 +3,7 @@ import { of, from, empty } from "rxjs"; import * as Actions from "./actionCreators"; import * as Selectors from "./selectors"; import * as Utils from "../utils"; -import { - filter, - switchMap, - map, - ignoreElements, - mergeMap, - tap -} from "rxjs/operators"; +import { filter, switchMap, map, mergeMap } from "rxjs/operators"; import { search } from "../algolia"; const urlChangedEpic = actions => diff --git a/src/redux/reducer.js b/src/redux/reducer.js index b7192706..ec9fb881 100644 --- a/src/redux/reducer.js +++ b/src/redux/reducer.js @@ -25,6 +25,12 @@ const defaultState = { export default function reducer(state = defaultState, action) { switch (action.type) { + case "GOT_SKIN_DATA": { + return { + ...state, + skins: { ...state.skins, [action.hash]: action.data } + }; + } case "GOT_SKIN_CHUNK": { const newSkins = { ...state.skins }; const newDefaultSkins = [...state.defaultSkins]; diff --git a/src/redux/selectors.js b/src/redux/selectors.js index c7de2dea..662307ca 100644 --- a/src/redux/selectors.js +++ b/src/redux/selectors.js @@ -94,19 +94,50 @@ export function getRandomSkinHash(state) { return skinHashes[randomIndex]; } +export const getPermalinkUrlFromHashGetter = createSelector( + getSkins, + skins => { + return hash => { + const skin = skins[hash]; + if (skin == null) { + return `/skin/${hash}/`; + } + return `/skin/${hash}/${skin.fileName}/`; + }; + } +); + +export const getAbsolutePermalinkUrlFromHashGetter = createSelector( + getPermalinkUrlFromHashGetter, + getPermalinkUrlFromHash => { + return hash => { + return window.location.origin + getPermalinkUrlFromHash(hash); + }; + } +); + export const getUrl = createSelector( getActiveContentPage, getSelectedSkinHash, getSearchQuery, getFileExplorerOpen, getFocusedSkinFile, - (activeContentPage, hash, query, fileExplorerOpen, focusedSkinFile) => { + getSkins, + getPermalinkUrlFromHashGetter, + ( + activeContentPage, + hash, + query, + fileExplorerOpen, + focusedSkinFile, + skins, + getPermalinkUrlFromHash + ) => { if (activeContentPage === ABOUT_PAGE) { return "/about/"; } if (hash) { - // TODO: Add a human readable version - const skinUrl = Utils.getPermalinkUrlFromHash(hash); + const skinUrl = getPermalinkUrlFromHash(hash); if (fileExplorerOpen && focusedSkinFile) { return `${skinUrl}files/${encodeURIComponent( focusedSkinFile.fileName diff --git a/src/utils.js b/src/utils.js index bd6c0288..f56e9f43 100644 --- a/src/utils.js +++ b/src/utils.js @@ -6,20 +6,6 @@ export function skinUrlFromHash(hash) { return `https://s3.amazonaws.com/webamp-uploaded-skins/skins/${hash}.wsz`; } -export function getPermalinkUrlFromHash(hash) { - // TODO: Make this a selector - return `/skin/${hash}/${hash}/`; -} - -export function getPermalinkUrlFromHashAndFilename(hash, fileName) { - // TODO: Make this a selector - return `/skin/${hash}/${fileName}/`; -} - -export function getAbsolutePermalinkUrlFromHash(hash) { - return window.location.origin + getPermalinkUrlFromHash(hash); -} - export function getWindowSize() { var w = window, d = document,