1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-09-15 17:46:55 +00:00

Basic mod cards support (#423)

* More work on mod cards

* More work on mod cards!

Some sort of dynamic component thingy madoohickey

* Change up implementation of tabs

* Implement focus and tabindex

* Remove unused GQL queries / mutations

* Implement user info

* Only show use rlogin if an international name was detected

Also attempt to fix line height

* Remove testing memes

* Remove derps... whoops
This commit is contained in:
Lordmau5 2018-04-30 20:27:14 +02:00 committed by Mike
parent c548f15290
commit 9ef7c2aee3
10 changed files with 411 additions and 3 deletions

View file

@ -17,6 +17,7 @@ import ChatLine from './line';
import SettingsMenu from './settings_menu';
import EmoteMenu from './emote_menu';
import TabCompletion from './tab_completion';
import ModCards from './mod_cards';
const MESSAGE_TYPES = ((e = {}) => {
@ -119,6 +120,8 @@ export default class ChatHook extends Module {
this.inject(SettingsMenu);
this.inject(EmoteMenu);
this.inject(TabCompletion);
this.inject(ModCards);
this.ChatController = this.fine.define(

View file

@ -25,6 +25,7 @@ export default class ChatLine extends Module {
this.inject('site');
this.inject('site.fine');
this.inject('site.web_munch');
this.inject('site.apollo');
this.inject(RichContent);
this.inject('chat.actions');
@ -138,7 +139,7 @@ export default class ChatLine extends Module {
e('a', {
className: 'chat-author__display-name notranslate',
style: { color },
onClick: this.usernameClickHandler
onClick: t.parent.mod_cards.openCustomModCard.bind(t.parent.mod_cards, this, user)
}, [
user.userDisplayName,
user.isIntl && e('span', {

View file

@ -0,0 +1,63 @@
<template>
<section class="tw-background-c tw-relative">
<div class="tw-c-background tw-full-width tw-flex tw-flex-row tw-pd-r-05 tw-pd-l-1 tw-pd-y-1">
<div class="tw-mg-r-05">
<div class="tw-inline-block">
<button class="tw-button">
<span class="tw-button__text" data-a-target="tw-button-text">Add Friend</span>
</button>
</div>
</div>
<div class="tw-mg-r-05">
<div class="tw-inline-block">
<button class="tw-button" data-a-target="usercard-whisper-button" data-test-selector="whisper-button">
<span class="tw-button__text" data-a-target="tw-button-text">Whisper</span>
</button>
</div>
</div>
<div class="tw-flex-grow-1 tw-align-right">
<div class="tw-inline-block">
<button data-title="More Options" data-tooltip-type="html" class="tw-button-icon ffz-tooltip" @click="close">
<span class="tw-button-icon__icon">
<figure class="ffz-i-ellipsis-vert" />
</span>
</button>
</div>
</div>
</div>
<div class="tw-c-background-alt-2 tw-pd-x-1 tw-pd-y-05">
<div>
<div class="tw-inline-block tw-pd-r-1">
<button data-title="Ban User" data-tooltip-type="html" class="tw-button-icon ffz-tooltip" @click="close">
<span class="tw-button-icon__icon">
<figure class="ffz-i-block" />
</span>
</button>
</div>
<div class="tw-inline-block tw-pd-r-1">
<button data-title="Timeout User" data-tooltip-type="html" class="tw-button-icon ffz-tooltip" @click="close">
<span class="tw-button-icon__icon">
<figure class="ffz-i-clock" />
</span>
</button>
</div>
<div class="tw-inline-block tw-pd-r-1">
<button data-title="Mod User" data-tooltip-type="html" class="tw-button-icon ffz-tooltip" @click="close">
<span class="tw-button-icon__icon">
<figure class="ffz-i-star" />
</span>
</button>
</div>
</div>
</div>
</section>
</template>
<script>
import TabMixin from '../tab-mixin';
export default {
mixins: [TabMixin],
props: ['tab', 'user', 'room', 'currentUser']
}
</script>

View file

@ -0,0 +1,25 @@
query($userLogin: String) {
user(login: $userLogin) {
bannerImageURL
displayName
id
login
profileImageURL(width: 50)
createdAt
followers {
totalCount
}
profileViewCount
self {
friendship {
... on FriendEdge {
node {
displayName
id
login
}
}
}
}
}
}

View file

@ -0,0 +1,117 @@
'use strict';
// ============================================================================
// Mod Cards Component
// ============================================================================
import Module from 'utilities/module';
import {createElement} from 'utilities/dom';
import GET_USER_INFO from './get_user_info.gql';
export default class ModCards extends Module {
constructor(...args) {
super(...args);
this.inject('site.apollo');
this.inject('i18n');
this.lastZIndex = 9001;
this.open_mod_cards = {};
this.tabs = {};
this.addTab('main', {
visible: () => true,
label: 'Main',
pill: 0,
data: (user, room) => ({
}),
component: () => import('./components/main.vue')
});
}
addTab(key, data) {
if (this.tabs[key]) return;
this.tabs[key] = data;
}
async openCustomModCard(t, user, e) {
// Old mod-card
// t.usernameClickHandler(e);
const posX = Math.min(window.innerWidth - 300, e.clientX),
posY = Math.min(window.innerHeight - 300, e.clientY),
room = {
id: t.props.channelID,
login: t.props.message.roomLogin
},
currentUser = {
isModerator: t.props.isCurrentUserModerator,
isStaff: t.props.isCurrentUserStaff
};
if (this.open_mod_cards[user.userLogin]) {
this.open_mod_cards[user.userLogin].style.zIndex = ++this.lastZIndex;
return;
}
const vue = this.resolve('vue'),
_mod_card_vue = import(/* webpackChunkName: "mod-card" */ './mod-card.vue'),
_user_info = this.apollo.client.query({
query: GET_USER_INFO,
variables: {
userLogin: user.userLogin
}
});
const [, mod_card_vue, user_info] = await Promise.all([vue.enable(), _mod_card_vue, _user_info]);
vue.component('mod-card', mod_card_vue.default);
const mod_card = this.open_mod_cards[user.userLogin] = this.buildModCard(vue, user_info.data.user, room, currentUser);
const main = document.querySelector('.twilight-root>.tw-full-height');
main.appendChild(mod_card);
mod_card.style.left = `${posX}px`;
mod_card.style.top = `${posY}px`;
}
buildModCard(vue, user, room, currentUser) {
this.log.info(user);
const vueEl = new vue.Vue({
el: createElement('div'),
render: h => {
const vueModCard = h('mod-card', {
activeTab: Object.keys(this.tabs)[0],
tabs: this.tabs,
user,
room,
currentUser,
rawUserAge: this.i18n.toLocaleString(new Date(user.createdAt)),
userAge: this.i18n.toHumanTime((new Date() - new Date(user.createdAt)) / 1000),
setActiveTab: tab => vueModCard.data.activeTab = tab,
focus: el => {
el.style.zIndex = ++this.lastZIndex;
},
close: () => {
this.open_mod_cards[user.login].remove();
this.open_mod_cards[user.login] = null;
}
});
return vueModCard;
}
});
return vueEl.$el;
}
}

View file

@ -0,0 +1,139 @@
<template>
<div
class="ffz-mod-card tw-elevation-3 tw-c-background-alt tw-c-text tw-border tw-flex tw-flex-nowrap tw-flex-column"
tabindex="-1"
@focusin="doFocus"
>
<header
:style="`background-image: url('${user.bannerImageURL}');`"
class="tw-full-width tw-align-items-center tw-flex tw-flex-nowrap tw-relative"
>
<div class="tw-full-width tw-align-items-center tw-flex tw-flex-nowrap tw-pd-1 ffz--background-dimmer">
<div class="tw-inline-block">
<figure class="tw-avatar tw-avatar--size-50">
<div class="tw-overflow-hidden ">
<img
:src="user.profileImageURL"
class="tw-image"
>
</div>
</figure>
</div>
<div class="tw-ellipsis tw-inline-block">
<div class="tw-align-items-center tw-mg-l-1 ffz--info-lines">
<h4>
<a :href="`/${user.login}`" class="tw-link tw-link--hover-underline-none tw-link--inherit" target="_blank">
{{ user.displayName }}
</a>
</h4>
<h5
v-if="user.displayName && user.displayName.toLowerCase() !== user.login.toLowerCase()"
>
<a :href="`/${user.login}`" class="tw-link tw-link--hover-underline-none tw-link--inherit" target="_blank">
{{ user.login }}
</a>
</h5>
<div>
<span class="tw-mg-r-05">
<figure class="ffz-i-info tw-inline"/>
{{ user.profileViewCount }}
</span>
<span class="tw-mg-r-05">
<figure class="ffz-i-heart tw-inline"/>
{{ user.followers.totalCount }}
</span>
<span
:data-title="rawUserAge"
data-tooltip-type="html"
class="ffz-tooltip"
>
<figure class="ffz-i-clock tw-inline"/>
{{ userAge }}
</span>
</div>
</div>
</div>
<div class="tw-flex-grow-1 tw-pd-x-2"/>
<div class="tw-inline-block">
<button class="tw-button-icon tw-absolute tw-right-0 tw-top-0 tw-mg-t-05 tw-mg-r-05" @click="close">
<span class="tw-button-icon__icon">
<figure class="ffz-i-cancel" />
</span>
</button>
<button class="tw-button-icon tw-absolute tw-right-0 tw-bottom-0 tw-mg-b-05 tw-mg-r-05" @click="close">
<span class="tw-button-icon__icon">
<figure class="ffz-i-ignore" />
</span>
</button>
</div>
</div>
</header>
<section class="tw-background-c">
<div class="mod-cards__tabs-container tw-border-t">
<div
v-for="(data, key) in tabs"
:key="key"
:id="`mod-cards__${key}`"
:class="{active: activeTab === key}"
class="mod-cards__tab tw-pd-x-1"
@click="setActiveTab(key)"
>
<span>{{ data.label }}</span>
</div>
</div>
</section>
<component
v-for="(tab, key) in tabs"
v-if="tab.visible && activeTab === key"
:is="tab.component"
:tab="tab"
:user="user"
:room="room"
:current-user="currentUser"
:key="key"
@close="close"
/>
</div>
</template>
<script>
import displace from 'displacejs';
export default {
data() {
return this.$vnode.data;
},
mounted() {
this.createDrag();
},
beforeDestroy() {
this.destroyDrag();
},
methods: {
destroyDrag() {
if ( this.displace ) {
this.displace.destroy();
this.displace = null;
}
},
createDrag() {
this.$nextTick(() => {
this.displace = displace(this.$el, {
handle: this.$el.querySelector('header'),
highlightInputs: true,
constrain: true
});
})
},
doFocus() {
this.focus(this.$el);
}
}
}
</script>

View file

@ -0,0 +1,19 @@
'use strict';
export default {
computed: {
data() {
const data = this.tab.data;
if ( typeof data === 'function' )
return data.call(this, this.user, this.room, this.currentUser);
return data;
}
},
methods: {
close() {
this.$emit('close');
}
}
}

View file

@ -357,4 +357,4 @@ export default class Following extends SiteModule {
}
}
}
}
}

View file

@ -11,4 +11,5 @@
@import 'fixes';
@import 'host_options';
@import 'featured_follow';
@import 'featured_follow';
@import 'mod_card';

View file

@ -0,0 +1,40 @@
.ffz-mod-card {
width: 340px;
z-index: 9001;
> header {
background-size: cover;
background-position: top;
}
.ffz-tooltip {
> * {
pointer-events: none;
}
}
.ffz--background-dimmer {
background-color: rgba(0, 0, 0, 0.8);
}
.ffz--info-lines > * {
line-height: 1.2;
}
.mod-cards__tabs-container {
height: 3rem;
> .mod-cards__tab {
position: relative;
top: -.1rem;
cursor: pointer;
display: inline-block;
line-height: 3rem;
margin-right: .5rem;
&:hover, &.active {
border-top: 1px solid #6441a4;
}
}
}
}