diff --git a/CHANGELOG.md b/CHANGELOG.md index 75a53a9d1..7f63fe65b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Focus on search bar with {cmd,ctrl} + "l" ([#2003](https://github.com/lbryio/lbry-desktop/pull/2003)) * Add support for clickable channel names on explore page headings ([#2023](https://github.com/lbryio/lbry-desktop/pull/2023)) * Content loading placeholder styles on FileCard/FileTile ([#2022](https://github.com/lbryio/lbry-desktop/pull/2022)) - + ### Changed * Make tooltip smarter ([#1979](https://github.com/lbryio/lbry-desktop/pull/1979)) * Change channel pages to have 48 items instead of 10 ([#2002](https://github.com/lbryio/lbry-desktop/pull/2002)) @@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Simplify FileCard and FileTile component styling ([#2011](https://github.com/lbryio/lbry-desktop/pull/2011)) * Credit card verification messaging ([#2025](https://github.com/lbryio/lbry-desktop/pull/2025)) * Reverse Order & Use System/Location Time/Date ([#2036]https://github.com/lbryio/lbry-desktop/pull/2036) + * Limit file type can be uploaded as thumbnail for publishing ([#2034](https://github.com/lbryio/lbry-desktop/pull/2034)) + ### Fixed * Fixed Transactions filter menu collides with transaction table ([#2005](https://github.com/lbryio/lbry-desktop/pull/2005)) diff --git a/src/renderer/component/common/file-selector.jsx b/src/renderer/component/common/file-selector.jsx index 3729202d0..1aea29fd2 100644 --- a/src/renderer/component/common/file-selector.jsx +++ b/src/renderer/component/common/file-selector.jsx @@ -5,12 +5,18 @@ import Button from 'component/button'; import { FormRow } from 'component/common/form'; import path from 'path'; +type FileFilters = { + name: string, + extensions: string[], +}; + type Props = { type: string, currentPath: ?string, onFileChosen: (string, string) => void, fileLabel?: string, directoryLabel?: string, + filters?: FileFilters[], }; class FileSelector extends React.PureComponent { @@ -29,6 +35,7 @@ class FileSelector extends React.PureComponent { { properties: this.props.type === 'file' ? ['openFile'] : ['openDirectory', 'createDirectory'], + filters: this.props.filters, }, paths => { if (!paths) { diff --git a/src/renderer/component/publishForm/view.jsx b/src/renderer/component/publishForm/view.jsx index 51ca29f54..901e03eac 100644 --- a/src/renderer/component/publishForm/view.jsx +++ b/src/renderer/component/publishForm/view.jsx @@ -52,7 +52,7 @@ type Props = { clearPublish: () => void, resolveUri: string => void, scrollToTop: () => void, - prepareEdit: ({}) => void, + prepareEdit: (claim: any, uri: string) => void, resetThumbnailStatus: () => void, amountNeededForTakeover: ?number, }; @@ -107,11 +107,7 @@ class PublishForm extends React.PureComponent { handleFileChange(filePath: string, fileName: string) { const { updatePublishForm, channel, name } = this.props; - const newFileParams: { - filePath: string, - name?: string, - uri?: string, - } = { filePath }; + const newFileParams: UpdatePublishFormData = { filePath }; if (!name) { const parsedFileName = fileName.replace(regexInvalidURI, ''); @@ -127,7 +123,7 @@ class PublishForm extends React.PureComponent { const { channel, updatePublishForm } = this.props; if (!name) { - updatePublishForm({ name, nameError: undefined }); + updatePublishForm({ nameError: undefined }); return; } @@ -149,7 +145,7 @@ class PublishForm extends React.PureComponent { handleChannelChange(channelName: string) { const { name, updatePublishForm } = this.props; - const form = { channel: channelName }; + const form: UpdatePublishFormData = { channel: channelName }; if (name) { form.uri = this.getNewUri(name, channelName); @@ -217,10 +213,10 @@ class PublishForm extends React.PureComponent { const publishingLicenseUrl = licenseType === COPYRIGHT ? '' : licenseUrl; - const publishParams = { - filePath, - bid: this.props.bid, - title: this.props.title, + const publishParams: PublishParams = { + filePath: filePath || undefined, + bid: this.props.bid || undefined, + title: this.props.title || '', thumbnail: this.props.thumbnail, description: this.props.description, language: this.props.language, @@ -228,16 +224,16 @@ class PublishForm extends React.PureComponent { license: publishingLicense, licenseUrl: publishingLicenseUrl, otherLicenseDescription, - name: this.props.name, + name: this.props.name || undefined, contentIsFree: this.props.contentIsFree, price: this.props.price, - uri: this.props.uri, + uri: this.props.uri || undefined, channel: this.props.channel, isStillEditing: this.props.isStillEditing, }; // Editing a claim - if (!filePath && myClaimForUri) { + if (!filePath && myClaimForUri && myClaimForUri.value) { const { source } = myClaimForUri.value.stream; publishParams.sources = source; } diff --git a/src/renderer/component/selectThumbnail/view.jsx b/src/renderer/component/selectThumbnail/view.jsx index e25c4dbb3..d670ac496 100644 --- a/src/renderer/component/selectThumbnail/view.jsx +++ b/src/renderer/component/selectThumbnail/view.jsx @@ -51,6 +51,12 @@ class SelectThumbnail extends React.PureComponent { thumbnailPath, resetThumbnailStatus, } = this.props; + const filters = [ + { + name: __('Thumbnail Image'), + extensions: ['png', 'jpg', 'jpeg', 'gif'], + }, + ]; const { thumbnailError, thumbnailErrorImage } = this.state; const thumbnailSrc = !thumbnail || thumbnailError ? Native.imagePath(thumbnailErrorImage) : thumbnail; @@ -67,7 +73,7 @@ class SelectThumbnail extends React.PureComponent { {status === THUMBNAIL_STATUSES.API_DOWN || status === THUMBNAIL_STATUSES.MANUAL ? (
{ openModal({ id: MODALS.CONFIRM_THUMBNAIL_UPLOAD }, { path })} /> )} diff --git a/src/renderer/page/settings/view.jsx b/src/renderer/page/settings/view.jsx index ab844c95c..e5aa1ce73 100644 --- a/src/renderer/page/settings/view.jsx +++ b/src/renderer/page/settings/view.jsx @@ -16,6 +16,7 @@ type DaemonSettings = { download_directory: string, disable_max_key_fee: boolean, share_usage_data: boolean, + max_key_fee?: Price, }; type Props = { @@ -34,6 +35,7 @@ type Props = { autoDownload: boolean, encryptWallet: () => void, decryptWallet: () => void, + updateWalletStatus: () => void, walletEncrypted: boolean, osNotificationsEnabled: boolean, }; @@ -65,10 +67,8 @@ class SettingsPage extends React.PureComponent { } componentDidMount() { - const { props } = this; - - props.getThemes(); - props.updateWalletStatus(); + this.props.getThemes(); + this.props.updateWalletStatus(); } onRunOnStartChange(event: SyntheticInputEvent<*>) { @@ -126,18 +126,22 @@ class SettingsPage extends React.PureComponent { } onChangeEncryptWallet() { - const { props } = this; - props.walletEncrypted ? props.decryptWallet() : props.encryptWallet(); - } - - setDaemonSetting(name: string, value: boolean | string | Price) { - this.props.setDaemonSetting(name, value); + const { decryptWallet, walletEncrypted, encryptWallet } = this.props; + if (walletEncrypted) { + decryptWallet(); + } else { + encryptWallet(); + } } onDesktopNotificationsChange(event: SyntheticInputEvent<*>) { this.props.setClientSetting(settings.OS_NOTIFICATIONS_ENABLED, event.target.checked); } + setDaemonSetting(name: string, value: boolean | string | Price) { + this.props.setDaemonSetting(name, value); + } + clearCache() { this.setState({ clearingCache: true, @@ -346,7 +350,7 @@ class SettingsPage extends React.PureComponent { this.onChangeEncryptWallet(e)} + onChange={() => this.onChangeEncryptWallet()} checked={walletEncrypted} postfix={__('Encrypt my wallet with a custom password.')} helper={ diff --git a/src/renderer/redux/actions/publish.js b/src/renderer/redux/actions/publish.js index 70a5cdcf9..ca831c214 100644 --- a/src/renderer/redux/actions/publish.js +++ b/src/renderer/redux/actions/publish.js @@ -19,13 +19,12 @@ import { doNavigate } from 'redux/actions/navigation'; import fs from 'fs'; import path from 'path'; import { CC_LICENSES, COPYRIGHT, OTHER } from 'constants/licenses'; +import type { Dispatch, GetState } from 'types/redux'; +import type { Source } from 'types/claim'; type Action = UpdatePublishFormAction | { type: ACTIONS.CLEAR_PUBLISH }; -type PromiseAction = Promise; -type Dispatch = (action: Action | PromiseAction | Array) => any; -type GetState = () => {}; -export const doResetThumbnailStatus = () => (dispatch: Dispatch): PromiseAction => { +export const doResetThumbnailStatus = () => (dispatch: Dispatch): Promise => { dispatch({ type: ACTIONS.UPDATE_PUBLISH_FORM, data: { @@ -61,20 +60,24 @@ export const doResetThumbnailStatus = () => (dispatch: Dispatch): PromiseAction ); }; -export const doClearPublish = () => (dispatch: Dispatch): PromiseAction => { +export const doClearPublish = () => (dispatch: Dispatch): Promise => { dispatch({ type: ACTIONS.CLEAR_PUBLISH }); return dispatch(doResetThumbnailStatus()); }; export const doUpdatePublishForm = (publishFormValue: UpdatePublishFormData) => ( - dispatch: Dispatch + dispatch: Dispatch ): UpdatePublishFormAction => - dispatch({ - type: ACTIONS.UPDATE_PUBLISH_FORM, - data: { ...publishFormValue }, - }); + dispatch( + ({ + type: ACTIONS.UPDATE_PUBLISH_FORM, + data: { ...publishFormValue }, + }: UpdatePublishFormAction) + ); -export const doUploadThumbnail = (filePath: string, nsfw: boolean) => (dispatch: Dispatch) => { +export const doUploadThumbnail = (filePath: string, nsfw: boolean) => ( + dispatch: Dispatch +) => { const thumbnail = fs.readFileSync(filePath); const fileExt = path.extname(filePath); const fileName = path.basename(filePath); @@ -128,7 +131,7 @@ export const doUploadThumbnail = (filePath: string, nsfw: boolean) => (dispatch: .catch(err => uploadError(err.message)); }; -export const doPrepareEdit = (claim: any, uri: string) => (dispatch: Dispatch) => { +export const doPrepareEdit = (claim: any, uri: string) => (dispatch: Dispatch) => { const { name, amount, @@ -155,7 +158,7 @@ export const doPrepareEdit = (claim: any, uri: string) => (dispatch: Dispatch) = title, } = metadata; - const publishData = { + const publishData: UpdatePublishFormData = { name, channel: channelName, bid: amount, @@ -189,7 +192,10 @@ export const doPrepareEdit = (claim: any, uri: string) => (dispatch: Dispatch) = dispatch({ type: ACTIONS.DO_PREPARE_EDIT, data: publishData }); }; -export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getState: () => {}) => { +export const doPublish = (params: PublishParams) => ( + dispatch: Dispatch, + getState: () => {} +) => { const state = getState(); const myChannels = selectMyChannelClaims(state); @@ -223,17 +229,18 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat licenseUrl, language, thumbnail, + fee: fee || undefined, + description: description || undefined, }; - if (fee) { - metadata.fee = fee; - } - - if (description) { - metadata.description = description; - } - - const publishPayload = { + const publishPayload: { + name: ?string, + channel_id: string, + bid: ?number, + metadata: ?any, + file_path?: string, + sources?: Source, + } = { name, channel_id: channelId, bid, @@ -265,7 +272,7 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat }; // Calls claim_list_mine until any pending publishes are confirmed -export const doCheckPendingPublishes = () => (dispatch: Dispatch, getState: GetState) => { +export const doCheckPendingPublishes = () => (dispatch: Dispatch, getState: GetState) => { const state = getState(); const pendingPublishes = selectPendingPublishes(state); diff --git a/src/renderer/redux/actions/shape_shift.js b/src/renderer/redux/actions/shape_shift.js index c2aecefba..5e66e6255 100644 --- a/src/renderer/redux/actions/shape_shift.js +++ b/src/renderer/redux/actions/shape_shift.js @@ -14,6 +14,7 @@ import type { GetActiveShiftFail, } from 'redux/reducers/shape_shift'; import type { FormikActions } from 'types/common'; +import type { Dispatch, ThunkAction } from 'types/redux'; // use promise chains instead of callbacks for shapeshift api const shapeShift = Promise.promisifyAll(require('shapeshift.io')); @@ -38,9 +39,6 @@ export type Action = // Basic thunk types // It would be nice to import these from types/common // Not sure how that would work since they rely on the Action type -type PromiseAction = Promise; -export type Dispatch = (action: Action | PromiseAction | Array) => any; -type ThunkAction = (dispatch: Dispatch) => any; // ShapeShift form values export type ShapeShiftFormValues = { @@ -49,7 +47,7 @@ export type ShapeShiftFormValues = { receiveAddress: string, }; -export const getCoinStats = (coin: string) => (dispatch: Dispatch): ThunkAction => { +export const getCoinStats = (coin: string) => (dispatch: Dispatch): ThunkAction => { const pair = `${coin.toLowerCase()}_lbc`; dispatch({ type: ACTIONS.GET_COIN_STATS_START, data: coin }); @@ -60,7 +58,7 @@ export const getCoinStats = (coin: string) => (dispatch: Dispatch): ThunkAction .catch(err => dispatch({ type: ACTIONS.GET_COIN_STATS_FAIL, data: err })); }; -export const shapeShiftInit = () => (dispatch: Dispatch): ThunkAction => { +export const shapeShiftInit = () => (dispatch: Dispatch): ThunkAction => { dispatch({ type: ACTIONS.GET_SUPPORTED_COINS_START }); return shapeShift @@ -94,8 +92,8 @@ export const shapeShiftInit = () => (dispatch: Dispatch): ThunkAction => { }; export const createShapeShift = (values: ShapeShiftFormValues, actions: FormikActions) => ( - dispatch: Dispatch -): ThunkAction => { + dispatch: Dispatch +): ThunkAction => { const { originCoin, returnAddress, receiveAddress: withdrawalAddress } = values; const pair = `${originCoin.toLowerCase()}_lbc`; @@ -114,7 +112,9 @@ export const createShapeShift = (values: ShapeShiftFormValues, actions: FormikAc }); }; -export const getActiveShift = (depositAddress: string) => (dispatch: Dispatch): ThunkAction => { +export const getActiveShift = (depositAddress: string) => ( + dispatch: Dispatch +): ThunkAction => { dispatch({ type: ACTIONS.GET_ACTIVE_SHIFT_START }); return shapeShift @@ -123,5 +123,5 @@ export const getActiveShift = (depositAddress: string) => (dispatch: Dispatch): .catch(err => dispatch({ type: ACTIONS.GET_ACTIVE_SHIFT_FAIL, data: err })); }; -export const clearShapeShift = () => (dispatch: Dispatch): Action => +export const clearShapeShift = () => (dispatch: Dispatch): Action => dispatch({ type: ACTIONS.CLEAR_SHAPE_SHIFT }); diff --git a/src/renderer/redux/reducers/publish.js b/src/renderer/redux/reducers/publish.js index c3e728384..53e20375c 100644 --- a/src/renderer/redux/reducers/publish.js +++ b/src/renderer/redux/reducers/publish.js @@ -4,6 +4,7 @@ import { buildURI } from 'lbry-redux'; import * as ACTIONS from 'constants/action_types'; import * as THUMBNAIL_STATUSES from 'constants/thumbnail_upload_statuses'; import { CHANNEL_ANONYMOUS } from 'constants/claim'; +import type { Source } from 'types/claim'; type PublishState = { editingURI: ?string, @@ -53,6 +54,8 @@ export type UpdatePublishFormData = { bidError?: string, otherLicenseDescription?: string, licenseUrl?: string, + licenseType?: string, + uri?: string, }; export type UpdatePublishFormAction = { @@ -61,32 +64,27 @@ export type UpdatePublishFormAction = { }; export type PublishParams = { - name: string, - bid: number, - filePath: string, + name?: string, + bid?: number, + filePath?: string, description: ?string, language: string, - publishingLicense: string, - publishingLicenseUrl: string, + publishingLicense?: string, + publishingLicenseUrl?: string, thumbnail: ?string, nsfw: boolean, channel: string, - channelId: string, + channelId?: string, title: string, contentIsFree: boolean, - uri: string, + uri?: string, license: ?string, licenseUrl: ?string, price: { currency: string, amount: number, }, - source?: { - contentType: string, - source: string, - sourceType: string, - version: string, - }, + sources?: Source, }; const defaultState: PublishState = { diff --git a/src/renderer/redux/reducers/subscriptions.js b/src/renderer/redux/reducers/subscriptions.js index 348528007..0ccfbd7db 100644 --- a/src/renderer/redux/reducers/subscriptions.js +++ b/src/renderer/redux/reducers/subscriptions.js @@ -3,6 +3,7 @@ import * as ACTIONS from 'constants/action_types'; import * as NOTIFICATION_TYPES from 'constants/notification_types'; import { handleActions } from 'util/redux-utils'; import type { Subscription } from 'types/subscription'; +import type { Dispatch as ReduxDispatch } from 'types/redux'; export type NotificationType = | NOTIFICATION_TYPES.DOWNLOADING @@ -79,7 +80,7 @@ export type Action = | CheckSubscriptionStarted | CheckSubscriptionCompleted | Function; -export type Dispatch = (action: Action) => any; +export type Dispatch = ReduxDispatch; const defaultState = { subscriptions: [], diff --git a/src/renderer/scss/_gui.scss b/src/renderer/scss/_gui.scss index d4db1e911..8124440f8 100644 --- a/src/renderer/scss/_gui.scss +++ b/src/renderer/scss/_gui.scss @@ -382,4 +382,7 @@ p { .thumbnail-preview { height: var(--thumbnail-preview-height); width: var(--thumbnail-preview-width); + background-size: cover; + background-repeat: no-repeat; + background-position: 50% 50%; } diff --git a/src/renderer/types/claim.js b/src/renderer/types/claim.js index d45ee3bf3..dcc8fef1e 100644 --- a/src/renderer/types/claim.js +++ b/src/renderer/types/claim.js @@ -1,6 +1,14 @@ // @flow // Currently incomplete + +export type Source = { + contentType: string, + source: string, + sourceType: string, + version: string, +}; + export type Metadata = { nsfw: boolean, title: string, @@ -36,6 +44,7 @@ export type Claim = { }, stream: { metadata: Metadata, + source: Source, }, }, }; diff --git a/src/renderer/types/redux.js b/src/renderer/types/redux.js new file mode 100644 index 000000000..e198e51b3 --- /dev/null +++ b/src/renderer/types/redux.js @@ -0,0 +1,6 @@ +// @flow + +// eslint-disable-next-line no-use-before-define +export type Dispatch = (action: T | Promise | Array | ThunkAction) => any; // Need to refer to ThunkAction +export type GetState = () => {}; +export type ThunkAction = (dispatch: Dispatch, getState: GetState) => any;