diff --git a/CHANGELOG.md b/CHANGELOG.md index 13778289b..87e3b0aee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,42 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] +### Fixed + +### Added + +### Changed + + + +## [0.22.2] - 2018-07-09 + +### Fixed + * Fixed 'Get Credits' screen so the app doesn't break when LBC is unavailable on ShapeShift ([#1739](https://github.com/lbryio/lbry-app/pull/1739)) + + +## [0.22.1] - 2018-07-05 + +### Added + + +### Fixed + * Take previous bid amount into account when determining how much users have available to deposit ([#1725](https://github.com/lbryio/lbry-app/pull/1725)) + * Sidebar sizing on larger screens ([#1709](https://github.com/lbryio/lbry-app/pull/1709)) + * Publishing scenario while editing and changing URI ([#1716](https://github.com/lbryio/lbry-app/pull/1716)) + * Fix can't right click > paste into description on publish ([#1664](https://github.com/lbryio/lbry-app/issues/1664)) + * Mac/Linux error when starting app up too quickly after shutdown ([#1727](https://github.com/lbryio/lbry-app/pull/1727)) + * Console errors when multiple downloads for same claim exist ([#1724](https://github.com/lbryio/lbry-app/pull/1724)) + * App version in dev mode ([#1722](https://github.com/lbryio/lbry-app/pull/1722)) + * Long URI name displays in transaction list/Help ([#1694](https://github.com/lbryio/lbry-app/pull/1694))/([#1692](https://github.com/lbryio/lbry-app/pull/1692)) + * Edit option missing from certain published claims ([#175](https://github.com/lbryio/lbry-desktop/issues/1756)) + +### Changed + * Show claim name, instead of URI, when loading a channel([#1711](https://github.com/lbryio/lbry-app/pull/1711)) + * Updated LBRY daemon to 0.20.3 which contains some availability improvements ([v0.20.3](https://github.com/lbryio/lbry/releases/tag/v0.20.3)) + +## [0.22.0] - 2018-06-26 + ### Added * Ability to upload thumbnails through spee.ch while publishing ([#1248](https://github.com/lbryio/lbry-app/pull/1248)) * QR code for wallet address to Send and Receive page ([#1582](https://github.com/lbryio/lbry-app/pull/1582)) diff --git a/README.md b/README.md index c5a9478a0..366fccbe3 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Build Status](https://travis-ci.org/lbryio/lbry-app.svg?branch=master)](https://travis-ci.org/lbryio/lbry-app) [![Dependencies](https://david-dm.org/lbryio/lbry-app/status.svg)](https://david-dm.org/lbryio/lbry-app) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/78b627d4f5524792adc48719835e1523)](https://www.codacy.com/app/LBRY/lbry-app?utm_source=github.com&utm_medium=referral&utm_content=lbryio/lbry-app&utm_campaign=Badge_Grade) -[![chat on Discord](https://img.shields.io/discord/362322208485277697.svg?logo=discord)](https://discord.gg/U5aRyN6) +[![chat on Discord](https://img.shields.io/discord/362322208485277697.svg?logo=discord)](https://chat.lbry.io) The LBRY app is a graphical browser for the decentralized content marketplace provided by the [LBRY](https://lbry.io) protocol. It is essentially the @@ -22,17 +22,18 @@ We provide installers for Windows, macOS (v10.9 or greater), and Debian-based Li | Latest Pre-release | [Download](https://lbry.io/get/lbry.pre.exe) | [Download](https://lbry.io/get/lbry.pre.dmg) | [Download](https://lbry.io/get/lbry.pre.deb) Our [releases page](https://github.com/lbryio/lbry-app/releases) also contains the latest -release, pre-releases, and past builds. +release, pre-releases, and past builds. +*Note: If the deb fails to install using the Ubuntu Software Center, install manually via `sudo dpkg -i `. You'll need to run `sudo apt-get install -f` if this is the first time installing it to install dependencies* To install from source or make changes to the application, continue to the next section below. **Community maintained** builds for Arch Linux and Flatpak are available, see below. These installs will need to be updated manually as the in-app update process only supports deb installs at this time. *Note: If coming from a deb install, the directory structure is different and you'll need to [migrate data](https://lbry.io/faq/backup-data).* -| | Flatpak | Arch +| | Flatpak | Arch | --------------------- | ------------------------------------------| -------------------------------------------- | Latest Release | [FlatHub Page](https://flathub.org/apps/details/io.lbry.lbry-app) | [AUR Package](https://aur.archlinux.org/packages/lbry-app-bin/) -| Maintainers | [@choofee](https://github.com/choffee)/[@iuyte](https://github.com/iuyte) | [@kcseb]()/[@TimurKiyivinski](https://github.com/TimurKiyivinski) +| Maintainers | [@choofee](https://github.com/choffee)/[@iuyte](https://github.com/iuyte) | [@kcseb]()/[@TimurKiyivinski](https://github.com/TimurKiyivinski) ## Usage Double click the installed application to browse with the LBRY network. @@ -48,7 +49,7 @@ Double click the installed application to browse with the LBRY network. #### Steps -1. Clone this repository: `git clone https://github.com/lbryio/lbry-app` +1. Clone (or [fork](https://help.github.com/articles/fork-a-repo/)) this repository: `git clone https://github.com/lbryio/lbry-app` 2. Change directories into the downloaded folder: `cd lbry-app` 3. Install the dependencies: `yarn` 4. Run the app: `yarn dev` diff --git a/package.json b/package.json index b83802db7..fc2355f09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "LBRY", - "version": "0.22.0", + "version": "0.22.2", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.", "keywords": [ "lbry" @@ -30,8 +30,8 @@ "flow-defs": "flow-typed install", "release": "yarn compile && electron-builder build", "precommit": "lint-staged", - "postinstall": "electron-builder install-app-deps & node build/downloadDaemon.js", - "clean": "rm -r node_modules && yarn cache clean lbry-redux && yarn" + "preinstall": "yarn cache clean lbry-redux", + "postinstall": "electron-builder install-app-deps & node build/downloadDaemon.js" }, "dependencies": { "bluebird": "^3.5.1", @@ -48,8 +48,9 @@ "formik": "^0.10.4", "hast-util-sanitize": "^1.1.2", "keytar": "^4.2.1", - "lbry-redux": "lbryio/lbry-redux#201d78b68a329065ee5d2a03bfb1607ea0666588", + "lbry-redux": "lbryio/lbry-redux#a0d2d1ac532ade639d39c92f79678ac26e904dfd", "localforage": "^1.7.1", + "mime": "^2.3.1", "mixpanel-browser": "^2.17.1", "moment": "^2.22.0", "qrcode.react": "^0.8.0", @@ -60,7 +61,7 @@ "react-modal": "^3.1.7", "react-paginate": "^5.2.1", "react-redux": "^5.0.3", - "react-simplemde-editor": "^3.6.15", + "react-simplemde-editor": "^3.6.16", "react-toggle": "^4.0.2", "react-transition-group": "1.x", "redux": "^3.6.0", @@ -76,6 +77,7 @@ "semver": "^5.3.0", "shapeshift.io": "^1.3.1", "source-map-support": "^0.5.4", + "stream-to-blob-url": "^2.1.1", "tree-kill": "^1.1.0", "y18n": "^4.0.0" }, @@ -127,7 +129,7 @@ "yarn": "^1.3" }, "lbrySettings": { - "lbrynetDaemonVersion": "0.20.2", + "lbrynetDaemonVersion": "0.20.3", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-daemon-vDAEMONVER-OSNAME.zip", "lbrynetDaemonDir": "static/daemon", "lbrynetDaemonFileName": "lbrynet-daemon" diff --git a/src/main/createWindow.js b/src/main/createWindow.js index 4d4a5bc25..976e8352b 100644 --- a/src/main/createWindow.js +++ b/src/main/createWindow.js @@ -14,7 +14,7 @@ export default appState => { defaultHeight: height, }); - let windowConfiguration = { + const windowConfiguration = { backgroundColor: '#44b098', minWidth: 950, minHeight: 600, @@ -26,17 +26,13 @@ export default appState => { // If state is undefined, create window as maximized. width: windowState.width === undefined ? width : windowState.width, height: windowState.height === undefined ? height : windowState.height, - }; - // Disable renderer process's webSecurity on development to enable CORS. - windowConfiguration = isDev - ? { - ...windowConfiguration, - webPreferences: { - webSecurity: false, - }, - } - : windowConfiguration; + webPreferences: { + // Disable renderer process's webSecurity on development to enable CORS. + webSecurity: !isDev, + plugins: true, + }, + }; const rendererURL = isDev ? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}` diff --git a/src/main/index.js b/src/main/index.js index c678f6579..5bb58725b 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -11,6 +11,7 @@ import isDev from 'electron-is-dev'; import Daemon from './Daemon'; import createTray from './createTray'; import createWindow from './createWindow'; +import pjson from '../../package.json'; autoUpdater.autoDownload = true; @@ -58,16 +59,18 @@ app.on('ready', async () => { if (!isDaemonRunning) { daemon = new Daemon(); daemon.on('exit', () => { - daemon = null; - if (!appState.isQuitting) { - dialog.showErrorBox( - 'Daemon has Exited', - 'The daemon may have encountered an unexpected error, or another daemon instance is already running. \n\n' + - 'For more information please visit: \n' + - 'https://lbry.io/faq/startup-troubleshooting' - ); + if (!isDev) { + daemon = null; + if (!appState.isQuitting) { + dialog.showErrorBox( + 'Daemon has Exited', + 'The daemon may have encountered an unexpected error, or another daemon instance is already running. \n\n' + + 'For more information please visit: \n' + + 'https://lbry.io/faq/startup-troubleshooting' + ); + } + app.quit(); } - app.quit(); }); daemon.launch(); } @@ -82,7 +85,9 @@ app.on('ready', async () => { }); app.on('activate', () => { - rendererWindow.show(); + if (rendererWindow) { + rendererWindow.show(); + } }); app.on('will-quit', event => { @@ -119,6 +124,10 @@ app.on('will-quit', event => { daemon.quit(); event.preventDefault(); } + + if (rendererWindow) { + rendererWindow = null; + } }); // https://electronjs.org/docs/api/app#event-will-finish-launching @@ -171,7 +180,7 @@ ipcMain.on('version-info-requested', () => { return ver.replace(/([^-])rc/, '$1-rc'); } - const localVersion = app.getVersion(); + const localVersion = pjson.version; const latestReleaseAPIURL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest'; const opts = { headers: { diff --git a/src/renderer/component/button/view.jsx b/src/renderer/component/button/view.jsx index 8b496ea12..5cbc17917 100644 --- a/src/renderer/component/button/view.jsx +++ b/src/renderer/component/button/view.jsx @@ -23,6 +23,7 @@ type Props = { noPadding: ?boolean, // to remove padding and allow circular buttons uppercase: ?boolean, iconColor: ?string, + tourniquet: ?boolean, // to shorten the button and ellipsis, only use for links }; class Button extends React.PureComponent { @@ -50,6 +51,7 @@ class Button extends React.PureComponent { noPadding, uppercase, iconColor, + tourniquet, ...otherProps } = this.props; @@ -69,6 +71,7 @@ class Button extends React.PureComponent { 'btn--link': button === 'link', 'btn--external-link': button === 'link' && href, 'btn--uppercase': uppercase, + 'btn--tourniquet': tourniquet, } : 'btn--no-style', className diff --git a/src/renderer/component/common/busy-indicator.jsx b/src/renderer/component/common/busy-indicator.jsx index 759e91b5c..1dfae9332 100644 --- a/src/renderer/component/common/busy-indicator.jsx +++ b/src/renderer/component/common/busy-indicator.jsx @@ -5,10 +5,20 @@ type Props = { message: ?string, }; -const BusyIndicator = (props: Props) => ( - - {props.message} - -); +class BusyIndicator extends React.PureComponent { + static defaultProps = { + message: '', + }; + + render() { + const { message } = this.props; + + return ( + + {message} + + ); + } +} export default BusyIndicator; diff --git a/src/renderer/component/common/form-components/form-field.jsx b/src/renderer/component/common/form-components/form-field.jsx index 0b23e49e8..0cac65160 100644 --- a/src/renderer/component/common/form-components/form-field.jsx +++ b/src/renderer/component/common/form-components/form-field.jsx @@ -6,6 +6,7 @@ import MarkdownPreview from 'component/common/markdown-preview'; import SimpleMDE from 'react-simplemde-editor'; import 'simplemde/dist/simplemde.min.css'; import Toggle from 'react-toggle'; +import { openEditorMenu } from 'util/contextMenu'; type Props = { name: string, @@ -54,11 +55,21 @@ export class FormField extends React.PureComponent { ); } else if (type === 'markdown') { + const stopContextMenu = event => { + event.preventDefault(); + event.stopPropagation(); + }; + const handleEvents = { + contextmenu(codeMirror, event) { + openEditorMenu(event, codeMirror); + }, + }; input = ( -
+
{ @@ -17,8 +17,7 @@ class LoadingScreen extends React.PureComponent { return (
{spinner && } - - {status} + {status && {status}}
); } diff --git a/src/renderer/component/fileList/view.jsx b/src/renderer/component/fileList/view.jsx index cf1b22ae8..5c1c4e6a4 100644 --- a/src/renderer/component/fileList/view.jsx +++ b/src/renderer/component/fileList/view.jsx @@ -152,7 +152,12 @@ class FileList extends React.PureComponent { } this.sortFunctions[sortBy](fileInfos).forEach(fileInfo => { - const { name: claimName, claim_name: claimNameDownloaded, claim_id: claimId } = fileInfo; + const { + name: claimName, + claim_name: claimNameDownloaded, + claim_id: claimId, + outpoint, + } = fileInfo; const uriParams = {}; // This is unfortunate @@ -162,7 +167,8 @@ class FileList extends React.PureComponent { uriParams.claimId = claimId; const uri = buildURI(uriParams); - content.push(); + // See https://github.com/lbryio/lbry-app/issues/1327 for discussion around using outpoint as the key + content.push(); }); return ( diff --git a/src/renderer/component/fileRender/index.js b/src/renderer/component/fileRender/index.js new file mode 100644 index 000000000..5eba1d818 --- /dev/null +++ b/src/renderer/component/fileRender/index.js @@ -0,0 +1,16 @@ +import { connect } from 'react-redux'; +import { THEME } from 'constants/settings'; +import { makeSelectClientSetting } from 'redux/selectors/settings'; + +import FileRender from './view'; + +const select = (state, props) => ({ + currentTheme: makeSelectClientSetting(THEME)(state), +}); + +const perform = dispatch => ({}); + +export default connect( + select, + perform +)(FileRender); diff --git a/src/renderer/component/fileRender/view.jsx b/src/renderer/component/fileRender/view.jsx new file mode 100644 index 000000000..5ce35c6b7 --- /dev/null +++ b/src/renderer/component/fileRender/view.jsx @@ -0,0 +1,47 @@ +// @flow +import React from 'react'; +import LoadingScreen from 'component/common/loading-screen'; +import PdfViewer from 'component/viewers/pdfViewer'; + +type Props = { + mediaType: string, + source: { + filePath: string, + fileType: string, + downloadPath: string, + }, + currentTheme: string, +}; + +class FileRender extends React.PureComponent { + renderViewer() { + const { source, mediaType, currentTheme } = this.props; + const viewerProps = { source, theme: currentTheme }; + + // Supported mediaTypes + const mediaTypes = { + // '3D-file': , + // Add routes to viewer... + }; + + // Supported fileType + const fileTypes = { + pdf: , + // Add routes to viewer... + }; + + const { fileType } = source; + const viewer = mediaType && source && (mediaTypes[mediaType] || fileTypes[fileType]); + const unsupportedMessage = __("Sorry, looks like we can't preview this file."); + const unsupported = ; + + // Return viewer + return viewer || unsupported; + } + + render() { + return
{this.renderViewer()}
; + } +} + +export default FileRender; diff --git a/src/renderer/component/video/index.js b/src/renderer/component/fileViewer/index.js similarity index 95% rename from src/renderer/component/video/index.js rename to src/renderer/component/fileViewer/index.js index 066b7c951..00ef7c3c0 100644 --- a/src/renderer/component/video/index.js +++ b/src/renderer/component/fileViewer/index.js @@ -17,7 +17,7 @@ import { import { makeSelectClientSetting, selectShowNsfw } from 'redux/selectors/settings'; import { selectMediaPaused, makeSelectMediaPositionForUri } from 'redux/selectors/media'; import { selectPlayingUri } from 'redux/selectors/content'; -import Video from './view'; +import FileViewer from './view'; const select = (state, props) => ({ claim: makeSelectClaimForUri(props.uri)(state), @@ -45,4 +45,7 @@ const perform = dispatch => ({ savePosition: (claimId, position) => dispatch(savePosition(claimId, position)), }); -export default connect(select, perform)(Video); +export default connect( + select, + perform +)(FileViewer); diff --git a/src/renderer/component/video/internal/play-button.jsx b/src/renderer/component/fileViewer/internal/play-button.jsx similarity index 78% rename from src/renderer/component/video/internal/play-button.jsx rename to src/renderer/component/fileViewer/internal/play-button.jsx index dcc30e8cc..61d2ce27f 100644 --- a/src/renderer/component/video/internal/play-button.jsx +++ b/src/renderer/component/fileViewer/internal/play-button.jsx @@ -1,6 +1,7 @@ // @flow import React from 'react'; import Button from 'component/button'; +import * as icons from 'constants/icons'; type Props = { play: () => void, @@ -14,8 +15,8 @@ class VideoPlayButton extends React.PureComponent { const { fileInfo, mediaType, isLoading, play } = this.props; const disabled = isLoading || fileInfo === undefined; const doesPlayback = ['audio', 'video'].indexOf(mediaType) !== -1; - const icon = doesPlayback ? 'Play' : 'Folder'; - const label = doesPlayback ? 'Play' : 'View'; + const icon = doesPlayback ? icons.PLAY : icons.EYE; + const label = doesPlayback ? __('Play') : __('View'); return
diff --git a/src/renderer/component/transactionList/internal/transaction-list-item.jsx b/src/renderer/component/transactionList/internal/transaction-list-item.jsx index 3a764aa36..379758722 100644 --- a/src/renderer/component/transactionList/internal/transaction-list-item.jsx +++ b/src/renderer/component/transactionList/internal/transaction-list-item.jsx @@ -72,6 +72,7 @@ class TransactionListItem extends React.PureComponent { {name && claimId && (
))}
@@ -182,34 +203,32 @@ class FilePage extends React.Component {
- {(claimIsMine || subscriptionUri || speechSharable) && ( -
- {claimIsMine ? ( -
- )} +
+ {claimIsMine ? ( +
diff --git a/src/renderer/page/fileListDownloaded/view.jsx b/src/renderer/page/fileListDownloaded/view.jsx index f6b977468..4b53fc668 100644 --- a/src/renderer/page/fileListDownloaded/view.jsx +++ b/src/renderer/page/fileListDownloaded/view.jsx @@ -1,6 +1,5 @@ import React from 'react'; import Button from 'component/button'; -import { FileTile } from 'component/fileTile'; import FileList from 'component/fileList'; import Page from 'component/page'; diff --git a/src/renderer/page/show/view.jsx b/src/renderer/page/show/view.jsx index afa753be6..e6ab992cf 100644 --- a/src/renderer/page/show/view.jsx +++ b/src/renderer/page/show/view.jsx @@ -1,5 +1,6 @@ // @flow import React from 'react'; +import { parseURI } from 'lbry-redux'; import BusyIndicator from 'component/common/busy-indicator'; import ChannelPage from 'page/channel'; import FilePage from 'page/file'; @@ -39,10 +40,11 @@ class ShowPage extends React.PureComponent { let innerContent = ''; if ((isResolvingUri && !claim) || !claim) { + const { claimName } = parseURI(uri); innerContent = (
-

{uri}

+

{claimName}

{isResolvingUri && } {claim === null && diff --git a/src/renderer/redux/actions/app.js b/src/renderer/redux/actions/app.js index e2e433b02..96d215e23 100644 --- a/src/renderer/redux/actions/app.js +++ b/src/renderer/redux/actions/app.js @@ -253,17 +253,19 @@ export function doCheckUpgradeSubscribe() { export function doCheckDaemonVersion() { return dispatch => { Lbry.version().then(({ lbrynet_version: lbrynetVersion }) => { - if (config.lbrynetDaemonVersion === lbrynetVersion) { - dispatch({ + // Avoid the incompatible daemon modal if running in dev mode + // Lets you run a different daemon than the one specified in package.json + if (isDev || config.lbrynetDaemonVersion === lbrynetVersion) { + return dispatch({ type: ACTIONS.DAEMON_VERSION_MATCH, }); - return; } dispatch({ type: ACTIONS.DAEMON_VERSION_MISMATCH, }); - dispatch( + + return dispatch( doNotify({ id: MODALS.INCOMPATIBLE_DAEMON, }) diff --git a/src/renderer/redux/actions/publish.js b/src/renderer/redux/actions/publish.js index 5c725a64f..3f2f8c871 100644 --- a/src/renderer/redux/actions/publish.js +++ b/src/renderer/redux/actions/publish.js @@ -74,6 +74,7 @@ export const doResetThumbnailStatus = () => (dispatch: Dispatch): PromiseAction export const doUploadThumbnail = (filePath: string, nsfw: boolean) => (dispatch: Dispatch) => { const thumbnail = fs.readFileSync(filePath); const fileExt = path.extname(filePath); + const fileName = path.basename(filePath); const makeid = () => { let text = ''; @@ -100,9 +101,9 @@ export const doUploadThumbnail = (filePath: string, nsfw: boolean) => (dispatch: const data = new FormData(); const name = makeid(); - const blob = new Blob([thumbnail], { type: `image/${fileExt.slice(1)}` }); + const file = new File([thumbnail], fileName, { type: `image/${fileExt.slice(1)}` }); data.append('name', name); - data.append('file', blob); + data.append('file', file); data.append('nsfw', nsfw.toString()); return fetch('https://spee.ch/api/claim/publish', { method: 'POST', diff --git a/src/renderer/redux/actions/shape_shift.js b/src/renderer/redux/actions/shape_shift.js index 293b565ef..d68b54f9c 100644 --- a/src/renderer/redux/actions/shape_shift.js +++ b/src/renderer/redux/actions/shape_shift.js @@ -1,5 +1,6 @@ // @flow import Promise from 'bluebird'; +import * as SHAPESHIFT_STATUSES from 'constants/shape_shift'; import * as ACTIONS from 'constants/action_types'; import { coinRegexPatterns } from 'util/shape_shift'; import type { @@ -65,9 +66,15 @@ export const shapeShiftInit = () => (dispatch: Dispatch): ThunkAction => { return shapeShift .coinsAsync() .then(coinData => { + if (coinData.LBC.status === SHAPESHIFT_STATUSES.UNAVAILABLE) { + return dispatch({ + type: ACTIONS.GET_SUPPORTED_COINS_FAIL, + }); + } + let supportedCoins = []; Object.keys(coinData).forEach(symbol => { - if (coinData[symbol].status === 'available') { + if (coinData[symbol].status === SHAPESHIFT_STATUSES.UNAVAILABLE) { supportedCoins.push(coinData[symbol]); } }); @@ -81,7 +88,7 @@ export const shapeShiftInit = () => (dispatch: Dispatch): ThunkAction => { type: ACTIONS.GET_SUPPORTED_COINS_SUCCESS, data: supportedCoins, }); - dispatch(getCoinStats(supportedCoins[0])); + return dispatch(getCoinStats(supportedCoins[0])); }) .catch(err => dispatch({ type: ACTIONS.GET_SUPPORTED_COINS_FAIL, data: err })); }; diff --git a/src/renderer/redux/reducers/shape_shift.js b/src/renderer/redux/reducers/shape_shift.js index ea692e377..60d3216a6 100644 --- a/src/renderer/redux/reducers/shape_shift.js +++ b/src/renderer/redux/reducers/shape_shift.js @@ -117,7 +117,7 @@ export default handleActions( [ACTIONS.GET_SUPPORTED_COINS_FAIL]: (state: ShapeShiftState): ShapeShiftState => ({ ...state, loading: false, - error: 'Error getting available coins', + error: __('There was an error. Please try again later.'), }), [ACTIONS.GET_COIN_STATS_START]: ( diff --git a/src/renderer/scss/all.scss b/src/renderer/scss/all.scss index 487a4684b..fd1c40a2d 100644 --- a/src/renderer/scss/all.scss +++ b/src/renderer/scss/all.scss @@ -23,5 +23,6 @@ @import 'component/_spinner.scss'; @import 'component/_nav.scss'; @import 'component/_file-list.scss'; +@import 'component/_file-render.scss'; @import 'component/_search.scss'; @import 'component/_toggle.scss'; diff --git a/src/renderer/scss/component/_button.scss b/src/renderer/scss/component/_button.scss index afa91989c..d1e6f7e91 100644 --- a/src/renderer/scss/component/_button.scss +++ b/src/renderer/scss/component/_button.scss @@ -62,7 +62,7 @@ button:disabled { font-size: 1em; color: var(--btn-color-inverse); border-radius: 0; - display: inline-block; + display: inline; min-width: 0; box-shadow: none; text-align: left; @@ -76,6 +76,13 @@ button:disabled { background-color: var(--btn-bg-secondary); } +.btn.btn--tourniquet { + max-width: 20vw; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .btn.btn--no-style { font-size: inherit; font-weight: inherit; diff --git a/src/renderer/scss/component/_card.scss b/src/renderer/scss/component/_card.scss index 71495864d..8468ac8d9 100644 --- a/src/renderer/scss/component/_card.scss +++ b/src/renderer/scss/component/_card.scss @@ -150,10 +150,6 @@ padding-top: $spacing-vertical * 1/3; } -.card__subtitle--block { - display: block; -} - .card__meta { color: var(--color-help); font-size: 14px; diff --git a/src/renderer/scss/component/_content.scss b/src/renderer/scss/component/_content.scss index 4a245129a..7f324d20f 100644 --- a/src/renderer/scss/component/_content.scss +++ b/src/renderer/scss/component/_content.scss @@ -96,6 +96,25 @@ } } +.file-render { + width: 100%; + height: 100%; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1; + overflow: hidden; + + .file-render__viewer { + width: 100%; + height: 100%; + background: black; + } +} + img { max-height: 100%; max-width: 100%; diff --git a/src/renderer/scss/component/_file-render.scss b/src/renderer/scss/component/_file-render.scss new file mode 100644 index 000000000..c2e6d979c --- /dev/null +++ b/src/renderer/scss/component/_file-render.scss @@ -0,0 +1,25 @@ +.file-render { + width: 100%; + height: 100%; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1; + overflow: hidden; + + .file-render__viewer { + margin: 0; + width: 100%; + height: 100%; + background: black; + + iframe, + webview { + width: 100%; + height: 100%; + } + } +} diff --git a/src/renderer/scss/component/_nav.scss b/src/renderer/scss/component/_nav.scss index 154f699e1..8dc4587a4 100644 --- a/src/renderer/scss/component/_nav.scss +++ b/src/renderer/scss/component/_nav.scss @@ -10,6 +10,10 @@ border: solid 1px var(--color-divider); margin: $spacing-vertical $spacing-vertical * 2/3; } + + @media (min-width: $large-breakpoint) { + width: calc(var(--side-nav-width) * 1.1); + } } // Sidebar links diff --git a/src/renderer/scss/component/_table.scss b/src/renderer/scss/component/_table.scss index 57c5d98d5..c7811f9b8 100644 --- a/src/renderer/scss/component/_table.scss +++ b/src/renderer/scss/component/_table.scss @@ -80,6 +80,14 @@ table.table--help { font-family: 'metropolis-semibold'; min-width: 130px; } + td:nth-of-type(2) { + /*Tourniquets text over 20VW*/ + max-width: 20vw; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + color: var(--color-help); + } } table.table--transactions { diff --git a/src/renderer/util/contextMenu.js b/src/renderer/util/contextMenu.js index 900ebf54a..0bad608d7 100644 --- a/src/renderer/util/contextMenu.js +++ b/src/renderer/util/contextMenu.js @@ -21,11 +21,11 @@ function injectDevelopmentTemplate(event, templates) { return templates; } -export function openContextMenu(event, templates = []) { - const isSomethingSelected = window.getSelection().toString().length > 0; - const { type } = event.target; +export function openContextMenu(event, templates = [], canEdit = false, selection = '') { + const { type, value } = event.target; + const isSomethingSelected = selection.length > 0 || window.getSelection().toString().length > 0; const isInput = event.target.matches('input') && (type === 'text' || type === 'number'); - const { value } = event.target; + const isTextField = canEdit || isInput || event.target.matches('textarea'); templates.push({ label: 'Copy', @@ -36,12 +36,12 @@ export function openContextMenu(event, templates = []) { // If context menu is opened on Input and there is text on the input and something is selected. const { selectionStart, selectionEnd } = event.target; - if (!!value && isInput && selectionStart !== selectionEnd) { + if (!!value && isTextField && selectionStart !== selectionEnd) { templates.push({ label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' }); } // If context menu is opened on Input and text is present on clipboard - if (clipboard.readText().length > 0 && isInput) { + if (clipboard.readText().length > 0 && isTextField) { templates.push({ label: 'Paste', accelerator: 'CmdOrCtrl+V', @@ -50,7 +50,7 @@ export function openContextMenu(event, templates = []) { } // If context menu is opened on Input - if (isInput && value) { + if (isTextField && value) { templates.push({ label: 'Select All', accelerator: 'CmdOrCtrl+A', @@ -61,6 +61,31 @@ export function openContextMenu(event, templates = []) { injectDevelopmentTemplate(event, templates); remote.Menu.buildFromTemplate(templates).popup(); } + +// This function is used for the markdown description on the publish page +export function openEditorMenu(event, codeMirror) { + const value = codeMirror.doc.getValue(); + const selection = codeMirror.doc.getSelection(); + const templates = [ + { + label: 'Select All', + accelerator: 'CmdOrCtrl+A', + role: 'selectall', + click: () => { + codeMirror.execCommand('selectAll'); + }, + enabled: value.length > 0, + }, + { + label: 'Cut', + accelerator: 'CmdOrCtrl+X', + role: 'cut', + enabled: selection.length > 0, + }, + ]; + openContextMenu(event, templates, true, selection); +} + export function openCopyLinkMenu(text, event) { const templates = [ { @@ -70,5 +95,5 @@ export function openCopyLinkMenu(text, event) { }, }, ]; - openContextMenu(event, templates, false); + openContextMenu(event, templates); } diff --git a/src/renderer/util/form-validation.js b/src/renderer/util/form-validation.js index 167e6c0b2..2057ef390 100644 --- a/src/renderer/util/form-validation.js +++ b/src/renderer/util/form-validation.js @@ -13,7 +13,7 @@ export const validateSendTx = (formValues: DraftTxValues) => { // All we need to check is if the address is valid // If values are missing, users wont' be able to submit the form - if (address && !regexAddress.test(address)) { + if (!process.env.NO_ADDRESS_VALIDATION && !regexAddress.test(address)) { errors.address = __('Not a valid LBRY address'); } diff --git a/src/renderer/util/getMediaType.js b/src/renderer/util/getMediaType.js new file mode 100644 index 000000000..476b68605 --- /dev/null +++ b/src/renderer/util/getMediaType.js @@ -0,0 +1,32 @@ +import mime from 'mime'; + +const formats = [ + [/\.(mp4|m4v|webm|flv|f4v|ogv)$/i, 'video'], + [/\.(mp3|m4a|aac|wav|flac|ogg|opus)$/i, 'audio'], + [/\.(html|htm|xml|pdf|odf|doc|docx|md|markdown|txt|epub|org)$/i, 'document'], + [/\.(stl|obj|fbx|gcode)$/i, '3D-file'], +]; + +export default function getMediaType(contentType, fileName) { + const extName = mime.getExtension(contentType); + const fileExt = extName ? `.${extName}` : null; + const testString = fileName || fileExt; + + // Get mediaType from file extension + if (testString) { + const res = formats.reduce((ret, testpair) => { + const [regex, mediaType] = testpair; + + return regex.test(ret) ? mediaType : ret; + }, testString); + + if (res !== testString) return res; + } + + // Get mediaType from contentType + if (contentType) { + return /^[^/]+/.exec(contentType)[0]; + } + + return 'unknown'; +} diff --git a/yarn.lock b/yarn.lock index e272149a0..37618b5c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5564,9 +5564,9 @@ lazy-val@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.3.tgz#bb97b200ef00801d94c317e29dc6ed39e31c5edc" -lbry-redux@lbryio/lbry-redux#201d78b68a329065ee5d2a03bfb1607ea0666588: +lbry-redux@lbryio/lbry-redux#a0d2d1ac532ade639d39c92f79678ac26e904dfd: version "0.0.1" - resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/201d78b68a329065ee5d2a03bfb1607ea0666588" + resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/a0d2d1ac532ade639d39c92f79678ac26e904dfd" dependencies: proxy-polyfill "0.1.6" reselect "^3.0.0" @@ -7626,9 +7626,9 @@ react-redux@^5.0.3: loose-envify "^1.1.0" prop-types "^15.6.0" -react-simplemde-editor@^3.6.15: - version "3.6.15" - resolved "https://registry.yarnpkg.com/react-simplemde-editor/-/react-simplemde-editor-3.6.15.tgz#b4991304c7e1cac79258bb225579d008c13b5991" +react-simplemde-editor@^3.6.16: + version "3.6.16" + resolved "https://registry.yarnpkg.com/react-simplemde-editor/-/react-simplemde-editor-3.6.16.tgz#33633259478d3395f2c7b70deb56a1a40e863bea" dependencies: simplemde "^1.11.2" @@ -8726,6 +8726,12 @@ stream-to-blob-url@^2.0.0: dependencies: stream-to-blob "^1.0.0" +stream-to-blob-url@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/stream-to-blob-url/-/stream-to-blob-url-2.1.1.tgz#e1ac97f86ca8e9f512329a48e7830ce9a50beef2" + dependencies: + stream-to-blob "^1.0.0" + stream-to-blob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-to-blob/-/stream-to-blob-1.0.0.tgz#9f7a1ada39e16ea282ebb7e4cda307edabde658d"