diff --git a/ui/js/actions/content.js b/ui/js/actions/content.js index 2f55e2094..0c9f3d72b 100644 --- a/ui/js/actions/content.js +++ b/ui/js/actions/content.js @@ -301,3 +301,32 @@ export function doFetchChannelListMine() { lbry.channel_list_mine().then(callback); }; } + +export function doCreateChannel(name, amount) { + return function(dispatch, getState) { + dispatch({ + type: types.CREATE_CHANNEL_STARTED, + }); + + return new Promise((resolve, reject) => { + lbry + .channel_new({ + channel_name: name, + amount: parseFloat(amount), + }) + .then( + channelClaim => { + channelClaim.name = name; + dispatch({ + type: types.CREATE_CHANNEL_COMPLETED, + data: { channelClaim }, + }); + resolve(channelClaim); + }, + err => { + resolve(err); + } + ); + }); + }; +} diff --git a/ui/js/constants/action_types.js b/ui/js/constants/action_types.js index a63b2345a..1a670c654 100644 --- a/ui/js/constants/action_types.js +++ b/ui/js/constants/action_types.js @@ -64,6 +64,8 @@ export const FETCH_CHANNEL_LIST_MINE_STARTED = "FETCH_CHANNEL_LIST_MINE_STARTED"; export const FETCH_CHANNEL_LIST_MINE_COMPLETED = "FETCH_CHANNEL_LIST_MINE_COMPLETED"; +export const CREATE_CHANNEL_STARTED = "CREATE_CHANNEL_STARTED"; +export const CREATE_CHANNEL_COMPLETED = "CREATE_CHANNEL_COMPLETED"; // Search export const SEARCH_STARTED = "SEARCH_STARTED"; diff --git a/ui/js/page/publish/index.js b/ui/js/page/publish/index.js index d959b04bd..f418b5592 100644 --- a/ui/js/page/publish/index.js +++ b/ui/js/page/publish/index.js @@ -13,6 +13,7 @@ import { doFetchClaimListMine, doFetchChannelListMine, doResolveUri, + doCreateChannel, } from "actions/content"; import rewards from "rewards"; import PublishPage from "./view"; @@ -33,6 +34,7 @@ const perform = dispatch => ({ dispatch(doClaimRewardType(rewards.TYPE_FIRST_CHANNEL)), fetchChannelListMine: () => dispatch(doFetchChannelListMine()), resolveUri: uri => dispatch(doResolveUri(uri)), + createChannel: (name, amount) => dispatch(doCreateChannel(name, amount)), }); export default connect(select, perform)(PublishPage); diff --git a/ui/js/page/publish/view.jsx b/ui/js/page/publish/view.jsx index 5d06b9b6f..ea4f84dd6 100644 --- a/ui/js/page/publish/view.jsx +++ b/ui/js/page/publish/view.jsx @@ -216,6 +216,10 @@ class PublishPage extends React.PureComponent { handleNameChange(event) { var rawName = event.target.value; + this.nameChanged(rawName); + } + + nameChanged(rawName) { if (!rawName) { this.setState({ rawName: "", @@ -233,15 +237,26 @@ class PublishPage extends React.PureComponent { return; } + let channel = ""; + if (this.state.channel !== "anonymous") channel = this.state.channel; + const name = rawName.toLowerCase(); - const uri = lbryuri.normalize(name); + const uri = lbryuri.build({ contentName: name, channelName: channel }); this.setState({ rawName: rawName, name: name, uri, }); - this.props.resolveUri(uri); + if (this.resolveUriTimeout) { + clearTimeout(this.resolveUriTimeout); + this.resolveUriTimeout = undefined; + } + const resolve = () => this.props.resolveUri(uri); + + this.resolveUriTimeout = setTimeout(resolve.bind(this), 500, { + once: true, + }); } handleBidChange(event) { @@ -302,40 +317,12 @@ class PublishPage extends React.PureComponent { }); } - handleChannelChange(event) { - const channel = event.target.value; - + handleChannelChange(channelName) { this.setState({ - channel: channel, - }); - } - - handleNewChannelNameChange(event) { - const newChannelName = event.target.value.startsWith("@") - ? event.target.value - : "@" + event.target.value; - - if ( - newChannelName.length > 1 && - !lbryuri.isValidName(newChannelName.substr(1), false) - ) { - this.refs.newChannelName.showError( - __("LBRY channel names must contain only letters, numbers and dashes.") - ); - return; - } else { - this.refs.newChannelName.clearError(); - } - - this.setState({ - newChannelName: newChannelName, - }); - } - - handleNewChannelBidChange(event) { - this.setState({ - newChannelBid: event.target.value, + channel: channelName, }); + const nameChanged = () => this.nameChanged(this.state.rawName); + setTimeout(nameChanged.bind(this), 500, { once: true }); } handleTOSChange(event) { @@ -413,7 +400,8 @@ class PublishPage extends React.PureComponent { getNameBidHelpText() { if ( this.state.uri && - this.props.resolvingUris.indexOf(this.state.uri) !== -1 + this.props.resolvingUris.indexOf(this.state.uri) !== -1 && + this.claim() === undefined ) { return ; } else if (!this.state.name) { @@ -700,77 +688,11 @@ class PublishPage extends React.PureComponent { -
-
-

{__("Identity")}

-
- {__("Who created this content?")} -
-
-
- {this.props.fetchingChannels - ? - : { - this.handleChannelChange(event); - }} - value={this.state.channel} - > - - {this.props.channels.map(({ name }) => - - )} - - } -
- {this.state.channel == "new" - ?
- { - this.handleNewChannelNameChange(event); - }} - ref={newChannelName => { - this.refs.newChannelName = newChannelName; - }} - value={this.state.newChannelName} - /> - { - this.handleNewChannelBidChange(event); - }} - value={this.state.newChannelBid} - /> -
- { - this.handleCreateChannelClick(event); - }} - disabled={this.state.creatingChannel} - /> -
-
- : null} -
+
@@ -898,4 +820,175 @@ class PublishPage extends React.PureComponent { } } +class ChannelSection extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + newChannelName: "@", + newChannelBid: 10, + addingChannel: false, + }; + } + + handleChannelChange(event) { + const channel = event.target.value; + if (channel === "new") this.setState({ addingChannel: true }); + else { + this.setState({ addingChannel: false }); + this.props.handleChannelChange(event.target.value); + } + } + + handleNewChannelNameChange(event) { + const newChannelName = event.target.value.startsWith("@") + ? event.target.value + : "@" + event.target.value; + + if ( + newChannelName.length > 1 && + !lbryuri.isValidName(newChannelName.substr(1), false) + ) { + this.refs.newChannelName.showError( + __("LBRY channel names must contain only letters, numbers and dashes.") + ); + return; + } else { + this.refs.newChannelName.clearError(); + } + + this.setState({ + newChannelName, + }); + } + + handleNewChannelBidChange(event) { + this.setState({ + newChannelBid: event.target.value, + }); + } + + handleCreateChannelClick(event) { + if (this.state.newChannelName.length < 5) { + this.refs.newChannelName.showError( + __("LBRY channel names must be at least 4 characters in length.") + ); + return; + } + + this.setState({ + creatingChannel: true, + }); + + const newChannelName = this.state.newChannelName; + const amount = parseFloat(this.state.newChannelBid); + this.setState({ + creatingChannel: true, + }); + const success = (() => { + this.setState({ + creatingChannel: false, + addingChannel: false, + channel: newChannelName, + }); + this.props.handleChannelChange(newChannelName); + }).bind(this); + const failure = (err => { + this.setState({ + creatingChannel: false, + }); + this.refs.newChannelName.showError( + __("Unable to create channel due to an internal error.") + ); + }).bind(this); + this.props.createChannel(newChannelName, amount).then(success, failure); + } + + render() { + const lbcInputHelp = __( + "This LBC remains yours and the deposit can be undone at any time." + ); + + const { fetchingChannels, channels } = this.props; + + let channelContent = []; + if (channels.length > 0) { + channelContent.push( + + + {this.props.channels.map(({ name }) => + + )} + + + ); + if (fetchingChannels) { + channelContent.push( + + ); + } + } else if (fetchingChannels) { + channelContent.push(); + } + + return ( +
+
+

{__("Identity")}

+
+ {__("Who created this content?")} +
+
+
+ {channelContent} +
+ {this.state.addingChannel && +
+ { + this.handleNewChannelNameChange(event); + }} + value={this.state.newChannelName} + /> + +
+ +
+
} +
+ ); + } +} + export default PublishPage; diff --git a/ui/js/reducers/claims.js b/ui/js/reducers/claims.js index 2172832af..233028c8c 100644 --- a/ui/js/reducers/claims.js +++ b/ui/js/reducers/claims.js @@ -15,7 +15,13 @@ reducers[types.RESOLVE_URI_COMPLETED] = function(state, action) { byUri[uri] = claim.claim_id; } else if (claim === undefined && certificate !== undefined) { byId[certificate.claim_id] = certificate; - byUri[uri] = certificate.claim_id; + // Don't point URI at the channel certificate unless it actually is + // a channel URI. This is brittle. + if (!uri.split(certificate.name)[1].match(/\//)) { + byUri[uri] = certificate.claim_id; + } else { + byUri[uri] = null; + } } else { byUri[uri] = null; } @@ -85,6 +91,20 @@ reducers[types.FETCH_CHANNEL_CLAIMS_COMPLETED] = function(state, action) { }); }; +reducers[types.CREATE_CHANNEL_COMPLETED] = function(state, action) { + const { channelClaim } = action.data; + const { byId } = state; + const myChannelClaims = new Set(state.myChannelClaims); + + byId[channelClaim.claim_id] = channelClaim; + myChannelClaims.add(channelClaim.claim_id); + + return Object.assign({}, state, { + byId, + myChannelClaims, + }); +}; + export default function reducer(state = defaultState, action) { const handler = reducers[action.type]; if (handler) return handler(state, action);