Merge 'relase' into 'master' (#2289)

* v0.29.0-rc.1

* bump daemon

* v0.29.0-rc.2

* bump lbry/components

* v0.29.0-rc.3

* fix: remember if search options were open

* fix: scrollable list on subscriptions

* fix: icon alignment on file page

* fix: button color in publish form

* fix: select option color for windows/linux

* fix: don't break app when navigating to abaondoned claims

* fix: thumbnail size on related content

* maint: rc4

with UI fixes

* fix: ... buttons ... again

* fix: file percentage on pending downloads

Changed to DidMount (did update seemed to trigger the file list too many times) - this will show the percentage if it's not completed and currently downloading. Typical cases are if a person refreshed or navigated away/back. This way if a download is stuck, a user will see the percentage and can try to delete/redownload (we should add a start/stop feature later on).

* fix: name input text wrapping

* change: use select drop down for search results

* add search analytics

* v0.29.0-rc.5

* v0.29.0-rc.6

* fix: name input alignment

* fix: metadata

* bump sdk

* v0.29.0-rc.7

* fix: line-height

* v0.29.0-rc.8

* v0.29.0
This commit is contained in:
Sean Yesmunt 2019-02-21 17:45:17 -05:00 committed by GitHub
parent f0389221f2
commit 7577586ea9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 272 additions and 140 deletions

View file

@ -1,6 +1,6 @@
{
"name": "LBRY",
"version": "0.29.0-rc.0",
"version": "0.29.0",
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
"keywords": [
"lbry"
@ -34,7 +34,7 @@
"postinstall": "electron-builder install-app-deps && node build/downloadDaemon.js"
},
"dependencies": {
"@lbry/components": "^2.2.1",
"@lbry/components": "^2.2.4",
"@types/three": "^0.93.1",
"bluebird": "^3.5.1",
"breakdance": "^3.0.1",
@ -134,7 +134,7 @@
"yarn": "^1.3"
},
"lbrySettings": {
"lbrynetDaemonVersion": "0.32.0",
"lbrynetDaemonVersion": "0.32.2",
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
"lbrynetDaemonDir": "static/daemon",
"lbrynetDaemonFileName": "lbrynet"

View file

@ -80,6 +80,10 @@ const analytics: Analytics = {
Lbryio.call('event', 'publish');
}
},
apiSearchFeedback: (query, vote) => {
// We don't need to worry about analytics enabled here because users manually click on the button to provide feedback
Lbryio.call('feedback', 'search', { query, vote });
},
};
export default analytics;

View file

@ -57,21 +57,21 @@ class CategoryList extends PureComponent<Props, State> {
fetchChannel(categoryLink);
}
if (!urisInList) {
return;
}
const scrollWrapper = this.scrollWrapper.current;
if (scrollWrapper) {
scrollWrapper.addEventListener('scroll', throttle(this.handleArrowButtonsOnScroll, 500));
if (lazyLoad) {
const scrollWrapper = this.scrollWrapper.current;
if (scrollWrapper) {
scrollWrapper.addEventListener('scroll', throttle(this.handleArrowButtonsOnScroll, 500));
if (!urisInList) {
return;
}
if (urisInList && window.innerHeight > scrollWrapper.offsetTop) {
if (lazyLoad) {
if (window.innerHeight > scrollWrapper.offsetTop) {
resolveUris(urisInList);
}
} else {
resolveUris(urisInList);
}
} else {
resolveUris(urisInList);
}
}

View file

@ -168,11 +168,9 @@ export class FormField extends React.PureComponent<Props> {
input = (
<React.Fragment>
<fieldset-section>
{(label || errorMessage) && (
<label htmlFor={name}>
{errorMessage ? <span className="error-text">{errorMessage}</span> : label}
</label>
)}
<label htmlFor={name}>
{errorMessage ? <span className="error-text">{errorMessage}</span> : label}
</label>
{prefix && (
<label className="form-field--inline-prefix" htmlFor={name}>
{prefix}

View file

@ -7,7 +7,7 @@ type Props = {
title: string,
subtitle: string | React.Node,
type: string,
className: ?string,
className?: string,
};
const yrblTypes = {
@ -30,7 +30,7 @@ export default class extends React.PureComponent<Props> {
<img alt="Friendly gerbil" className="yrbl" src={Native.imagePath(image)} />
<div className="card__content">
<h2 className="card__title">{title}</h2>
<p className="card__subtitle">{subtitle}</p>
<div className="card__subtitle">{subtitle}</div>
</div>
</div>
);

View file

@ -16,6 +16,7 @@ type Props = {
outpoint: number,
download_path: string,
completed: boolean,
status: string,
},
loading: boolean,
costInfo: ?{},
@ -26,15 +27,16 @@ type Props = {
};
class FileDownloadLink extends React.PureComponent<Props> {
componentWillUpdate() {
const { downloading, fileInfo, uri, restartDownload } = this.props;
componentDidMount() {
const { fileInfo, uri, restartDownload } = this.props;
if (
!downloading &&
fileInfo &&
!fileInfo.completed &&
fileInfo.status === 'running' &&
fileInfo.written_bytes !== false &&
fileInfo.written_bytes < fileInfo.total_bytes
) {
// This calls file list to show the percentage
restartDownload(uri, fileInfo.outpoint);
}
}

View file

@ -38,6 +38,68 @@ class MediaPlayer extends React.PureComponent {
}
componentDidMount() {
this.playMedia();
// Temp hack to force the video to play if the metadataloaded event was never fired
// Will be removed with the new video player
setTimeout(() => {
const { hasMetadata } = this.state;
if (!hasMetadata) {
this.refreshMetadata();
this.playMedia();
}
}, 5000);
}
componentWillReceiveProps(next) {
const el = this.media.children[0];
if (!this.props.paused && next.paused && !el.paused) el.pause();
}
componentDidUpdate() {
const { contentType, downloadCompleted } = this.props;
const { startedPlaying, fileSource } = this.state;
if (this.playableType() && !startedPlaying && downloadCompleted) {
const container = this.media.children[0];
if (MediaPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
this.renderAudio(this.media, true);
} else {
player.append(
this.file(),
container,
{ autoplay: true, controls: true },
renderMediaCallback.bind(this)
);
}
} else if (this.fileType() && !fileSource && downloadCompleted) {
this.renderFile();
}
}
componentWillUnmount() {
document.removeEventListener('keydown', this.togglePlayListener);
const mediaElement = this.media.children[0];
if (mediaElement) {
mediaElement.removeEventListener('click', this.togglePlayListener);
}
}
toggleFullScreen(event) {
const mediaElement = this.media.children[0];
if (mediaElement) {
if (document.webkitIsFullScreen) {
document.webkitExitFullscreen();
} else {
mediaElement.webkitRequestFullScreen();
}
}
}
playMedia() {
const { hasMetadata } = this.state;
const container = this.media;
const {
downloadCompleted,
@ -51,15 +113,6 @@ class MediaPlayer extends React.PureComponent {
savePosition,
} = this.props;
const loadedMetadata = () => {
this.setState({ hasMetadata: true, startedPlaying: true });
if (onStartCb) {
onStartCb();
}
this.media.children[0].play();
};
const renderMediaCallback = error => {
if (error) this.setState({ unplayable: true });
};
@ -91,16 +144,15 @@ class MediaPlayer extends React.PureComponent {
}
document.addEventListener('keydown', this.togglePlayListener);
const mediaElement = this.media.children[0];
const mediaElement = container.children[0];
if (mediaElement) {
if (position) {
mediaElement.currentTime = position;
}
mediaElement.addEventListener('loadedmetadata', () => this.refreshMetadata());
mediaElement.addEventListener('timeupdate', () => savePosition(mediaElement.currentTime));
mediaElement.addEventListener('click', this.togglePlayListener);
mediaElement.addEventListener('loadedmetadata', loadedMetadata.bind(this), {
once: true,
});
mediaElement.addEventListener('ended', () => {
if (onFinishCb) {
onFinishCb();
@ -116,48 +168,18 @@ class MediaPlayer extends React.PureComponent {
}
}
componentWillReceiveProps(next) {
const el = this.media.children[0];
if (!this.props.paused && next.paused && !el.paused) el.pause();
refreshMetadata() {
const { onStartCb } = this.props;
this.setState({ hasMetadata: true, startedPlaying: true });
if (onStartCb) {
onStartCb();
}
this.media.children[0].play();
}
componentDidUpdate() {
const { contentType, downloadCompleted } = this.props;
const { startedPlaying, fileSource } = this.state;
if (this.playableType() && !startedPlaying && downloadCompleted) {
const container = this.media.children[0];
if (MediaPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
this.renderAudio(this.media, true);
} else {
player.render(this.file(), container, {
autoplay: true,
controls: true,
});
}
} else if (this.fileType() && !fileSource && downloadCompleted) {
this.renderFile();
}
}
componentWillUnmount() {
document.removeEventListener('keydown', this.togglePlayListener);
const mediaElement = this.media.children[0];
if (mediaElement) {
mediaElement.removeEventListener('click', this.togglePlayListener);
}
}
toggleFullScreen(event) {
const mediaElement = this.media.children[0];
if (mediaElement) {
if (document.webkitIsFullScreen) {
document.webkitExitFullscreen();
} else {
mediaElement.webkitRequestFullScreen();
}
}
setReady() {
this.setState({ ready: true });
}
togglePlay(event) {

View file

@ -1,13 +1,42 @@
import { connect } from 'react-redux';
import { selectSearchOptions, doUpdateSearchOptions } from 'lbry-redux';
import {
selectSearchOptions,
doUpdateSearchOptions,
makeSelectQueryWithOptions,
doToast,
} from 'lbry-redux';
import { doToggleSearchExpanded } from 'redux/actions/app';
import { selectSearchOptionsExpanded } from 'redux/selectors/app';
import analytics from 'analytics';
import SearchOptions from './view';
const select = state => ({
options: selectSearchOptions(state),
expanded: selectSearchOptionsExpanded(state),
query: makeSelectQueryWithOptions()(state),
});
const perform = dispatch => ({
setSearchOption: (option, value) => dispatch(doUpdateSearchOptions({ [option]: value })),
toggleSearchExpanded: () => dispatch(doToggleSearchExpanded()),
onFeedbackPositive: query => {
analytics.apiSearchFeedback(query, 1);
dispatch(
doToast({
message: __('Thanks for the feedback! You help make the app better for everyone.'),
})
);
},
onFeedbackNegative: query => {
analytics.apiSearchFeedback(query, 0);
dispatch(
doToast({
message: __(
'Thanks for the feedback. Mark has been notified and is currently walking over to his computer to work on this.'
),
})
);
},
});
export default connect(

View file

@ -1,6 +1,6 @@
// @flow
import * as ICONS from 'constants/icons';
import React, { useState } from 'react';
import React from 'react';
import { SEARCH_OPTIONS } from 'lbry-redux';
import { Form, FormField } from 'component/common/form';
import posed from 'react-pose';
@ -14,28 +14,50 @@ const ExpandableOptions = posed.div({
type Props = {
setSearchOption: (string, boolean | string | number) => void,
options: {},
expanded: boolean,
toggleSearchExpanded: () => void,
query: string,
onFeedbackPositive: string => void,
onFeedbackNegative: string => void,
};
const SearchOptions = (props: Props) => {
const { options, setSearchOption } = props;
const [expanded, setExpanded] = useState(false);
const {
options,
setSearchOption,
expanded,
toggleSearchExpanded,
query,
onFeedbackPositive,
onFeedbackNegative,
} = props;
const resultCount = options[SEARCH_OPTIONS.RESULT_COUNT];
return (
<div className="card card--section search__options-wrapper">
<div className="card--space-between">
<Button
label={__('SEARCH OPTIONS')}
icon={ICONS.OPTIONS}
onClick={() => setExpanded(!expanded)}
button="alt"
label={__('ADVANCED SEARCH')}
iconRight={expanded ? ICONS.UP : ICONS.DOWN}
onClick={toggleSearchExpanded}
/>
{/*
Will be added back when api is ready
<div className="media__action-group">
<div className="media__action-group">
<span>{__('Find what you were looking for?')}</span>
<Button description={__('Yes')} icon={ICONS.YES} />
<Button description={__('No')} icon={ICONS.NO} />
</div> */}
<Button
button="alt"
description={__('Yes')}
onClick={() => onFeedbackPositive(query)}
icon={ICONS.YES}
/>
<Button
button="alt"
description={__('No')}
onClick={() => onFeedbackNegative(query)}
icon={ICONS.NO}
/>
</div>
</div>
<ExpandableOptions pose={expanded ? 'show' : 'hide'}>
{expanded && (
@ -77,7 +99,7 @@ const SearchOptions = (props: Props) => {
},
{
option: SEARCH_OPTIONS.MEDIA_AUDIO,
label: __('Sounds'),
label: __('Audio'),
},
{
option: SEARCH_OPTIONS.MEDIA_IMAGE,
@ -108,13 +130,18 @@ const SearchOptions = (props: Props) => {
<fieldset>
<legend className="search__legend--3">{__('Other Options')}</legend>
<FormField
type="number"
type="select"
name="result-count"
value={resultCount}
onChange={e => setSearchOption(SEARCH_OPTIONS.RESULT_COUNT, e.target.value)}
blockWrap={false}
label={__('Returned Results')}
/>
>
<option value={10}>10</option>
<option value={30}>30</option>
<option value={50}>50</option>
<option value={100}>100</option>
</FormField>
</fieldset>
</Form>
)}

View file

@ -16,6 +16,7 @@ export const VOLUME_CHANGED = 'VOLUME_CHANGED';
export const ADD_COMMENT = 'ADD_COMMENT';
export const SHOW_MODAL = 'SHOW_MODAL';
export const HIDE_MODAL = 'HIDE_MODAL';
export const TOGGLE_SEARCH_EXPANDED = 'TOGGLE_SEARCH_EXPANDED';
export const ENNNHHHAAANNNCEEE = 'ENNNHHHAAANNNCEEE';
// Navigation

View file

@ -44,3 +44,5 @@ export const FILE = 'File';
export const OPTIONS = 'Sliders';
export const YES = 'ThumbsUp';
export const NO = 'ThumbsDown';
export const UP = 'ChevronUp';
export const DOWN = 'ChevronDown';

View file

@ -129,7 +129,7 @@ class RewardsPage extends PureComponent<Props> {
<Fragment>
<section className="card card--section">
<h2 className="card__title">{__('No Rewards Available')}</h2>
<p>
<p className="card__content">
{claimed && claimed.length
? __(
"You have claimed all available rewards! We're regularly adding more so be sure to check back later."

View file

@ -31,7 +31,7 @@ class ShowPage extends React.PureComponent<Props> {
if (
!isResolvingUri &&
uri &&
(claim === undefined || (claim.name[0] === '@' && totalPages === undefined))
(claim === undefined || (claim && claim.name[0] === '@' && totalPages === undefined))
) {
resolveUri(uri);
}

View file

@ -380,3 +380,9 @@ export function doToggleEnhancedLayout() {
type: ACTIONS.ENNNHHHAAANNNCEEE,
};
}
export function doToggleSearchExpanded() {
return {
type: ACTIONS.TOGGLE_SEARCH_EXPANDED,
};
}

View file

@ -35,6 +35,7 @@ export type AppState = {
isUpgradeSkipped: ?boolean,
hasClickedComment: boolean,
enhancedLayout: boolean,
searchOptionsExpanded: boolean,
};
const defaultState: AppState = {
@ -59,6 +60,7 @@ const defaultState: AppState = {
isUpgradeAvailable: undefined,
isUpgradeSkipped: undefined,
enhancedLayout: false,
searchOptionsExpanded: false,
};
reducers[ACTIONS.DAEMON_READY] = state =>
@ -220,6 +222,11 @@ reducers[ACTIONS.ENNNHHHAAANNNCEEE] = state =>
enhancedLayout: !state.enhancedLayout,
});
reducers[ACTIONS.TOGGLE_SEARCH_EXPANDED] = state =>
Object.assign({}, state, {
searchOptionsExpanded: !state.searchOptionsExpanded,
});
export default function reducer(state: AppState = defaultState, action: any) {
const handler = reducers[action.type];
if (handler) return handler(state, action);

View file

@ -248,3 +248,8 @@ export const selectModal = createSelector(selectState, state => {
});
export const selectEnhancedLayout = createSelector(selectState, state => state.enhancedLayout);
export const selectSearchOptionsExpanded = createSelector(
selectState,
state => state.searchOptionsExpanded
);

View file

@ -17,11 +17,12 @@
stroke-opacity: 1;
width: 1.4rem;
height: 1.4rem;
top: 0;
top: 0.1em;
}
}
svg + .button__label {
svg + .button__label,
.button__label + svg {
margin-left: var(--spacing-vertical-small);
}
@ -52,35 +53,45 @@
}
}
.button--inverse {
// This is a hack and the extra styles are just so this is more specific than the @lbry/components styling
// Will make a PR there, but just doing it now for the release - Sean
[type='button'].button--inverse {
font-size: 1rem;
transition: background-color 0.2s;
border-radius: 0;
html[data-mode='dark'] & {
border-color: rgba($lbry-white, 0.1);
background-color: rgba($lbry-black, 0.3);
}
&:not(:hover) {
border-color: rgba($lbry-white, 0.1);
background-color: rgba($lbry-black, 0.3);
}
&:not(:hover) {
background-color: $lbry-white;
}
&:hover {
background-color: $lbry-gray-1;
color: $lbry-black;
html[data-mode='dark'] & {
&:hover {
border-color: rgba($lbry-white, 0.1);
background-color: rgba($lbry-white, 0.1);
color: $lbry-white;
}
}
html[data-mode='light'] & {
&:not(:hover) {
background-color: $lbry-white;
color: $lbry-black;
}
&:hover {
background-color: $lbry-gray-1;
color: $lbry-black;
}
}
.button__content {
svg {
color: $lbry-gray-4;
}
}
}
.button--link:not(:disabled) {
html[data-mode='dark'] & {
&:not(:hover) {

View file

@ -192,6 +192,10 @@
.card__title {
font-size: 2rem;
font-weight: 600;
.button {
font-size: 1.2rem;
}
}
.card__title--flex {

View file

@ -9,7 +9,7 @@ input[type='number'] {
input[type='text'],
input[type='number'],
select {
padding-bottom: 0.2em;
padding-bottom: 0.1em;
[data-mode='dark'] & {
&::placeholder {
@ -99,6 +99,10 @@ fieldset-group {
&.fieldset-group--disabled-prefix {
align-items: flex-end;
label {
min-height: 18px;
}
fieldset-section:first-child .form-field__prefix,
fieldset-section:last-child input {
border-color: $lbry-black;
@ -110,6 +114,7 @@ fieldset-group {
fieldset-section:first-child {
.form-field__prefix {
white-space: nowrap;
padding: 0.15em var(--spacing-s);
padding-right: 0;
border: 1px solid;
@ -142,14 +147,22 @@ fieldset-group {
// form buttons are black by default
form {
// [data-mode='dark'] & {
.button--primary:not(:hover) {
background-color: $lbry-teal-5;
border-color: $lbry-teal-5;
[type='button'],
[type='submit'] {
&.button--primary {
&:not(:hover) {
background-color: $lbry-teal-5;
border-color: $lbry-teal-5;
}
&:hover {
background-color: $lbry-teal-3;
border-color: $lbry-teal-3;
&:hover {
background-color: $lbry-teal-3;
border-color: $lbry-teal-3;
}
}
&.button--inverse {
@extend .button--inverse;
}
}
}
@ -175,6 +188,10 @@ fieldset-section {
[data-mode='dark'] & {
background-color: transparent;
option {
background-color: $lbry-gray-5;
}
}
}
@ -215,17 +232,6 @@ label + .react-toggle,
margin-left: var(--spacing-vertical-small);
}
.form-field--inline-prefix {
position: absolute;
top: 9.5em;
left: 2.75em;
width: auto;
& + input {
padding-left: 4em;
}
}
.form-field__help {
@extend .help;
margin-top: var(--spacing-vertical-medium);

View file

@ -144,8 +144,8 @@
.media__actions {
display: flex;
flex-wrap: wrap;
padding-top: var(--spacing-vertical-large);
padding-bottom: var(--spacing-vertical-large);
margin-top: var(--spacing-vertical-small);
margin-bottom: var(--spacing-vertical-small);
}
.media__actions--between {
@ -160,6 +160,8 @@
.media__action-group--large {
display: flex;
margin-top: var(--spacing-vertical-small);
margin-bottom: var(--spacing-vertical-small);
> * {
font-size: 1.15rem;
@ -168,6 +170,10 @@
margin-right: var(--spacing-vertical-large);
}
}
&:not(:last-child) {
margin-right: var(--spacing-vertical-large);
}
}
// M E D I A

View file

@ -89,6 +89,7 @@ code {
display: -webkit-box;
overflow: hidden;
-webkit-box-orient: vertical;
word-break: break-word;
}
.busy-indicator__loader {

View file

@ -99,7 +99,7 @@ const fileInfoFilter = createFilter('fileInfo', [
'fileListDownloadedSort',
'fileListSubscriptionSort',
]);
const appFilter = createFilter('app', ['hasClickedComment']);
const appFilter = createFilter('app', ['hasClickedComment', 'searchOptionsExpanded']);
// We only need to persist the receiveAddress for the wallet
const walletFilter = createFilter('wallet', ['receiveAddress']);
const searchFilter = createFilter('search', ['options']);

View file

@ -111,10 +111,10 @@
version "0.7.1"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f"
"@lbry/components@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@lbry/components/-/components-2.2.1.tgz#3fc67e807796e9dc8cd1d5cd86a719849c4710d5"
integrity sha512-k8bdSjr0Hq3+ZlvAKRuPea4LTEvXx4zF7+IOb1RmFWfMIk8jBNZi02LmvLoj4VuCa3mKoVfECeaYyXg6vwmY3w==
"@lbry/components@^2.2.4":
version "2.2.4"
resolved "https://registry.yarnpkg.com/@lbry/components/-/components-2.2.4.tgz#ee2c91788de447ed33185daa7a747ba684e5143f"
integrity sha512-SF0g4JROwYsvJc9fEFmAYAj9cITJ9L2MTkWCs9vfPhDS4iXE6ZW5VNtcxI5JOVAvsZMRHq5oX9EtjxHs1pIIdg==
"@mapbox/hast-util-table-cell-style@^0.1.3":
version "0.1.3"
@ -5668,19 +5668,20 @@ lbry-redux@lbryio/lbry-redux#3ab065b11a52d3e2e6a50a25459f9ff0aac03b13:
reselect "^3.0.0"
uuid "^3.3.2"
lbry-redux@lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404:
lbry-redux@lbryio/lbry-redux#76d8bbef9640bf8ea5c4f45550e55b77d3944ee3:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/84b7d396934d57a37802aadbef71db91230a9404"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/76d8bbef9640bf8ea5c4f45550e55b77d3944ee3"
dependencies:
proxy-polyfill "0.1.6"
reselect "^3.0.0"
uuid "^3.3.2"
lbryinc@lbryio/lbryinc#60d80401891743f991c040bafa8e51da7e939777:
lbryinc@lbryio/lbryinc#2334ad53e82c22d6291899785d5292347008f2a9:
version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/60d80401891743f991c040bafa8e51da7e939777"
resolved "https://codeload.github.com/lbryio/lbryinc/tar.gz/2334ad53e82c22d6291899785d5292347008f2a9"
dependencies:
lbry-redux lbryio/lbry-redux#84b7d396934d57a37802aadbef71db91230a9404
bluebird "^3.5.1"
lbry-redux lbryio/lbry-redux#76d8bbef9640bf8ea5c4f45550e55b77d3944ee3
reselect "^3.0.0"
lcid@^1.0.0: