From 3257d87a68d1fd0bc9bd194647c55e8dd9bb09bc Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sun, 30 Jul 2017 15:43:11 +0100 Subject: [PATCH 1/5] Issue #247 Support claim implementation --- ui/js/actions/file_actions.js | 71 +++++++++++++++ ui/js/component/fileActions/index.js | 15 ++++ ui/js/component/fileActions/view.jsx | 124 ++++++++++++++++++++++++++- ui/js/constants/action_types.js | 10 ++- ui/js/reducers/file_actions.js | 68 +++++++++++++++ ui/js/selectors/file_actions.js | 13 +++ ui/js/store.js | 2 + ui/scss/component/_file-actions.scss | 9 ++ 8 files changed, 309 insertions(+), 3 deletions(-) create mode 100644 ui/js/actions/file_actions.js create mode 100644 ui/js/reducers/file_actions.js create mode 100644 ui/js/selectors/file_actions.js diff --git a/ui/js/actions/file_actions.js b/ui/js/actions/file_actions.js new file mode 100644 index 000000000..c0d895426 --- /dev/null +++ b/ui/js/actions/file_actions.js @@ -0,0 +1,71 @@ +import * as types from "constants/action_types"; +import lbry from "lbry"; +import { + selectClaimSupport, + selectClaimSupportAmount, +} from "selectors/file_actions"; +import { selectBalance } from "selectors/wallet"; +import { doOpenModal } from "actions/app"; + +export function doClaimNewSupport() { + return function(dispatch, getState) { + const state = getState(); + const claimSupport = selectClaimSupport(state); + const balance = selectBalance(state); + const amount = selectClaimSupportAmount(state); + + if (balance - amount < 1) { + return dispatch(doOpenModal("insufficientBalance")); + } + + dispatch({ + type: types.CLAIM_SUPPORT_STARTED, + }); + + const successCallback = results => { + // txid hash present indicates successful request + if (results.txid && results.txid.length > 0) { + dispatch({ + type: types.CLAIM_SUPPORT_COMPLETED, + }); + dispatch(doOpenModal("transactionSuccessful")); + } else { + dispatch({ + type: types.CLAIM_SUPPORT_FAILED, + data: { error: results }, + }); + dispatch(doOpenModal("transactionFailed")); + } + }; + + const errorCallback = error => { + dispatch({ + type: types.CLAIM_SUPPORT_FAILED, + data: { error: error.message }, + }); + dispatch(doOpenModal("transactionFailed")); + }; + + lbry + .claim_new_support({ + name: claimSupport.name, + claim_id: claimSupport.claim_id, + amount: claimSupport.amount, + }) + .then(successCallback, errorCallback); + }; +} + +export function doSetClaimSupportAmount(amount) { + return { + type: types.SET_CLAIM_SUPPORT_AMOUNT, + data: { amount }, + }; +} + +export function doSetClaimSupportClaim(claim_id, name) { + return { + type: types.SET_CLAIM_SUPPORT_CLAIM, + data: { claim_id, name }, + }; +} diff --git a/ui/js/component/fileActions/index.js b/ui/js/component/fileActions/index.js index 4311823d1..c65daf59c 100644 --- a/ui/js/component/fileActions/index.js +++ b/ui/js/component/fileActions/index.js @@ -6,6 +6,7 @@ import { makeSelectDownloadingForUri, makeSelectLoadingForUri, } from "selectors/file_info"; + import { makeSelectIsAvailableForUri } from "selectors/availability"; import { selectCurrentModal } from "selectors/app"; import { makeSelectCostInfoForUri } from "selectors/cost_info"; @@ -15,8 +16,16 @@ import { doOpenFileInShell, doOpenFileInFolder } from "actions/file_info"; import { makeSelectClaimForUriIsMine } from "selectors/claims"; import { doPurchaseUri, doLoadVideo, doStartDownload } from "actions/content"; import FileActions from "./view"; +import { makeSelectClaimForUri } from "selectors/claims"; +import { + doClaimNewSupport, + doSetClaimSupportAmount, + doSetClaimSupportClaim, +} from "actions/file_actions"; +import { selectClaimSupportAmount } from "selectors/file_actions"; const makeSelect = () => { + const selectClaim = makeSelectClaimForUri(); const selectFileInfoForUri = makeSelectFileInfoForUri(); const selectIsAvailableForUri = makeSelectIsAvailableForUri(); const selectDownloadingForUri = makeSelectDownloadingForUri(); @@ -25,6 +34,7 @@ const makeSelect = () => { const selectClaimForUriIsMine = makeSelectClaimForUriIsMine(); const select = (state, props) => ({ + claim: selectClaim(state, props), fileInfo: selectFileInfoForUri(state, props), /*availability check is disabled due to poor performance, TBD if it dies forever or requires daemon fix*/ isAvailable: true, //selectIsAvailableForUri(state, props), @@ -34,6 +44,7 @@ const makeSelect = () => { costInfo: selectCostInfoForUri(state, props), loading: selectLoadingForUri(state, props), claimIsMine: selectClaimForUriIsMine(state, props), + amount: selectClaimSupportAmount(state), }); return select; @@ -48,6 +59,10 @@ const perform = dispatch => ({ startDownload: uri => dispatch(doPurchaseUri(uri, "affirmPurchase")), loadVideo: uri => dispatch(doLoadVideo(uri)), restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)), + claimNewSupport: () => dispatch(doClaimNewSupport()), + setAmount: event => dispatch(doSetClaimSupportAmount(event.target.value)), + setClaimSupport: (claim_id, name) => + dispatch(doSetClaimSupportClaim(claim_id, name)), }); export default connect(makeSelect, perform)(FileActions); diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index df624a345..b010309c1 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -1,6 +1,7 @@ import React from "react"; import { Icon, BusyMessage } from "component/common"; import FilePrice from "component/filePrice"; +import { FormField } from "component/form"; import { Modal } from "component/modal"; import Link from "component/link"; import { ToolTip } from "component/tooltip"; @@ -33,7 +34,7 @@ class FileActions extends React.PureComponent { fileInfo && !fileInfo.completed && fileInfo.written_bytes !== false && - fileInfo.written_bytes < fileInfo.total_bytes + fileInfo.fwritten_bytes < fileInfo.total_bytes ) { restartDownload(uri, fileInfo.outpoint); } @@ -57,6 +58,58 @@ class FileActions extends React.PureComponent { this.props.loadVideo(this.props.uri); } + onSupportClaimClicked(event) { + if (this.state.showSupportClaimForm) { + return; + } + + let button; + let parentCard; + if ("a" === event.target.tagName.toLowerCase()) { + button = event.target; + } + + let parent = event.target.parentElement; + do { + if (!button && "a" === parent.tagName.toLowerCase()) { + button = parent; + } + + if ("card" === parent.className.trim()) { + parentCard = parent; + } + parent = parent.parentElement; + } while (parent && !parentCard); + + this.setState({ + showSupportClaimForm: true, + supportClaimLinkOffset: button && parentCard + ? button.getBoundingClientRect().left - + parentCard.getBoundingClientRect().left - + 12 /* left pad + icon */ + : 0, + }); + } + + sendSupportClaim() { + this.setState({ supportInProgress: true }); + const { claim, setClaimSupport, claimNewSupport } = this.props; + setClaimSupport(claim.claim_id, claim.name); + claimNewSupport(); + } + + onClaimSupportSuccessful() { + this.setState({ + showSupportClaimForm: false, + }); + this.onClaimSupportCompleted(); + } + + onClaimSupportCompleted() { + this.props.closeModal(); + this.setState({ supportInProgress: false }); + } + render() { const { fileInfo, @@ -73,6 +126,8 @@ class FileActions extends React.PureComponent { costInfo, loading, claimIsMine, + amount, + setAmount, } = this.props; const metadata = fileInfo ? fileInfo.metadata : null, @@ -180,6 +235,45 @@ class FileActions extends React.PureComponent { /> : ""} + + {this.state.showSupportClaimForm && +
+
+ +
+ 0.0) || this.state.supportInProgress + } + /> + this.setState({ showSupportClaimForm: false })} + /> +
+ +
} } + {modal == "insufficientBalance" && + + {__( + "Insufficient balance: after supporting this claim, you would have less than 1 LBC in your wallet." + )} + } + {modal == "transactionSuccessful" && + + {__( + "Your claim support transaction was successfully placed in the queue." + )} + } + {modal == "transactionFailed" && + + {__("Something went wrong")}: + } ); } diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index 71d6d8072..439e10fb2 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -112,5 +112,11 @@ export const CLAIM_REWARD_STARTED = "CLAIM_REWARD_STARTED"; export const CLAIM_REWARD_SUCCESS = "CLAIM_REWARD_SUCCESS"; export const CLAIM_REWARD_FAILURE = "CLAIM_REWARD_FAILURE"; export const CLAIM_REWARD_CLEAR_ERROR = "CLAIM_REWARD_CLEAR_ERROR"; -export const FETCH_REWARD_CONTENT_COMPLETED = - "FETCH_REWARD_CONTENT_COMPLETED"; +export const FETCH_REWARD_CONTENT_COMPLETED = "FETCH_REWARD_CONTENT_COMPLETED"; + +// File Actions +export const SET_CLAIM_SUPPORT_CLAIM = "SET_CLAIM_SUPPORT_CLAIM"; +export const SET_CLAIM_SUPPORT_AMOUNT = "SET_CLAIM_SUPPORT_AMOUNT"; +export const CLAIM_SUPPORT_STARTED = "CLAIM_SUPPORT_STARTED"; +export const CLAIM_SUPPORT_COMPLETED = "CLAIM_SUPPORT_COMPLETED"; +export const CLAIM_SUPPORT_FAILED = "CLAIM_SUPPORT_FAILED"; diff --git a/ui/js/reducers/file_actions.js b/ui/js/reducers/file_actions.js new file mode 100644 index 000000000..796b00a2b --- /dev/null +++ b/ui/js/reducers/file_actions.js @@ -0,0 +1,68 @@ +import * as types from "constants/action_types"; + +const reducers = {}; +const buildClaimSupport = () => ({ + name: undefined, + amount: undefined, + claim_id: undefined, +}); + +const defaultState = { + claimSupport: buildClaimSupport(), +}; + +reducers[types.SET_CLAIM_SUPPORT_CLAIM] = function(state, action) { + const oldClaimSupport = state.claimSupport; + const newClaimSupport = Object.assign({}, oldClaimSupport, { + claim_id: action.data.claim_id, + name: action.data.name, + }); + + return Object.assign({}, state, { + claimSupport: newClaimSupport, + }); +}; + +reducers[types.SET_CLAIM_SUPPORT_AMOUNT] = function(state, action) { + const oldClaimSupport = state.claimSupport; + const newClaimSupport = Object.assign({}, oldClaimSupport, { + amount: parseFloat(action.data.amount), + }); + + return Object.assign({}, state, { + claimSupport: newClaimSupport, + }); +}; + +reducers[types.CLAIM_SUPPORT_STARTED] = function(state, action) { + const newClaimSupport = Object.assign({}, state.claimSupport, { + sending: true, + }); + + return Object.assign({}, state, { + claimSupport: newClaimSupport, + }); +}; + +reducers[types.CLAIM_SUPPORT_COMPLETED] = function(state, action) { + return Object.assign({}, state, { + draftTransaction: buildClaimSupport(), + }); +}; + +reducers[types.CLAIM_SUPPORT_FAILED] = function(state, action) { + const newClaimSupport = Object.assign({}, state.claimSupport, { + sending: false, + error: action.data.error, + }); + + return Object.assign({}, state, { + claimSupport: newClaimSupport, + }); +}; + +export default function reducer(state = defaultState, action) { + const handler = reducers[action.type]; + if (handler) return handler(state, action); + return state; +} diff --git a/ui/js/selectors/file_actions.js b/ui/js/selectors/file_actions.js new file mode 100644 index 000000000..d4c9bed45 --- /dev/null +++ b/ui/js/selectors/file_actions.js @@ -0,0 +1,13 @@ +import { createSelector } from "reselect"; + +export const _selectState = state => state.fileActions || {}; + +export const selectClaimSupport = createSelector( + _selectState, + state => state.claimSupport || {} +); + +export const selectClaimSupportAmount = createSelector( + selectClaimSupport, + claimSupport => claimSupport.amount +); diff --git a/ui/js/store.js b/ui/js/store.js index 5eb84d4cf..710fad66d 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -10,6 +10,7 @@ import searchReducer from "reducers/search"; import settingsReducer from "reducers/settings"; import userReducer from "reducers/user"; import walletReducer from "reducers/wallet"; +import fileActionsReducer from "reducers/file_actions"; import { persistStore, autoRehydrate } from "redux-persist"; import createCompressor from "redux-persist-transform-compress"; import createFilter from "redux-persist-transform-filter"; @@ -65,6 +66,7 @@ const reducers = redux.combineReducers({ settings: settingsReducer, wallet: walletReducer, user: userReducer, + fileActions: fileActionsReducer, }); const bulkThunk = createBulkThunkMiddleware(); diff --git a/ui/scss/component/_file-actions.scss b/ui/scss/component/_file-actions.scss index 4eda16b51..1d1782770 100644 --- a/ui/scss/component/_file-actions.scss +++ b/ui/scss/component/_file-actions.scss @@ -29,4 +29,13 @@ $color-download: #444; z-index: 1; top: 0px; left: 0px; +} +.file-actions__support_claim { + position: relative; + display: block; + width: 50%; +} +.file-actions__inline-buttons { + display: inline-block; + margin-left: $spacing-vertical * 1 } \ No newline at end of file -- 2.45.3 From f455e59e3436c46cc196e87483b5a5ccbaa51d0f Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sun, 30 Jul 2017 15:58:13 +0100 Subject: [PATCH 2/5] fix fat-finger typo --- ui/js/component/fileActions/view.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/js/component/fileActions/view.jsx b/ui/js/component/fileActions/view.jsx index b010309c1..344e5fe48 100644 --- a/ui/js/component/fileActions/view.jsx +++ b/ui/js/component/fileActions/view.jsx @@ -34,7 +34,7 @@ class FileActions extends React.PureComponent { fileInfo && !fileInfo.completed && fileInfo.written_bytes !== false && - fileInfo.fwritten_bytes < fileInfo.total_bytes + fileInfo.written_bytes < fileInfo.total_bytes ) { restartDownload(uri, fileInfo.outpoint); } -- 2.45.3 From b7058b5f2e194234f69da716ab2f26d007698d36 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sun, 30 Jul 2017 15:59:54 +0100 Subject: [PATCH 3/5] removed unnecessary multiplier in css --- ui/scss/component/_file-actions.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/scss/component/_file-actions.scss b/ui/scss/component/_file-actions.scss index 1d1782770..27e231358 100644 --- a/ui/scss/component/_file-actions.scss +++ b/ui/scss/component/_file-actions.scss @@ -37,5 +37,5 @@ $color-download: #444; } .file-actions__inline-buttons { display: inline-block; - margin-left: $spacing-vertical * 1 + margin-left: $spacing-vertical } \ No newline at end of file -- 2.45.3 From 25b510dabab94fe1010d4d2ed8993f27ba5fed33 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sun, 30 Jul 2017 16:14:30 +0100 Subject: [PATCH 4/5] created actions/claims, removed file_actions reducer and selector and moved corresponding methods to claims --- ui/js/actions/{file_actions.js => claims.js} | 5 +- ui/js/component/fileActions/index.js | 4 +- ui/js/reducers/claims.js | 59 ++++++++++++++++- ui/js/reducers/file_actions.js | 68 -------------------- ui/js/selectors/claims.js | 10 +++ ui/js/selectors/file_actions.js | 13 ---- ui/js/store.js | 2 - 7 files changed, 71 insertions(+), 90 deletions(-) rename ui/js/actions/{file_actions.js => claims.js} (95%) delete mode 100644 ui/js/reducers/file_actions.js delete mode 100644 ui/js/selectors/file_actions.js diff --git a/ui/js/actions/file_actions.js b/ui/js/actions/claims.js similarity index 95% rename from ui/js/actions/file_actions.js rename to ui/js/actions/claims.js index c0d895426..8ac1d7548 100644 --- a/ui/js/actions/file_actions.js +++ b/ui/js/actions/claims.js @@ -1,9 +1,6 @@ import * as types from "constants/action_types"; import lbry from "lbry"; -import { - selectClaimSupport, - selectClaimSupportAmount, -} from "selectors/file_actions"; +import { selectClaimSupport, selectClaimSupportAmount } from "selectors/claims"; import { selectBalance } from "selectors/wallet"; import { doOpenModal } from "actions/app"; diff --git a/ui/js/component/fileActions/index.js b/ui/js/component/fileActions/index.js index c65daf59c..d1d2b9f9d 100644 --- a/ui/js/component/fileActions/index.js +++ b/ui/js/component/fileActions/index.js @@ -21,8 +21,8 @@ import { doClaimNewSupport, doSetClaimSupportAmount, doSetClaimSupportClaim, -} from "actions/file_actions"; -import { selectClaimSupportAmount } from "selectors/file_actions"; +} from "actions/claims"; +import { selectClaimSupportAmount } from "selectors/claims"; const makeSelect = () => { const selectClaim = makeSelectClaimForUri(); diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index c70fd45e0..3ad236687 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -2,7 +2,14 @@ import * as types from "constants/action_types"; import lbryuri from "lbryuri"; const reducers = {}; -const defaultState = {}; +const buildClaimSupport = () => ({ + name: undefined, + amount: undefined, + claim_id: undefined, +}); +const defaultState = { + claimSupport: buildClaimSupport(), +}; reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { const { uri, certificate, claim } = action.data; @@ -190,6 +197,56 @@ reducers[types.CREATE_CHANNEL_COMPLETED] = function(state, action) { }); }; +reducers[types.SET_CLAIM_SUPPORT_CLAIM] = function(state, action) { + const oldClaimSupport = state.claimSupport; + const newClaimSupport = Object.assign({}, oldClaimSupport, { + claim_id: action.data.claim_id, + name: action.data.name, + }); + + return Object.assign({}, state, { + claimSupport: newClaimSupport, + }); +}; + +reducers[types.SET_CLAIM_SUPPORT_AMOUNT] = function(state, action) { + const oldClaimSupport = state.claimSupport; + const newClaimSupport = Object.assign({}, oldClaimSupport, { + amount: parseFloat(action.data.amount), + }); + + return Object.assign({}, state, { + claimSupport: newClaimSupport, + }); +}; + +reducers[types.CLAIM_SUPPORT_STARTED] = function(state, action) { + const newClaimSupport = Object.assign({}, state.claimSupport, { + sending: true, + }); + + return Object.assign({}, state, { + claimSupport: newClaimSupport, + }); +}; + +reducers[types.CLAIM_SUPPORT_COMPLETED] = function(state, action) { + return Object.assign({}, state, { + draftTransaction: buildClaimSupport(), + }); +}; + +reducers[types.CLAIM_SUPPORT_FAILED] = function(state, action) { + const newClaimSupport = Object.assign({}, state.claimSupport, { + sending: false, + error: action.data.error, + }); + + return Object.assign({}, state, { + claimSupport: newClaimSupport, + }); +}; + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action); diff --git a/ui/js/reducers/file_actions.js b/ui/js/reducers/file_actions.js deleted file mode 100644 index 796b00a2b..000000000 --- a/ui/js/reducers/file_actions.js +++ /dev/null @@ -1,68 +0,0 @@ -import * as types from "constants/action_types"; - -const reducers = {}; -const buildClaimSupport = () => ({ - name: undefined, - amount: undefined, - claim_id: undefined, -}); - -const defaultState = { - claimSupport: buildClaimSupport(), -}; - -reducers[types.SET_CLAIM_SUPPORT_CLAIM] = function(state, action) { - const oldClaimSupport = state.claimSupport; - const newClaimSupport = Object.assign({}, oldClaimSupport, { - claim_id: action.data.claim_id, - name: action.data.name, - }); - - return Object.assign({}, state, { - claimSupport: newClaimSupport, - }); -}; - -reducers[types.SET_CLAIM_SUPPORT_AMOUNT] = function(state, action) { - const oldClaimSupport = state.claimSupport; - const newClaimSupport = Object.assign({}, oldClaimSupport, { - amount: parseFloat(action.data.amount), - }); - - return Object.assign({}, state, { - claimSupport: newClaimSupport, - }); -}; - -reducers[types.CLAIM_SUPPORT_STARTED] = function(state, action) { - const newClaimSupport = Object.assign({}, state.claimSupport, { - sending: true, - }); - - return Object.assign({}, state, { - claimSupport: newClaimSupport, - }); -}; - -reducers[types.CLAIM_SUPPORT_COMPLETED] = function(state, action) { - return Object.assign({}, state, { - draftTransaction: buildClaimSupport(), - }); -}; - -reducers[types.CLAIM_SUPPORT_FAILED] = function(state, action) { - const newClaimSupport = Object.assign({}, state.claimSupport, { - sending: false, - error: action.data.error, - }); - - return Object.assign({}, state, { - claimSupport: newClaimSupport, - }); -}; - -export default function reducer(state = defaultState, action) { - const handler = reducers[action.type]; - if (handler) return handler(state, action); - return state; -} diff --git a/ui/js/selectors/claims.js b/ui/js/selectors/claims.js index 2c4b4b511..065b7475c 100644 --- a/ui/js/selectors/claims.js +++ b/ui/js/selectors/claims.js @@ -215,3 +215,13 @@ export const selectMyChannelClaims = createSelector( return claims; } ); + +export const selectClaimSupport = createSelector( + _selectState, + state => state.claimSupport || {} +); + +export const selectClaimSupportAmount = createSelector( + selectClaimSupport, + claimSupport => claimSupport.amount +); diff --git a/ui/js/selectors/file_actions.js b/ui/js/selectors/file_actions.js deleted file mode 100644 index d4c9bed45..000000000 --- a/ui/js/selectors/file_actions.js +++ /dev/null @@ -1,13 +0,0 @@ -import { createSelector } from "reselect"; - -export const _selectState = state => state.fileActions || {}; - -export const selectClaimSupport = createSelector( - _selectState, - state => state.claimSupport || {} -); - -export const selectClaimSupportAmount = createSelector( - selectClaimSupport, - claimSupport => claimSupport.amount -); diff --git a/ui/js/store.js b/ui/js/store.js index 710fad66d..5eb84d4cf 100644 --- a/ui/js/store.js +++ b/ui/js/store.js @@ -10,7 +10,6 @@ import searchReducer from "reducers/search"; import settingsReducer from "reducers/settings"; import userReducer from "reducers/user"; import walletReducer from "reducers/wallet"; -import fileActionsReducer from "reducers/file_actions"; import { persistStore, autoRehydrate } from "redux-persist"; import createCompressor from "redux-persist-transform-compress"; import createFilter from "redux-persist-transform-filter"; @@ -66,7 +65,6 @@ const reducers = redux.combineReducers({ settings: settingsReducer, wallet: walletReducer, user: userReducer, - fileActions: fileActionsReducer, }); const bulkThunk = createBulkThunkMiddleware(); -- 2.45.3 From 6de2f7dda18b16ae454f799f2908f7184d366f49 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Sun, 30 Jul 2017 17:09:03 +0100 Subject: [PATCH 5/5] Updated CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10c3f05b7..01532c8b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Web UI version numbers should always match the corresponding version of LBRY App * Added a loading message to file actions * URL is auto suggested in Publish Page * Added infinite scroll to channel pages + * Added claim support button and inline form to file page. ### Changed * Publishing revamped. Editing claims is much easier. -- 2.45.3