1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-30 16:18:31 +00:00

Clean up featured follow. Try to make the UX a bit nicer. Localize strings that weren't being localized. Add error handling.

This commit is contained in:
SirStendec 2018-04-08 15:00:16 -04:00
parent 8c5b6910ed
commit c5564150b1
3 changed files with 151 additions and 106 deletions

View file

@ -25,40 +25,48 @@
<a :href="'/' + user.login" :title="user.login" @click.prevent="route(user.login)"><p class="tw-ellipsis tw-flex-grow-1 tw-mg-l-1 tw-font-size-5">{{ user.displayName }}</p></a> <a :href="'/' + user.login" :title="user.login" @click.prevent="route(user.login)"><p class="tw-ellipsis tw-flex-grow-1 tw-mg-l-1 tw-font-size-5">{{ user.displayName }}</p></a>
<div class="tw-flex-grow-1 tw-pd-x-2"/> <div class="tw-flex-grow-1 tw-pd-x-2"/>
<button <div v-if="user.error">
v-if="user.following" {{ t('featured-follow.error', 'An error occured.') }}
:data-title="`Unfollow ${user.login}`" </div>
data-tooltip-type="html" <template v-else>
class="tw-button tw-button--status tw-button--success ffz-tooltip ffz--featured-button-unfollow" <button
@click="unfollowUser(user.id)" v-if="user.following"
> :disabled="user.loading"
<span class="tw-button__icon tw-button__icon--status tw-flex"> :data-title="user.loading ? null : t('featured-follow.button.unfollow', 'Unfollow %{user}', {user: user.displayName})"
<figure class="ffz-i-heart ffz--featured-button-unfollow-button"/> data-tooltip-type="html"
</span> class="tw-button tw-button--status tw-button--success ffz-tooltip ffz--featured-button-unfollow"
</button> @click="clickWithTip($event, unfollowUser, user.id)"
<button >
v-if="user.following" <span class="tw-button__icon tw-button__icon--status tw-flex">
:data-title="`${(user.disableNotifications ? 'Enable' : 'Disable')} Notifications`" <figure class="ffz-i-heart ffz--featured-button-unfollow-button"/>
data-tooltip-type="html" </span>
class="tw-button-icon tw-mg-l-05 ffz-tooltip ffz--featured-button-notification" </button>
@click="updateNotificationStatus(user.id, user.disableNotifications)" <button
> v-if="user.following"
<span class="tw-button__icon tw-flex"> :disabled="user.loading"
<figure :class="{ 'ffz-i-bell': !user.disableNotifications, 'ffz-i-bell-off': user.disableNotifications }"/> :data-title="notifyTip(user.disableNotifications)"
</span> data-tooltip-type="html"
</button> class="tw-button-icon tw-mg-l-05 ffz-tooltip ffz--featured-button-notification"
<button @click="clickWithTip($event, updateNotificationStatus, user.id, user.disableNotifications)"
v-else >
class="tw-button" <span class="tw-button__icon tw-flex">
@click="followUser(user.id)" <figure :class="{ 'ffz-i-bell': !user.disableNotifications, 'ffz-i-bell-off': user.disableNotifications }"/>
> </span>
<span class="tw-button__icon tw-button__icon--left"> </button>
<figure class="ffz-i-heart"/> <button
</span> v-else
<span class="tw-button__text"> :disabled="user.loading"
{{ t('featured-follow.button.follow', 'Follow') }} class="tw-button"
</span> @click="followUser(user.id)"
</button> >
<span class="tw-button__icon tw-button__icon--left">
<figure class="ffz-i-heart"/>
</span>
<span class="tw-button__text">
{{ t('featured-follow.button.follow', 'Follow') }}
</span>
</button>
</template>
</div> </div>
</div> </div>
</div> </div>
@ -72,6 +80,24 @@
export default { export default {
data() { data() {
return this.$vnode.data; return this.$vnode.data;
},
methods: {
clickWithTip(event, fn, ...args) {
const el = event.target,
tip = el && el._ffz_tooltip$0,
visible = tip && tip.visible;
visible && tip.hide();
fn.call(this, ...args);
visible && setTimeout(() => document.contains(el) && tip.show(), 0)
},
notifyTip(state) {
return state ?
this.t('featured-follow.notify.off', 'Notifications are currently disabled. Click to enable.') :
this.t('featured-follow.notify.on', 'Notifications are currently enabled. Click to disable.');
}
} }
} }
</script> </script>

View file

@ -5,14 +5,14 @@
// ============================================================================ // ============================================================================
import Module from 'utilities/module'; import Module from 'utilities/module';
import {createElement as e} from 'utilities/dom'; import {createElement} from 'utilities/dom';
import {has} from 'utilities/object';
import FEATURED_QUERY from './featured_follow_query.gql'; import FEATURED_QUERY from './featured_follow_query.gql';
import FEATURED_FOLLOW from './featured_follow_follow.gql'; import FEATURED_FOLLOW from './featured_follow_follow.gql';
import FEATURED_UNFOLLOW from './featured_follow_unfollow.gql'; import FEATURED_UNFOLLOW from './featured_follow_unfollow.gql';
const TWITCH_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/([A-Za-z0-9_]+)/i;
export default class FeaturedFollow extends Module { export default class FeaturedFollow extends Module {
constructor(...args) { constructor(...args) {
@ -84,31 +84,37 @@ export default class FeaturedFollow extends Module {
this.follow_data = {}; this.follow_data = {};
this.socket.on(':command:follow_buttons', data => { this.socket.on(':command:follow_buttons', data => {
for(const channel_login in data) { for(const channel_login in data)
if (!data.hasOwnProperty(channel_login)) continue; if ( has(data, channel_login) )
this.follow_data[channel_login] = data[channel_login];
this.follow_data[channel_login] = data[channel_login]; if ( this.vueFeaturedFollow )
}
if (this.vueFeaturedFollow) {
this.vueFeaturedFollow.data.hasUpdate = true; this.vueFeaturedFollow.data.hasUpdate = true;
}
this.metadata.updateMetadata('following'); this.metadata.updateMetadata('following');
}); });
// ffz.resolve('site.featured_follow').updateFeaturedChannels({ login: 'lordmau5' }, ['sirstendec','jugachi']);
} }
async getFollowsForLogin(login) { async getFollowsForLogin(login) {
const follow_data = this.follow_data && this.follow_data[login]; const follow_data = this.follow_data && this.follow_data[login];
if (!follow_data || follow_data.length === 0) return []; if ( ! follow_data || ! follow_data.length )
return [];
const ap_data = await this.apollo.client.query({ query: FEATURED_QUERY, variables: { logins: follow_data }}); const ap_data = await this.apollo.client.query({
query: FEATURED_QUERY,
variables: {
logins: follow_data
}
}),
follows = {};
const follows = {};
for (const user of ap_data.data.users) { for (const user of ap_data.data.users) {
if ( ! user.id )
continue;
follows[user.id] = { follows[user.id] = {
loading: false,
error: false,
id: user.id, id: user.id,
login: user.login, login: user.login,
displayName: user.displayName, displayName: user.displayName,
@ -117,12 +123,13 @@ export default class FeaturedFollow extends Module {
disableNotifications: user.self.follower.disableNotifications disableNotifications: user.self.follower.disableNotifications
}; };
} }
return follows; return follows;
} }
buildFeaturedFollowMenu(vue, login, follows) { buildFeaturedFollowMenu(vue, login, follows) {
const vueEl = new vue.Vue({ const vueEl = new vue.Vue({
el: e('div'), el: createElement('div'),
render: h => this.vueFeaturedFollow = h('featured-follow', { render: h => this.vueFeaturedFollow = h('featured-follow', {
login, login,
follows, follows,
@ -147,67 +154,82 @@ export default class FeaturedFollow extends Module {
return vueEl.$el; return vueEl.$el;
} }
updateFeaturedChannels(room, args) {
args = args.join(' ').trim().toLowerCase().split(/[ ,]+/);
const out = [];
for (let i = 0; i < args.length; i++) {
let arg = args[i];
const match = arg.match(TWITCH_URL);
if (match)
arg = match[1];
if (arg !== '' && out.indexOf(arg) === -1)
out.push(arg);
}
this.socket.call('update_follow_buttons', room.login, out)
.then(() => {
// this.log.info('Success!', data);
})
.catch(() => 'There was an error communicating with the server.');
// , (success, data) => {
// if (!success) {
// this.log.warn('Not a Success: ', data);
// // f.room_message(room, data);
// return;
// }
// this.log.info('Success!', data);
// // this.room.message(`The following buttons have been ${data ? 'updated' : 'disabled'}.`);
// }) )
}
async followUser(follows, id) { async followUser(follows, id) {
const ap_data = await this.apollo.client.mutate({ mutation: FEATURED_FOLLOW, variables: { targetID: id, disableNotifications: false }}); const f = follows[id];
f.loading = true;
const follow = ap_data.data.followUser.follow;
try {
follows[id].following = follow.followedAt != null; const ap_data = await this.apollo.client.mutate({
follows[id].disableNotifications = follow.disableNotifications; mutation: FEATURED_FOLLOW,
variables: {
targetID: id,
disableNotifications: false
}
});
const update = ap_data.data.followUser.follow;
f.loading = false;
f.following = update.followedAt != null;
f.disableNotifications = update.disableNotifications;
} catch(err) {
this.log.warn('There was a problem following.', err);
f.error = true;
}
} }
async updateNotificationStatus(follows, id, oldStatus) { async updateNotificationStatus(follows, id, oldStatus) {
const ap_data = await this.apollo.client.mutate({ mutation: FEATURED_FOLLOW, variables: { targetID: id, disableNotifications: !oldStatus }}); const f = follows[id];
f.loading = true;
const follow = ap_data.data.followUser.follow;
// Immediate Feedback
follows[id].following = follow.followedAt != null; f.disableNotifications = ! oldStatus;
follows[id].disableNotifications = follow.disableNotifications;
try {
const ap_data = await this.apollo.client.mutate({
mutation: FEATURED_FOLLOW,
variables: {
targetID: id,
disableNotifications: !oldStatus
}
});
const update = ap_data.data.followUser.follow;
f.loading = false;
f.following = update.followedAt != null;
f.disableNotifications = update.disableNotifications;
} catch(err) {
this.log.warn('There was a problem updating notification status.', err);
f.error = true;
}
} }
async unfollowUser(follows, id) { async unfollowUser(follows, id) {
await this.apollo.client.mutate({ mutation: FEATURED_UNFOLLOW, variables: { targetID: id }}); const f = follows[id];
f.loading = true;
follows[id].following = false;
follows[id].disableNotifications = false; try {
await this.apollo.client.mutate({
mutation: FEATURED_UNFOLLOW,
variables: {
targetID: id
}
});
f.loading = false;
f.following = false;
f.disableNotifications = false;
} catch(err) {
this.log.warn('There was a problem unfollowing.', err);
f.error = true;
}
} }
route(channel) { route(channel) {
this.router.navigate('user', { userName: channel }); this.router.navigate('user', { userName: channel });
} }
} }

View file

@ -4,15 +4,12 @@
} }
.ffz--featured-button-unfollow { .ffz--featured-button-unfollow {
&:hover &:before { &:hover figure:before {
content: '\e813'; content: '\e813';
} }
> * {
pointer-events: none;
}
} }
.ffz--featured-button-unfollow,
.ffz--featured-button-notification { .ffz--featured-button-notification {
> * { > * {
pointer-events: none; pointer-events: none;