diff --git a/ui/js/actions/app.js b/ui/js/actions/app.js index 8ede77fee..d7ac439a4 100644 --- a/ui/js/actions/app.js +++ b/ui/js/actions/app.js @@ -6,7 +6,12 @@ import { selectUpgradeDownloadItem, selectUpgradeFilename, selectPageTitle, + selectCurrentPage, + selectCurrentParams, } from 'selectors/app' +import { + doSearch, +} from 'actions/search' const {remote, ipcRenderer, shell} = require('electron'); const path = require('path'); @@ -43,6 +48,16 @@ export function doChangePath(path) { path, } }) + + const state = getState() + const pageTitle = selectPageTitle(state) + window.document.title = pageTitle + + const currentPage = selectCurrentPage(state) + if (currentPage === 'search') { + const params = selectCurrentParams(state) + dispatch(doSearch(params.query)) + } } } @@ -59,7 +74,6 @@ export function doHistoryPush(params, title, relativeUrl) { const url = pathParts.join('/') title += " - LBRY" history.pushState(params, title, url) - window.document.title = title } } @@ -210,3 +224,16 @@ export function doDaemonReady() { type: types.DAEMON_READY } } + +export function doShowSnackBar(data) { + return { + type: types.SHOW_SNACKBAR, + data, + } +} + +export function doRemoveSnackBarSnack() { + return { + type: types.REMOVE_SNACKBAR_SNACK, + } +} diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index efb1851e2..0e7366dca 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -223,8 +223,11 @@ export function doPurchaseUri(uri) { // the blobs. Or perhaps there's another way to see if a file was already // purchased? - // we already fully downloaded the file - if (fileInfo && fileInfo.completed) { + // we already fully downloaded the file. If completed is true but + // writtenBytes is false then we downloaded it before but deleted it again, + // which means it needs to be reconstructed from the blobs by dispatching + // doLoadVideo. + if (fileInfo && fileInfo.completed && !!fileInfo.writtenBytes) { return Promise.resolve() } diff --git a/ui/js/actions/search.js b/ui/js/actions/search.js index 8dd85d822..149aa78ea 100644 --- a/ui/js/actions/search.js +++ b/ui/js/actions/search.js @@ -31,26 +31,24 @@ export function doSearch(query) { if(page != 'search') { dispatch(doNavigate('search', { query: query })) } else { - dispatch(doHistoryPush({ query }, "Search for " + query, '/search')) - } - - lighthouse.search(query).then(results => { - results.forEach(result => { - const uri = lbryuri.build({ - channelName: result.channel_name, - contentName: result.name, - claimId: result.channel_id || result.claim_id, + lighthouse.search(query).then(results => { + results.forEach(result => { + const uri = lbryuri.build({ + channelName: result.channel_name, + contentName: result.name, + claimId: result.channel_id || result.claim_id, + }) + dispatch(doResolveUri(uri)) }) - dispatch(doResolveUri(uri)) - }) - dispatch({ - type: types.SEARCH_COMPLETED, - data: { - query, - results, - } + dispatch({ + type: types.SEARCH_COMPLETED, + data: { + query, + results, + } + }) }) - }) + } } } diff --git a/ui/js/app.js b/ui/js/app.js index e69c40218..b354036ba 100644 --- a/ui/js/app.js +++ b/ui/js/app.js @@ -1,6 +1,6 @@ import store from 'store.js'; -const env = process.env.NODE_ENV || 'production'; +const env = ENV; const config = require(`./config/${env}`); const logs = []; const app = { diff --git a/ui/js/component/fileListSearch/view.jsx b/ui/js/component/fileListSearch/view.jsx index 4c9cfc625..986d8aa5c 100644 --- a/ui/js/component/fileListSearch/view.jsx +++ b/ui/js/component/fileListSearch/view.jsx @@ -58,11 +58,17 @@ class FileListSearch extends React.Component{ } = this.props return ( - isSearching ? - : - (results && results.length) ? +
+ {isSearching && !results && + } + + {isSearching && results && + } + + {(results && !!results.length) ? : - + } +
) } } diff --git a/ui/js/component/snack-bar.js b/ui/js/component/snack-bar.js deleted file mode 100644 index 46dc3bd98..000000000 --- a/ui/js/component/snack-bar.js +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; - -export class SnackBar extends React.Component { - constructor(props) { - super(props); - - this._displayTime = 5; // in seconds - this._hideTimeout = null; - - this.state = { - snacks: [] - }; - } - - handleSnackReceived(event) { - // if (this._hideTimeout) { - // clearTimeout(this._hideTimeout); - // } - - let snacks = this.state.snacks; - snacks.push(event.detail); - this.setState({ snacks: snacks}); - } - - componentWillMount() { - document.addEventListener('globalNotice', this.handleSnackReceived); - } - - componentWillUnmount() { - document.removeEventListener('globalNotice', this.handleSnackReceived); - } - - render() { - if (!this.state.snacks.length) { - this._hideTimeout = null; //should be unmounting anyway, but be safe? - return null; - } - - let snack = this.state.snacks[0]; - - if (this._hideTimeout === null) { - this._hideTimeout = setTimeout(() => { - this._hideTimeout = null; - let snacks = this.state.snacks; - snacks.shift(); - this.setState({ snacks: snacks }); - }, this._displayTime * 1000); - } - - return ( -
- {snack.message} - {snack.linkText && snack.linkTarget ? - {snack.linkText} : ''} -
- ); - } -} - -export default SnackBar; \ No newline at end of file diff --git a/ui/js/component/snackBar/index.js b/ui/js/component/snackBar/index.js new file mode 100644 index 000000000..0d624ce87 --- /dev/null +++ b/ui/js/component/snackBar/index.js @@ -0,0 +1,23 @@ +import React from 'react' +import { + connect, +} from 'react-redux' +import { + doNavigate, + doRemoveSnackBarSnack, +} from 'actions/app' +import { + selectSnackBarSnacks, +} from 'selectors/app' +import SnackBar from './view' + +const perform = (dispatch) => ({ + navigate: (path) => dispatch(doNavigate(path)), + removeSnack: () => dispatch(doRemoveSnackBarSnack()), +}) + +const select = (state) => ({ + snacks: selectSnackBarSnacks(state), +}) + +export default connect(select, perform)(SnackBar) diff --git a/ui/js/component/snackBar/view.jsx b/ui/js/component/snackBar/view.jsx new file mode 100644 index 000000000..98206ae2b --- /dev/null +++ b/ui/js/component/snackBar/view.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import Link from 'component/link' + +class SnackBar extends React.Component { + constructor(props) { + super(props); + + this._displayTime = 5; // in seconds + this._hideTimeout = null; + } + + render() { + const { + navigate, + snacks, + removeSnack, + } = this.props + + if (!snacks.length) { + this._hideTimeout = null; //should be unmounting anyway, but be safe? + return null; + } + + const snack = snacks[0]; + const { + message, + linkText, + linkTarget, + } = snack + + if (this._hideTimeout === null) { + this._hideTimeout = setTimeout(() => { + this._hideTimeout = null; + removeSnack() + }, this._displayTime * 1000); + } + + return ( +
+ {message} + {linkText && linkTarget && + navigate(linkTarget)} label={linkText} /> + } +
+ ); + } +} + +export default SnackBar; \ No newline at end of file diff --git a/ui/js/component/wunderbar/index.js b/ui/js/component/wunderbar/index.js index 9e597220b..8cde7704c 100644 --- a/ui/js/component/wunderbar/index.js +++ b/ui/js/component/wunderbar/index.js @@ -7,9 +7,6 @@ import { selectWunderBarAddress, selectWunderBarIcon } from 'selectors/search' -import { - doSearch, -} from 'actions/search' import { doNavigate, } from 'actions/app' @@ -21,7 +18,7 @@ const select = (state) => ({ }) const perform = (dispatch) => ({ - onSearch: (query) => dispatch(doSearch(query)), + onSearch: (query) => dispatch(doNavigate('/search', { query, })), onSubmit: (query) => dispatch(doNavigate('/show', { uri: lbryuri.normalize(query) } )) }) diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 3607583e3..1d1dab869 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -2,6 +2,8 @@ export const CHANGE_PATH = 'CHANGE_PATH' export const OPEN_MODAL = 'OPEN_MODAL' export const CLOSE_MODAL = 'CLOSE_MODAL' export const HISTORY_BACK = 'HISTORY_BACK' +export const SHOW_SNACKBAR = 'SHOW_SNACKBAR' +export const REMOVE_SNACKBAR_SNACK = 'REMOVE_SNACKBAR_SNACK' export const DAEMON_READY = 'DAEMON_READY' diff --git a/ui/js/main.js b/ui/js/main.js index 1fa350eff..9dd2a5347 100644 --- a/ui/js/main.js +++ b/ui/js/main.js @@ -5,9 +5,10 @@ import lbryio from './lbryio.js'; import lighthouse from './lighthouse.js'; import App from 'component/app/index.js'; import SplashScreen from 'component/splash.js'; -import SnackBar from 'component/snack-bar.js'; +import SnackBar from 'component/snackBar'; import {AuthOverlay} from 'component/auth.js'; import { Provider } from 'react-redux'; +import batchActions from 'util/batchActions' import store from 'store.js'; import { doChangePath, @@ -21,7 +22,9 @@ import { import { doFileList } from 'actions/file_info' -import parseQueryParams from 'util/query_params' +import { + toQueryString, +} from 'util/query_params' const {remote, ipcRenderer, shell} = require('electron'); const contextMenu = remote.require('./menu/context-menu'); @@ -36,15 +39,19 @@ window.addEventListener('contextmenu', (event) => { }); window.addEventListener('popstate', (event, param) => { - const queryString = document.location.search + const params = event.state const pathParts = document.location.pathname.split('/') const route = '/' + pathParts[pathParts.length - 1] + const queryString = toQueryString(params) - if (route.match(/html$/)) return + let action + if (route.match(/html$/)) { + action = doChangePath('/discover') + } else { + action = doChangePath(`${route}?${queryString}`) + } - console.log('title should be set here, but it is not in popstate? TODO') - - app.store.dispatch(doChangePath(`${route}${queryString}`)) + app.store.dispatch(action) }) ipcRenderer.on('open-uri-requested', (event, uri) => { @@ -73,12 +80,16 @@ const initialState = app.store.getState(); var init = function() { function onDaemonReady() { - app.store.dispatch(doDaemonReady()) window.sessionStorage.setItem('loaded', 'y'); //once we've made it here once per session, we don't need to show splash again - app.store.dispatch(doHistoryPush({}, "" + - "Discover", "/discover")) - app.store.dispatch(doFetchDaemonSettings()) - app.store.dispatch(doFileList()) + const actions = [] + + actions.push(doDaemonReady()) + actions.push(doChangePath('/discover')) + actions.push(doFetchDaemonSettings()) + actions.push(doFileList()) + + app.store.dispatch(batchActions(actions)) + ReactDOM.render(
{ lbryio.enabled ? : '' }
, canvas) } diff --git a/ui/js/page/discover/view.jsx b/ui/js/page/discover/view.jsx index 024f0d913..91cdfafda 100644 --- a/ui/js/page/discover/view.jsx +++ b/ui/js/page/discover/view.jsx @@ -34,24 +34,23 @@ class DiscoverPage extends React.Component{ fetchingFeaturedUris, } = this.props - let content - - if (fetchingFeaturedUris) { - content = - } else { - if (typeof featuredUris === "object") { - content = Object.keys(featuredUris).map(category => { - return featuredUris[category].length ? - : - ''; - }) - } else if (featuredUris !== undefined) { - content =
Failed to load landing content.
- } - } - return ( -
{content}
+
+ { + fetchingFeaturedUris && + + } + { + typeof featuredUris === "object" && + Object.keys(featuredUris).map(category => ( + featuredUris[category].length ? : '' + )) + } + { + typeof featuredUris !== undefined && +
Failed to load landing content.
+ } +
) } } diff --git a/ui/js/reducers/app.js b/ui/js/reducers/app.js index e3070ed40..f80baae0b 100644 --- a/ui/js/reducers/app.js +++ b/ui/js/reducers/app.js @@ -93,6 +93,44 @@ reducers[types.DAEMON_READY] = function(state, action) { }) } +reducers[types.SHOW_SNACKBAR] = function(state, action) { + const { + message, + linkText, + linkTarget, + isError, + } = action.data + const snackBar = Object.assign({}, state.snackBar) + const snacks = Object.assign([], snackBar.snacks) + snacks.push({ + message, + linkText, + linkTarget, + isError, + }) + const newSnackBar = Object.assign({}, snackBar, { + snacks, + }) + + return Object.assign({}, state, { + snackBar: newSnackBar, + }) +} + +reducers[types.REMOVE_SNACKBAR_SNACK] = function(state, action) { + const snackBar = Object.assign({}, state.snackBar) + const snacks = Object.assign([], snackBar.snacks) + snacks.shift() + + const newSnackBar = Object.assign({}, snackBar, { + snacks, + }) + + return Object.assign({}, state, { + snackBar: newSnackBar, + }) +} + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/rewards.js b/ui/js/rewards.js index 9a2235130..eae27eb88 100644 --- a/ui/js/rewards.js +++ b/ui/js/rewards.js @@ -1,5 +1,8 @@ -import lbry from './lbry.js'; -import lbryio from './lbryio.js'; +import lbry from 'lbry'; +import lbryio from 'lbryio'; +import { + doShowSnackBar, +} from 'actions/app' function rewardMessage(type, amount) { return { @@ -40,14 +43,13 @@ rewards.claimReward = function (type) { }; // Display global notice - document.dispatchEvent(new CustomEvent('globalNotice', { - detail: { - message: message, - linkText: "Show All", - linkTarget: "/rewards", - isError: false, - }, - })); + const action = doShowSnackBar({ + message, + linkText: "Show All", + linkTarget: "/rewards", + isError: false, + }) + window.app.store.dispatch(action) // Add more events here to display other places diff --git a/ui/js/selectors/app.js b/ui/js/selectors/app.js index f0af3b5a1..2563eb81b 100644 --- a/ui/js/selectors/app.js +++ b/ui/js/selectors/app.js @@ -1,5 +1,7 @@ import {createSelector} from 'reselect' -import parseQueryParams from 'util/query_params' +import { + parseQueryParams, +} from 'util/query_params' import lbryuri from 'lbryuri' export const _selectState = state => state.app || {} @@ -37,7 +39,7 @@ export const selectPageTitle = createSelector( (page, params) => { switch (page) { case 'search': - return 'Search' + return params.query ? `Search results for ${params.query}` : 'Search' case 'settings': return 'Settings' case 'help': @@ -191,4 +193,14 @@ export const selectDaemonReady = createSelector( export const selectObscureNsfw = createSelector( _selectState, (state) => !!state.obscureNsfw +) + +export const selectSnackBar = createSelector( + _selectState, + (state) => state.snackBar || {} +) + +export const selectSnackBarSnacks = createSelector( + selectSnackBar, + (snackBar) => snackBar.snacks || [] ) \ No newline at end of file diff --git a/ui/js/store.js b/ui/js/store.js index 3c582ce5e..5ed542db5 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -1,6 +1,6 @@ const redux = require('redux'); const thunk = require("redux-thunk").default; -const env = process.env.NODE_ENV || 'production'; +const env = ENV; import { createLogger diff --git a/ui/js/util/query_params.js b/ui/js/util/query_params.js index 45abc635c..9f89900d2 100644 --- a/ui/js/util/query_params.js +++ b/ui/js/util/query_params.js @@ -1,4 +1,4 @@ -function parseQueryParams(queryString) { +export function parseQueryParams(queryString) { if (queryString === '') return {}; const parts = queryString .split('?') @@ -13,4 +13,14 @@ function parseQueryParams(queryString) { return params; } -export default parseQueryParams +export function toQueryString(params) { + if (!params) return "" + + const parts = [] + for (const key in params) { + if (params.hasOwnProperty(key) && params[key]) { + parts.push(key + "=" + params[key]) + } + } + return parts.join("&") +} diff --git a/ui/webpack.config.js b/ui/webpack.config.js index 9afb1d82f..fe0a5277b 100644 --- a/ui/webpack.config.js +++ b/ui/webpack.config.js @@ -1,4 +1,5 @@ const path = require('path'); +const webpack = require('webpack') const appPath = path.resolve(__dirname, 'js'); const PATHS = { @@ -18,6 +19,11 @@ module.exports = { root: appPath, extensions: ['', '.js', '.jsx', '.css'], }, + plugins: [ + new webpack.DefinePlugin({ + ENV: JSON.stringify("development"), + }), + ], module: { preLoaders: [ { diff --git a/ui/webpack.dev.config.js b/ui/webpack.dev.config.js index cc5372370..15ec5652d 100644 --- a/ui/webpack.dev.config.js +++ b/ui/webpack.dev.config.js @@ -1,4 +1,5 @@ const path = require('path'); +const webpack = require('webpack') const WebpackNotifierPlugin = require('webpack-notifier') const appPath = path.resolve(__dirname, 'js'); @@ -25,6 +26,9 @@ module.exports = { }, plugins: [ new WebpackNotifierPlugin(), + new webpack.DefinePlugin({ + ENV: JSON.stringify("development"), + }), ], module: { preLoaders: [