External-internal links (UX/UI) #1660

Merged
btzr-io merged 2 commits from links into master 2018-06-23 06:38:20 +02:00
19 changed files with 113 additions and 55 deletions

View file

@ -56,6 +56,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
* URI and outpoint not being passed properly to API ([#1494](https://github.com/lbryio/lbry-app/issues/1494)) * URI and outpoint not being passed properly to API ([#1494](https://github.com/lbryio/lbry-app/issues/1494))
* Incorrect markdown preview on url with parentheses ([#1570](https://github.com/lbryio/lbry-app/issues/1570)) * Incorrect markdown preview on url with parentheses ([#1570](https://github.com/lbryio/lbry-app/issues/1570))
* Fix Linux upgrade path and add manual installation note ([#1606](https://github.com/lbryio/lbry-app/issues/1606)) * Fix Linux upgrade path and add manual installation note ([#1606](https://github.com/lbryio/lbry-app/issues/1606))
* Fix can type in unfocused fields while publishing without selecting file ([#1456](https://github.com/lbryio/lbry-app/issues/1456))
* Fix navigation button resulting incorrect page designation ([#1502](https://github.com/lbryio/lbry-app/issues/1502))
* Fix shouldn't allow to open multiple export and choose file dialogs ([#1175](https://github.com/lbryio/lbry-app/issues/1175))

View file

@ -2,7 +2,6 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import Button from 'component/button'; import Button from 'component/button';
import parseData from 'util/parseData'; import parseData from 'util/parseData';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
@ -56,16 +55,20 @@ class FileExporter extends React.PureComponent<Props> {
], ],
}; };
remote.dialog.showSaveDialog(options, filename => { remote.dialog.showSaveDialog(
// User hit cancel so do nothing: remote.getCurrentWindow(),
if (!filename) return; options,
// Get extension and remove initial dot filename => {
const format = path.extname(filename).replace(/\./g, ''); // User hit cancel so do nothing:
// Parse data to string with the chosen format if (!filename) return;
const parsed = parseData(data, format, filters); // Get extension and remove initial dot
// Write file const format = path.extname(filename).replace(/\./g, '');
parsed && this.handleFileCreation(filename, parsed); // Parse data to string with the chosen format
}); const parsed = parseData(data, format, filters);
// Write file
parsed && this.handleFileCreation(filename, parsed);
}
);
} }
render() { render() {

View file

@ -25,6 +25,7 @@ class FileSelector extends React.PureComponent<Props> {
handleButtonClick() { handleButtonClick() {
remote.dialog.showOpenDialog( remote.dialog.showOpenDialog(
remote.getCurrentWindow(),
{ {
properties: properties:
this.props.type === 'file' ? ['openFile'] : ['openDirectory', 'createDirectory'], this.props.type === 'file' ? ['openFile'] : ['openDirectory', 'createDirectory'],

View file

@ -82,6 +82,7 @@ export class FormField extends React.PureComponent<Props> {
<div <div
className={classnames('form-field', { className={classnames('form-field', {
'form-field--stretch': stretch || type === 'markdown', 'form-field--stretch': stretch || type === 'markdown',
'form-field--disabled': inputProps.disabled,
})} })}
> >
{(label || errorMessage) && ( {(label || errorMessage) && (

View file

@ -1,6 +1,7 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import { MODALS, isURIValid } from 'lbry-redux'; import { MODALS, isURIValid } from 'lbry-redux';
import * as icons from 'constants/icons';
import Button from 'component/button'; import Button from 'component/button';
type Props = { type Props = {
@ -32,12 +33,12 @@ class ExternalLink extends React.PureComponent<Props> {
element = ( element = (
<Button <Button
button="link" button="link"
iconRight={icons.EXTERNAL_LINK}
title={title || href} title={title || href}
label={children}
className="btn--external-link" className="btn--external-link"
onClick={() => openModal({ id: MODALS.CONFIRM_EXTERNAL_LINK }, { uri: href })} onClick={() => openModal({ id: MODALS.CONFIRM_EXTERNAL_LINK }, { uri: href })}
> />
{children}
</Button>
); );
} }
@ -47,10 +48,9 @@ class ExternalLink extends React.PureComponent<Props> {
<Button <Button
button="link" button="link"
title={title || href} title={title || href}
label={children}
onClick={() => navigate('/show', { uri: href })} onClick={() => navigate('/show', { uri: href })}
> />
{children}
</Button>
); );
} }

View file

@ -11,6 +11,7 @@ type Props = {
isSearching: boolean, isSearching: boolean,
uris: ?Array<string>, uris: ?Array<string>,
downloadUris: ?Array<string>, downloadUris: ?Array<string>,
resultCount: number,
}; };
class FileListSearch extends React.PureComponent<Props> { class FileListSearch extends React.PureComponent<Props> {

View file

@ -7,6 +7,8 @@ import {
doBlurSearchInput, doBlurSearchInput,
doSearch, doSearch,
} from 'lbry-redux'; } from 'lbry-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import * as settings from 'constants/settings';
import { doNavigate } from 'redux/actions/navigation'; import { doNavigate } from 'redux/actions/navigation';
import Wunderbar from './view'; import Wunderbar from './view';
@ -21,12 +23,13 @@ const select = state => {
return { return {
...searchState, ...searchState,
wunderbarValue, wunderbarValue,
resultCount: makeSelectClientSetting(settings.RESULT_COUNT)(state),
}; };
}; };
const perform = dispatch => ({ const perform = dispatch => ({
onSearch: query => { onSearch: (query, size) => {
dispatch(doSearch(query, 30)); // Hard coding this for now until https://github.com/lbryio/lbry-app/pull/1639 is merged dispatch(doSearch(query, size));
dispatch(doNavigate(`/search`, { query })); dispatch(doNavigate(`/search`, { query }));
}, },
onSubmit: (uri, extraParams) => dispatch(doNavigate('/show', { uri, ...extraParams })), onSubmit: (uri, extraParams) => dispatch(doNavigate('/show', { uri, ...extraParams })),

View file

@ -15,6 +15,7 @@ type Props = {
suggestions: Array<string>, suggestions: Array<string>,
doFocus: () => void, doFocus: () => void,
doBlur: () => void, doBlur: () => void,
resultCount: number,
}; };
class WunderBar extends React.PureComponent<Props> { class WunderBar extends React.PureComponent<Props> {
@ -45,7 +46,7 @@ class WunderBar extends React.PureComponent<Props> {
} }
handleSubmit(value: string, suggestion?: { value: string, type: string }) { handleSubmit(value: string, suggestion?: { value: string, type: string }) {
const { onSubmit, onSearch } = this.props; const { onSubmit, onSearch, resultCount } = this.props;
const query = value.trim(); const query = value.trim();
const getParams = () => { const getParams = () => {
const parts = query.split('?'); const parts = query.split('?');
@ -61,7 +62,7 @@ class WunderBar extends React.PureComponent<Props> {
// User selected a suggestion // User selected a suggestion
if (suggestion) { if (suggestion) {
if (suggestion.type === 'search') { if (suggestion.type === 'search') {
onSearch(query); onSearch(query, resultCount);
} else { } else {
const params = getParams(); const params = getParams();
const uri = normalizeURI(query); const uri = normalizeURI(query);
@ -78,7 +79,7 @@ class WunderBar extends React.PureComponent<Props> {
const params = getParams(); const params = getParams();
onSubmit(uri, params); onSubmit(uri, params);
} catch (e) { } catch (e) {
onSearch(query); onSearch(query, resultCount);
} }
} }

View file

@ -26,3 +26,4 @@ export const HEART = 'Heart';
export const UNLOCK = 'Unlock'; export const UNLOCK = 'Unlock';
export const CHECK_SIMPLE = 'Check'; export const CHECK_SIMPLE = 'Check';
export const GLOBE = 'Globe'; export const GLOBE = 'Globe';
export const EXTERNAL_LINK = 'ExternalLink';

View file

@ -13,3 +13,4 @@ export const THEME = 'theme';
export const THEMES = 'themes'; export const THEMES = 'themes';
export const AUTOMATIC_DARK_MODE_ENABLED = 'automaticDarkModeEnabled'; export const AUTOMATIC_DARK_MODE_ENABLED = 'automaticDarkModeEnabled';
export const AUTOPLAY = 'autoplay'; export const AUTOPLAY = 'autoplay';
export const RESULT_COUNT = 'resultCount';

View file

@ -66,7 +66,7 @@ class ChannelPage extends React.PureComponent<Props> {
render() { render() {
const { fetching, claimsInChannel, claim, page, totalPages } = this.props; const { fetching, claimsInChannel, claim, page, totalPages } = this.props;
const { name, permanent_url: permanentUrl, claim_id: claimId } = claim; const { name, permanent_url: permanentUrl, claim_id: claimId } = claim;
const currentPage = parseInt((page || 1) - 1, 10);
let contentList; let contentList;
if (fetching) { if (fetching) {
contentList = <BusyIndicator message={__('Fetching content')} />; contentList = <BusyIndicator message={__('Fetching content')} />;
@ -104,7 +104,8 @@ class ChannelPage extends React.PureComponent<Props> {
breakClassName="pagination__item pagination__item--break" breakClassName="pagination__item pagination__item--break"
marginPagesDisplayed={2} marginPagesDisplayed={2}
onPageChange={e => this.changePage(e.selected + 1)} onPageChange={e => this.changePage(e.selected + 1)}
initialPage={parseInt(page - 1, 10)} forcePage={currentPage}
initialPage={currentPage}
containerClassName="pagination" containerClassName="pagination"
/> />

View file

@ -1,16 +1,22 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectIsSearching, doUpdateSearchQuery, makeSelectCurrentParam } from 'lbry-redux'; import * as settings from 'constants/settings';
import { selectIsSearching, makeSelectCurrentParam, doUpdateSearchQuery } from 'lbry-redux';
import { doSetClientSetting } from 'redux/actions/settings';
import { doNavigate } from 'redux/actions/navigation'; import { doNavigate } from 'redux/actions/navigation';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import SearchPage from './view'; import SearchPage from './view';
const select = state => ({ const select = state => ({
isSearching: selectIsSearching(state), isSearching: selectIsSearching(state),
query: makeSelectCurrentParam('query')(state), query: makeSelectCurrentParam('query')(state),
showUnavailable: makeSelectClientSetting(settings.SHOW_UNAVAILABLE)(state),
resultCount: makeSelectClientSetting(settings.RESULT_COUNT)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
navigate: path => dispatch(doNavigate(path)), navigate: path => dispatch(doNavigate(path)),
updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)), updateSearchQuery: query => dispatch(doUpdateSearchQuery(query)),
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
}); });
export default connect( export default connect(

View file

@ -1,6 +1,8 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import * as settings from 'constants/settings';
import { isURIValid, normalizeURI } from 'lbry-redux'; import { isURIValid, normalizeURI } from 'lbry-redux';
import { FormField, FormRow } from 'component/common/form';
import FileTile from 'component/fileTile'; import FileTile from 'component/fileTile';
import FileListSearch from 'component/fileListSearch'; import FileListSearch from 'component/fileListSearch';
import ToolTip from 'component/common/tooltip'; import ToolTip from 'component/common/tooltip';
@ -10,13 +12,52 @@ import * as icons from 'constants/icons';
type Props = { type Props = {
query: ?string, query: ?string,
showUnavailable: boolean,
resultCount: number,
setClientSetting: (string, number | boolean) => void,
}; };
class SearchPage extends React.PureComponent<Props> { class SearchPage extends React.PureComponent<Props> {
constructor() {
super();
(this: any).onShowUnavailableChange = this.onShowUnavailableChange.bind(this);
(this: any).onSearchResultCountChange = this.onSearchResultCountChange.bind(this);
}
onSearchResultCountChange(event: SyntheticInputEvent<*>) {
const count = Number(event.target.value);
this.props.setClientSetting(settings.RESULT_COUNT, count);
}
onShowUnavailableChange(event: SyntheticInputEvent<*>) {
this.props.setClientSetting(settings.SHOW_UNAVAILABLE, event.target.checked);
}
render() { render() {
const { query } = this.props; const { query, resultCount, showUnavailable } = this.props;
return ( return (
<Page> <Page>
<React.Fragment>
<FormRow alignRight>
<FormField
type="number"
name="result_count"
min={10}
max={1000}
value={resultCount}
onChange={this.onSearchResultCountChange}
postfix={__('returned results')}
/>
<FormField
type="checkbox"
name="show_unavailable"
onChange={this.onShowUnavailableChange}
checked={showUnavailable}
postfix={__('Include unavailable content')}
/>
</FormRow>
</React.Fragment>
{isURIValid(query) && ( {isURIValid(query) && (
<React.Fragment> <React.Fragment>
<div className="file-list__header"> <div className="file-list__header">

View file

@ -18,7 +18,6 @@ import SettingsPage from './view';
const select = state => ({ const select = state => ({
daemonSettings: selectDaemonSettings(state), daemonSettings: selectDaemonSettings(state),
showNsfw: makeSelectClientSetting(settings.SHOW_NSFW)(state), showNsfw: makeSelectClientSetting(settings.SHOW_NSFW)(state),
showUnavailable: makeSelectClientSetting(settings.SHOW_UNAVAILABLE)(state),
instantPurchaseEnabled: makeSelectClientSetting(settings.INSTANT_PURCHASE_ENABLED)(state), instantPurchaseEnabled: makeSelectClientSetting(settings.INSTANT_PURCHASE_ENABLED)(state),
instantPurchaseMax: makeSelectClientSetting(settings.INSTANT_PURCHASE_MAX)(state), instantPurchaseMax: makeSelectClientSetting(settings.INSTANT_PURCHASE_MAX)(state),
currentTheme: makeSelectClientSetting(settings.THEME)(state), currentTheme: makeSelectClientSetting(settings.THEME)(state),
@ -37,4 +36,7 @@ const perform = dispatch => ({
changeLanguage: newLanguage => dispatch(doChangeLanguage(newLanguage)), changeLanguage: newLanguage => dispatch(doChangeLanguage(newLanguage)),
}); });
export default connect(select, perform)(SettingsPage); export default connect(
select,
perform
)(SettingsPage);

View file

@ -20,14 +20,13 @@ type DaemonSettings = {
type Props = { type Props = {
setDaemonSetting: (string, boolean | string | Price) => void, setDaemonSetting: (string, boolean | string | Price) => void,
setClientSetting: (string, boolean | string | Price) => void, setClientSetting: (string, boolean | string | number | Price) => void,
clearCache: () => Promise<any>, clearCache: () => Promise<any>,
getThemes: () => void, getThemes: () => void,
daemonSettings: DaemonSettings, daemonSettings: DaemonSettings,
showNsfw: boolean, showNsfw: boolean,
instantPurchaseEnabled: boolean, instantPurchaseEnabled: boolean,
instantPurchaseMax: Price, instantPurchaseMax: Price,
showUnavailable: boolean,
currentTheme: string, currentTheme: string,
themes: Array<string>, themes: Array<string>,
automaticDarkModeEnabled: boolean, automaticDarkModeEnabled: boolean,
@ -50,7 +49,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
(this: any).onKeyFeeChange = this.onKeyFeeChange.bind(this); (this: any).onKeyFeeChange = this.onKeyFeeChange.bind(this);
(this: any).onInstantPurchaseMaxChange = this.onInstantPurchaseMaxChange.bind(this); (this: any).onInstantPurchaseMaxChange = this.onInstantPurchaseMaxChange.bind(this);
(this: any).onShowNsfwChange = this.onShowNsfwChange.bind(this); (this: any).onShowNsfwChange = this.onShowNsfwChange.bind(this);
(this: any).onShowUnavailableChange = this.onShowUnavailableChange.bind(this);
(this: any).onShareDataChange = this.onShareDataChange.bind(this); (this: any).onShareDataChange = this.onShareDataChange.bind(this);
(this: any).onThemeChange = this.onThemeChange.bind(this); (this: any).onThemeChange = this.onThemeChange.bind(this);
(this: any).onAutomaticDarkModeChange = this.onAutomaticDarkModeChange.bind(this); (this: any).onAutomaticDarkModeChange = this.onAutomaticDarkModeChange.bind(this);
@ -113,10 +111,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
this.props.setClientSetting(settings.SHOW_NSFW, event.target.checked); this.props.setClientSetting(settings.SHOW_NSFW, event.target.checked);
} }
onShowUnavailableChange(event: SyntheticInputEvent<*>) {
this.props.setClientSetting(settings.SHOW_UNAVAILABLE, event.target.checked);
}
setDaemonSetting(name: string, value: boolean | string | Price) { setDaemonSetting(name: string, value: boolean | string | Price) {
this.props.setDaemonSetting(name, value); this.props.setDaemonSetting(name, value);
} }
@ -140,7 +134,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
showNsfw, showNsfw,
instantPurchaseEnabled, instantPurchaseEnabled,
instantPurchaseMax, instantPurchaseMax,
showUnavailable,
currentTheme, currentTheme,
themes, themes,
automaticDarkModeEnabled, automaticDarkModeEnabled,
@ -253,13 +246,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
checked={autoplay} checked={autoplay}
postfix={__('Autoplay media files')} postfix={__('Autoplay media files')}
/> />
<FormField
type="checkbox"
name="show_unavailable"
onChange={this.onShowUnavailableChange}
checked={showUnavailable}
postfix={__('Show unavailable content in search results')}
/>
<FormField <FormField
type="checkbox" type="checkbox"
name="show_nsfw" name="show_nsfw"

View file

@ -25,6 +25,7 @@ const defaultState = {
themes: getLocalStorageSetting(SETTINGS.THEMES, []), themes: getLocalStorageSetting(SETTINGS.THEMES, []),
automaticDarkModeEnabled: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false), automaticDarkModeEnabled: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false),
autoplay: getLocalStorageSetting(SETTINGS.AUTOPLAY, false), autoplay: getLocalStorageSetting(SETTINGS.AUTOPLAY, false),
resultCount: Number(getLocalStorageSetting(SETTINGS.RESULT_COUNT, 50)),
}, },
isNight: false, isNight: false,
languages: {}, languages: {},

View file

@ -66,6 +66,7 @@
.card--disabled { .card--disabled {
opacity: 0.3; opacity: 0.3;
pointer-events: none;
} }
.card__media { .card__media {

View file

@ -41,6 +41,11 @@
} }
} }
.form-field.form-field--disabled {
opacity: 0.4;
pointer-events: none;
}
.form-field--SimpleMDE { .form-field--SimpleMDE {
display: block; display: block;
width: 100%; width: 100%;

View file

@ -9,12 +9,12 @@
line-height: $spacing-vertical * 1.5; line-height: $spacing-vertical * 1.5;
height: $spacing-vertical * 1.5; height: $spacing-vertical * 1.5;
border-radius: 2px; border-radius: 2px;
&:not(.pagination__item--selected):hover {
&:not(.pagination__item--selected):not(.pagination__item--break):not(.disabled):hover {
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
> a { cursor: pointer;
cursor: hand;
}
} }
> a { > a {
display: inline-block; display: inline-block;
padding: 0 $spacing-vertical * 2 / 3; padding: 0 $spacing-vertical * 2 / 3;