Skip to content
This repository has been archived by the owner on Mar 28, 2023. It is now read-only.

Follow refactor #676

Merged
merged 14 commits into from
Aug 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 29 additions & 23 deletions js/collections/Followers.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,39 @@
import { Collection } from 'backbone';
import UserShort from '../models/UserCard';
/* Used for lists of followers and following */

import app from '../app';
import { Collection } from 'backbone';
import Follower from '../models/Follower';

export default class extends Collection {
constructor(models = [], options = {}) {
super(models, options);

module.exports = Collection.extend({
/* we have to use the older style for this collection, the ES6 style creates a bug where models
cannot be removed using their ids */
const types = ['followers', 'following'];
if (types.indexOf(options.type) === -1) {
throw new Error(`Please provide a type as one of ${types.join(', ')}`);
}

initialize(models, options) {
if (!options.type) {
throw new Error('You must provide a type to the collection');
if (!options.peerId) {
throw new Error('Please provide a peerId');
}

this.guid = options.guid;
this.type = options.type;
},
this.options = options;
}

url() {
return app.getServerUrl(this.guid === app.profile.id || !this.guid ?
`ob/${this.type}` : `ipns/${this.guid}/${this.type}`);
},
model(attrs, options) {
return new Follower(attrs, options);
}

modelId(attrs) {
return attrs.peerId;
}

model: UserShort,
url() {
return app.getServerUrl(`ob/${this.options.type === 'followers' ? 'followers' : 'following'}` +
`${app.profile.id === this.options.peerId ? '' : `/${this.options.peerId}`}`);
}

parse(response) {
return response.map((guid) => {
// if a plain guid was passed in, convert it to an object
if (typeof guid === 'string') return { guid };
return guid;
});
},
});
return response.map(peerId => ({ peerId }));
}
}
22 changes: 14 additions & 8 deletions js/languages/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
"statusPublishFailed": "Publishing failed. %{retryLink}",
"statusPublishComplete": "Publishing complete."
},
"follow": {
"typeFollow": "following",
"typeUnfollow": "unfollowing",
"followErrorTitle": "There was an error %{type} %{user}."
},
"restartNow": "Restart Now",
"restartLater": "Restart Later",
"langChangeRestartTitle": "Restart needed for language change",
Expand Down Expand Up @@ -150,10 +155,15 @@
"website": "Website",
"email": "Email",
"noLocation": "No Location",
"noFollowers": "No One is Following %{name} Yet",
"noFollowing": "%{name} is Not Following Anyone Yet",
"noOwnFollowers": "You Don't Have Any Followers Yet",
"noOwnFollowing": "You Aren't Following Anyone Yet",
"followTab": {
"noFollowers": "No One is Following %{name} Yet",
"noFollowing": "%{name} is Not Following Anyone Yet",
"noOwnFollowers": "You Don't Have Any Followers Yet",
"noOwnFollowing": "You Aren't Following Anyone Yet",
"followersFetchError": "Unable to fetch the followers list.",
"followingFetchError": "Unable to fetch the following list.",
"btnRetry": "Retry"
},
"getFollowingError": "There was an error when determining if this user follows you.",
"modAddError": "There was an error adding this moderator. \n %{errMsg}",
"modRemoveError": "There was an error removing this moderator. \n %{errMsg}",
Expand Down Expand Up @@ -1396,10 +1406,6 @@
"provideValidPortRange": "Please provide a number between 0 and 65535.",
"invalidTorProxy": "The value does not appear to be in the right format. It should be in the format ip-address:port, e.g. 127.0.0.1:9150. The port must be a number between 0 and 65535."
},
"errors": {
"saveError": "There was an error saving your data.",
"badResult": "The server returned an error."
},
"shippingAddressModelErrors": {
"provideName": "Please provide a name.",
"provideCountry": "Please provide a country."
Expand Down
6 changes: 3 additions & 3 deletions js/models/UserCard.js → js/models/Follower.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* Used as a list item of both follower and following lists */

import BaseModel from './BaseModel';

export default class extends BaseModel {
// this model will only be { guid: exampleguid }

get idAttribute() {
return 'guid';
return 'peerId';
}
}

27 changes: 7 additions & 20 deletions js/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,10 @@ let walletBalanceFetch;
let walletBalanceFetchFailed;

function fetchStartupData() {
ownFollowingFetch = !ownFollowingFetch || ownFollowingFetch ?
ownFollowingFetch = !ownFollowingFetch || ownFollowingFailed ?
app.ownFollowing.fetch() : ownFollowingFetch;
exchangeRatesFetch = exchangeRatesFetch || fetchExchangeRates();
walletBalanceFetch = !walletBalanceFetch || walletBalanceFetch ?
walletBalanceFetch = !walletBalanceFetch || walletBalanceFetchFailed ?
app.walletBalance.fetch() : walletBalanceFetch;

$.whenAll(ownFollowingFetch, exchangeRatesFetch, walletBalanceFetch)
Expand Down Expand Up @@ -343,7 +343,7 @@ function onboardIfNeeded() {
// let's go onboard
onboard().done(() => onboardIfNeededDeferred.resolve());
} else {
fetchStartupData().done(() => onboardIfNeededDeferred.resolve());
onboardIfNeededDeferred.resolve();
}
});

Expand Down Expand Up @@ -382,8 +382,10 @@ function start() {
app.localSettings.save('language', getValidLanguage(lang));
});

app.ownFollowing = new Followers(null, { type: 'following' });
app.ownFollowers = new Followers(null, { type: 'followers' });
app.ownFollowing = new Followers([], {
type: 'following',
peerId: app.profile.id,
});

app.walletBalance = new WalletBalance();

Expand Down Expand Up @@ -744,20 +746,5 @@ ipcRenderer.on('close-attempt', (e) => {
}
});

// update ownFollowers based on follow socket communication
serverConnectEvents.on('connected', (connectedEvent) => {
connectedEvent.socket.on('message', (e) => {
if (e.jsonData) {
if (e.jsonData.notification) {
if (e.jsonData.notification.type === 'follow') {
app.ownFollowers.unshift({ guid: e.jsonData.notification.peerId });
} else if (e.jsonData.notification.type === 'unfollow') {
app.ownFollowers.remove(e.jsonData.notification.peerId); // remove by id
}
}
}
});
});

// initialize our listing delete handler
listingDeleteHandler();
2 changes: 1 addition & 1 deletion js/templates/chat/conversation.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</div>
<div class="subMenu boxList clrBr clrP clrSh1 hide js-subMenu">
<a class="clrT" href="<%= ob.guid %>"><%= ob.polyT('chat.conversation.subMenu.viewPage') %></a>
<a class="clrT js-blockUser"><%= ob.polyT('chat.conversation.subMenu.blockUser') %></a>
<a class="clrT js-blockUser TODO"><%= ob.polyT('chat.conversation.subMenu.blockUser') %></a>
<a class="clrT js-deleteConversation deleteConvoMenuItem"><%= ob.polyT('chat.conversation.subMenu.deleteConvo') %></a>
</div>
<div class="convoMessagesWindow tx6 js-convoMessagesWindow">
Expand Down
6 changes: 4 additions & 2 deletions js/templates/connectedPeersPage.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ <h1 class="flexExpand"><%= ob.polyT('connectedPeersPage.heading') %></h1>
<div class="tx4 border clrBr clrP pad"><%= ob.polyT('connectedPeersPage.totalPeers', { totalPeers: ob.peers.length }) %></div>
</div>
</div>
<div class="userPageFollow flexRow js-peerWrapper"></div>
<div class="userPageFollow">
<div class="userCardsContainer flexRow js-peerWrapper"></div>
</div>

<div class="js-morePeers hide">
<hr class="clrBr">
<a class="btn clrBr clrP js-morePeersBtn">Load More</a>
</div>
</div>
</div>
2 changes: 2 additions & 0 deletions js/templates/userPage/follow.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<div class="js-userCardsContainer userCardsContainer flexRow"></div>
<div class="js-followLoadingContainer followLoadingContainer"></div>
13 changes: 13 additions & 0 deletions js/templates/userPage/followLoading.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<% if (ob.isFetching) { %>
<div class="loadingSpinnerWrap">
<% print(ob.spinner({ className: 'spinnerMd' })) %>
</div>
<% } else if (ob.fetchFailed) { %>
<p><%= ob.fetchErrorTitle %></p>
<% if (ob.fetchErrorMsg) { %>
<p><%= ob.fetchErrorMsg %></p>
<% } %>
<button class="btn normalBtn clrP clrBr js-retry"><%= ob.polyT('userPage.followTab.btnRetry') %></button>
<% } else if (ob.noResults) { %>
<p><%= ob.noResultsMsg %></p>
<% } %>
4 changes: 2 additions & 2 deletions js/templates/userPage/userPage.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
<% if (ob.vendor || ob.ownPage) { %> <% // the store tab is only visible to the user if they have vendor set to false %>
<a class="btn tab clrBr js-tab" data-tab="store">Store<span class="clrTEmph1 margLSm js-listingsCount"><%= ob.stats.listingCount %></span></a>
<% } %>
<a class="btn tab clrBr js-tab" data-tab="following">Following<span class="clrTEmph1 margLSm js-followingCount"><%= ob.stats.followingCount %></span></a>
<a class="btn tab clrBr js-tab" data-tab="followers">Followers<span class="clrTEmph1 margLSm js-followerCount"><%= ob.stats.followerCount %></span></a>
<a class="btn tab clrBr js-tab" data-tab="following">Following<span class="clrTEmph1 margLSm js-followingCount"><%= ob.followingCount %></span></a>
<a class="btn tab clrBr js-tab" data-tab="followers">Followers<span class="clrTEmph1 margLSm js-followerCount"><%= ob.followerCount %></span></a>
</div>
</div>
</div>
Expand Down
19 changes: 14 additions & 5 deletions js/utils/follow.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import $ from 'jquery';
import { capitalize } from '../utils/string';
import app from '../app';
import Dialog from '../views/modals/Dialog';

Expand All @@ -19,6 +20,11 @@ export function followUnfollow(guid, type = 'follow') {
throw new Error('You must provide a valid guid.');
}

const types = ['follow', 'unfollow'];
if (types.indexOf(type) === -1) {
throw new Error(`type must be one of ${types.join(', ')}`);
}

if (guid === app.profile.id) {
throw new Error('You can not follow or unfollow your own guid');
}
Expand All @@ -32,15 +38,18 @@ export function followUnfollow(guid, type = 'follow') {
.done(() => {
// if the call succeeds, add or remove the guid from the collection
if (type === 'follow') {
app.ownFollowing.unshift({ guid });
app.ownFollowing.unshift({ peerId: guid });
} else {
app.ownFollowing.remove(guid); // remove via id
app.ownFollowing.remove(guid);
}
})
.fail((data) => {
.fail(data => {
new Dialog({
title: app.polyglot.t('errors.badResult'),
message: data.responseJSON.reason,
title: app.polyglot.t('follow.followErrorTitle', {
type: app.polyglot.t(`follow.type${capitalize(type)}`),
user: guid,
}),
message: data.responseJSON && data.responseJSON.reason || '',
})
.render()
.open();
Expand Down
33 changes: 15 additions & 18 deletions js/views/UserCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import BaseVw from './baseVw';
import loadTemplate from '../utils/loadTemplate';
import app from '../app';
import { followedByYou, followUnfollow } from '../utils/follow';
import Profile from '../models/profile/Profile';
import UserCard from '../models/UserCard';
import Profile, { getCachedProfiles } from '../models/profile/Profile';
import { launchModeratorDetailsModal } from '../utils/modalManager';
import { openSimpleMessage } from './modals/SimpleMessage';

Expand All @@ -17,11 +16,7 @@ export default class extends BaseVw {
if (this.model instanceof Profile) {
this.guid = this.model.id;
this.fetched = true;
} else if (this.model) {
this.guid = this.model.get('guid');
this.fetched = false;
} else {
this.model = new UserCard({ guid: options.guid });
this.guid = options.guid;
this.fetched = false;
}
Expand Down Expand Up @@ -49,10 +44,14 @@ export default class extends BaseVw {
this.$modBtn.attr('data-tip', this.getModTip());
});

this.listenTo(app.ownFollowing, 'sync update', () => {
this.followedByYou = followedByYou(this.guid);
this.$followBtn.toggleClass('active', this.followedByYou);
this.$followBtn.attr('data-tip', this.getFollowTip());
this.listenTo(app.ownFollowing, 'update', (cl, updateOpts) => {
const updatedModels = updateOpts.changes.added.concat(updateOpts.changes.removed);

if (updatedModels.filter(md => md.id === this.guid).length) {
this.followedByYou = followedByYou(this.guid);
this.$followBtn.toggleClass('active', this.followedByYou);
this.$followBtn.attr('data-tip', this.getFollowTip());
}
});
}

Expand All @@ -73,19 +72,16 @@ export default class extends BaseVw {
}

loadUser(guid = this.guid) {
let profile;
this.fetched = true;

if (guid === app.profile.id) {
// don't fetch our this user's own profile, since we have it already
this.profileFetch = $.Deferred().resolve();
profile = app.profile;
// don't fetch this user's own profile, since we have it already
this.profileFetch = $.Deferred().resolve(app.profile);
} else {
profile = new Profile({ peerID: guid });
this.profileFetch = profile.fetch();
this.profileFetch = getCachedProfiles([guid])[0];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to use the cached profiles after all. The latency improvement is substantial. And... even having the freshest moderator info doesn't really solve the issue. After a mod is added, whether it's 1 minute, 1 hour or 6 months later, that mod can change their terms and the users using that mod would have no idea. Perhaps it's as simple as sending a notification to users of a mod if the mod changes their terms? Seems like a bigger UX discussion we could circle back to.

}

this.profileFetch.done(() => {
this.profileFetch.done(profile => {
if (this.isRemoved()) return;
this.loading = false;
this.notFound = false;
Expand Down Expand Up @@ -197,7 +193,7 @@ export default class extends BaseVw {
getModTip: this.getModTip,
getFollowTip: this.getFollowTip,
...this.options,
...this.model.toJSON(),
...(this.model && this.model.toJSON() || {}),
}));

this._$followBtn = null;
Expand All @@ -213,5 +209,6 @@ export default class extends BaseVw {

remove() {
if (this.profileFetch && this.profileFetch.abort) this.profileFetch.abort();
super.remove();
}
}
2 changes: 1 addition & 1 deletion js/views/modals/BaseModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default class BaseModal extends baseVw {
dismissOnOverlayClick: false,
dismissOnEscPress: true,
showCloseButton: true,
closeButtonClass: 'cornerTR iconBtn clrP clrBr clrSh3 toolTipNoWrap',
closeButtonClass: 'cornerTR iconBtn clrP clrBr clrSh3 toolTipNoWrap modalCloseBtn',
innerButtonClass: 'ion-ios-close-empty',
closeButtonTip: app.polyglot.t('pageNav.toolTip.close'),
modelContentClass: 'modalContent',
Expand Down
Loading