mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 15:27:43 +00:00
Add support for clickable stream metadata. Add helper utility for detecting clicks outside of elements. Add support for manually shown/hidden tooltips, which is used for custom pop-ups.
This commit is contained in:
parent
dfa0c9c88f
commit
e224800fb9
7 changed files with 241 additions and 36 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
<div class="list-header">4.0.0-beta1.4<span>@8e759e6ddfa7aa70cfea</span> <time datetime="2017-12-01">(2017-12-01)</time></div>
|
||||||
|
<ul class="chat-menu-content menu-side-padding">
|
||||||
|
<li>Fixed: Message highlighting in chat.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div class="list-header">4.0.0-beta1.4<span>@eb51eeb2dadafdea2bde</span> <time datetime="2017-12-01">(2017-12-01)</time></div>
|
<div class="list-header">4.0.0-beta1.4<span>@eb51eeb2dadafdea2bde</span> <time datetime="2017-12-01">(2017-12-01)</time></div>
|
||||||
<ul class="chat-menu-content menu-side-padding">
|
<ul class="chat-menu-content menu-side-padding">
|
||||||
<li>Added: Block games and hide thumbnails in the directory.</li>
|
<li>Added: Block games and hide thumbnails in the directory.</li>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// Channel Metadata
|
// Channel Metadata
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
import {createElement as e} from 'utilities/dom';
|
import {createElement as e, ClickOutside} from 'utilities/dom';
|
||||||
import {has, get, maybe_call} from 'utilities/object';
|
import {has, get, maybe_call} from 'utilities/object';
|
||||||
|
|
||||||
import {duration_to_string} from 'utilities/time';
|
import {duration_to_string} from 'utilities/time';
|
||||||
|
@ -273,23 +273,117 @@ export default class Metadata extends Module {
|
||||||
|
|
||||||
const tooltip = maybe_call(def.tooltip, this, data),
|
const tooltip = maybe_call(def.tooltip, this, data),
|
||||||
order = maybe_call(def.order, this, data),
|
order = maybe_call(def.order, this, data),
|
||||||
color = maybe_call(def.color, this, data);
|
color = maybe_call(def.color, this, data) || '';
|
||||||
|
|
||||||
if ( ! el ) {
|
if ( ! el ) {
|
||||||
let icon = maybe_call(def.icon, this, data);
|
let icon = maybe_call(def.icon, this, data);
|
||||||
if ( typeof icon === 'string' )
|
|
||||||
icon = e('span', 'tw-stat__icon', e('figure', icon));
|
|
||||||
|
|
||||||
el = e('div', {
|
if ( def.popup || def.click ) {
|
||||||
className: 'ffz-stat tw-stat',
|
let btn, popup;
|
||||||
'data-key': key,
|
let cls = maybe_call(def.button, this, data);
|
||||||
tip_content: tooltip
|
if ( typeof cls !== 'string' )
|
||||||
}, [
|
cls = `tw-button--${cls ? 'hollow' : 'text'}`;
|
||||||
icon,
|
|
||||||
stat = e('span', 'tw-stat__value')
|
const fix = cls === 'tw-button--text';
|
||||||
]);
|
|
||||||
|
if ( typeof icon === 'string' )
|
||||||
|
icon = e('span', 'tw-button__icon tw-button__icon--left', e('figure', icon));
|
||||||
|
|
||||||
|
if ( def.popup && def.click ) {
|
||||||
|
el = e('span', {
|
||||||
|
className: `ffz-stat${fix ? ' ffz-fix-padding--left' : ''}`,
|
||||||
|
'data-key': key,
|
||||||
|
tip_content: tooltip
|
||||||
|
}, [
|
||||||
|
btn = e('button', `tw-button ${cls}`, [
|
||||||
|
icon,
|
||||||
|
stat = e('span', 'ffz-stat-text tw-button__text')
|
||||||
|
]),
|
||||||
|
popup = e('button', `tw-button ${cls} ffz-stat-arrow`,
|
||||||
|
e('span', 'tw-button__icon pd-x-0',
|
||||||
|
e('figure', 'ffz-i-down-dir')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
|
||||||
|
} else
|
||||||
|
btn = popup = el = e('button', {
|
||||||
|
className: `ffz-stat${fix ? ' ffz-fix-padding' : ''} tw-button ${cls}`,
|
||||||
|
'data-key': key,
|
||||||
|
tip_content: tooltip
|
||||||
|
}, [
|
||||||
|
icon,
|
||||||
|
stat = e('span', 'ffz-stat-text tw-button__text'),
|
||||||
|
def.popup && e('span', 'tw-button__icon tw-button__icon--right', e('figure', 'ffz-i-down-dir'))
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ( def.click )
|
||||||
|
btn.addEventListener('click', e => {
|
||||||
|
if ( btn.disabled || btn.classList.contains('disabled') || el.disabled || el.classList.contains('disabled') )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
def.click.call(this, data, e, () => refresh_fn(key));
|
||||||
|
});
|
||||||
|
|
||||||
|
if ( def.popup )
|
||||||
|
popup.addEventListener('click', e => {
|
||||||
|
if ( popup.disabled || popup.classList.contains('disabled') || el.disabled || el.classList.contains('disabled') )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( el._ffz_popup )
|
||||||
|
return el._ffz_destroy();
|
||||||
|
|
||||||
|
const destroy = el._ffz_destroy = () => {
|
||||||
|
if ( el._ffz_outside )
|
||||||
|
el._ffz_outside.destroy();
|
||||||
|
|
||||||
|
if ( el._ffz_popup ) {
|
||||||
|
const fp = el._ffz_popup;
|
||||||
|
el._ffz_popup = null;
|
||||||
|
fp.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
el._ffz_destroy = el._ffz_outside = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const tt = el._ffz_popup = new Tooltip(document.body, el, {
|
||||||
|
manual: true,
|
||||||
|
html: true,
|
||||||
|
|
||||||
|
tooltipClass: 'ffz-metadata-balloon tw-balloon block',
|
||||||
|
arrowClass: 'tw-balloon__tail',
|
||||||
|
innerClass: 'pd-1',
|
||||||
|
|
||||||
|
popper: {
|
||||||
|
placement: 'top-end'
|
||||||
|
},
|
||||||
|
content: (t, tip) => def.popup.call(this, data, tip, () => refresh_fn(key)),
|
||||||
|
onShow: (t, tip) =>
|
||||||
|
setTimeout(() => {
|
||||||
|
el._ffz_outside = new ClickOutside(tip.outer, destroy);
|
||||||
|
}),
|
||||||
|
onHide: destroy
|
||||||
|
})
|
||||||
|
|
||||||
|
tt._enter(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if ( typeof icon === 'string' )
|
||||||
|
icon = e('span', 'tw-stat__icon', e('figure', icon));
|
||||||
|
|
||||||
|
el = e('div', {
|
||||||
|
className: 'ffz-stat tw-stat',
|
||||||
|
'data-key': key,
|
||||||
|
tip_content: tooltip
|
||||||
|
}, [
|
||||||
|
icon,
|
||||||
|
stat = e('span', 'ffz-stat-text tw-stat__value')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
el._ffz_order = order;
|
el._ffz_order = order;
|
||||||
|
el._ffz_data = data;
|
||||||
|
|
||||||
if ( order != null )
|
if ( order != null )
|
||||||
el.style.order = order;
|
el.style.order = order;
|
||||||
|
@ -306,7 +400,7 @@ export default class Metadata extends Module {
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
stat = el.querySelector('.tw-stat__value');
|
stat = el.querySelector('.ffz-stat-text');
|
||||||
old_color = el.dataset.color || '';
|
old_color = el.dataset.color || '';
|
||||||
|
|
||||||
if ( el._ffz_order !== order )
|
if ( el._ffz_order !== order )
|
||||||
|
|
|
@ -6,6 +6,15 @@
|
||||||
order: 50;
|
order: 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz-fix-padding {
|
||||||
|
margin-left: -.8rem !important;
|
||||||
|
margin-right: .2rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-fix-padding--left {
|
||||||
|
margin-left: -.8rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
.c-text-live {
|
.c-text-live {
|
||||||
order: 1;
|
order: 1;
|
||||||
}
|
}
|
||||||
|
@ -19,3 +28,14 @@
|
||||||
margin-right: 0 !important
|
margin-right: 0 !important
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.theme--ffz, .theme--ffz.theme--dark, .theme--dark, body {
|
||||||
|
.ffz-stat > .tw-button,
|
||||||
|
.ffz-stat.tw-button {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-stat-arrow {
|
||||||
|
border-left: none !important;
|
||||||
|
}
|
|
@ -147,3 +147,25 @@ export class ManagedStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class ClickOutside {
|
||||||
|
constructor(element, callback) {
|
||||||
|
this.el = element;
|
||||||
|
this.cb = callback;
|
||||||
|
this._fn = this.handleClick.bind(this);
|
||||||
|
document.documentElement.addEventListener('click', this._fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if ( this._fn )
|
||||||
|
document.documentElement.removeEventListener('click', this._fn);
|
||||||
|
|
||||||
|
this.cb = this.el = this._fn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick(e) {
|
||||||
|
if ( ! this.el.contains(e.target) )
|
||||||
|
this.cb(e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,7 +67,10 @@ export class Tooltip {
|
||||||
|
|
||||||
this._onMouseOut = e => this._exit(e.target);
|
this._onMouseOut = e => this._exit(e.target);
|
||||||
|
|
||||||
if ( this.live ) {
|
if ( this.options.manual ) {
|
||||||
|
// Do nothing~!
|
||||||
|
|
||||||
|
} else if ( this.live ) {
|
||||||
this._onMouseOver = e => {
|
this._onMouseOver = e => {
|
||||||
const target = e.target;
|
const target = e.target;
|
||||||
if ( target.classList.contains(this.cls) )
|
if ( target.classList.contains(this.cls) )
|
||||||
|
@ -99,7 +102,9 @@ export class Tooltip {
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if ( this.live || this.elements.size > 5 ) {
|
if ( this.options.manual ) {
|
||||||
|
// Do nothing~!
|
||||||
|
} else if ( this.live || this.elements.size > 5 ) {
|
||||||
parent.removeEventListener('mouseover', this._onMouseOver);
|
parent.removeEventListener('mouseover', this._onMouseOver);
|
||||||
parent.removeEventListener('mouseout', this._onMouseOut);
|
parent.removeEventListener('mouseout', this._onMouseOut);
|
||||||
} else
|
} else
|
||||||
|
@ -215,17 +220,19 @@ export class Tooltip {
|
||||||
const interactive = maybe_call(opts.interactive, null, target, tip);
|
const interactive = maybe_call(opts.interactive, null, target, tip);
|
||||||
el.classList.toggle('interactive', interactive || false);
|
el.classList.toggle('interactive', interactive || false);
|
||||||
|
|
||||||
el.addEventListener('mouseover', () => {
|
if ( ! opts.manual ) {
|
||||||
if ( ! document.contains(target) )
|
el.addEventListener('mouseover', () => {
|
||||||
this.hide(tip);
|
if ( ! document.contains(target) )
|
||||||
|
this.hide(tip);
|
||||||
|
|
||||||
else if ( maybe_call(opts.interactive, null, target, tip) )
|
else if ( maybe_call(opts.interactive, null, target, tip) )
|
||||||
this._enter(target);
|
this._enter(target);
|
||||||
else
|
else
|
||||||
this._exit(target);
|
this._exit(target);
|
||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('mouseout', () => this._exit(target));
|
el.addEventListener('mouseout', () => this._exit(target));
|
||||||
|
}
|
||||||
|
|
||||||
// Assign our content. If there's a Promise, we'll need
|
// Assign our content. If there's a Promise, we'll need
|
||||||
// to do this weirdly.
|
// to do this weirdly.
|
||||||
|
|
|
@ -38,10 +38,6 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
|
||||||
> img {
|
|
||||||
margin: 0 !important
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
span {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -20px; bottom: -20px; left: -20px; right: -20px;
|
top: -20px; bottom: -20px; left: -20px; right: -20px;
|
||||||
|
|
|
@ -2,6 +2,75 @@ body {
|
||||||
overflow-x: hidden
|
overflow-x: hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz-metadata-balloon {
|
||||||
|
z-index: 999999999;
|
||||||
|
margin: 6px;
|
||||||
|
|
||||||
|
&[x-placement^="bottom"] > .tw-balloon__tail {
|
||||||
|
top: -3px;
|
||||||
|
box-shadow: -1px -1px 0 #dad8de;
|
||||||
|
|
||||||
|
.theme--dark & {
|
||||||
|
box-shadow: -1px -1px 0 #2c2541;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme--ffz & {
|
||||||
|
box-shadow: -1px -1px 0 var(--ffz-color-20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[x-placement^="top"] > .tw-balloon__tail {
|
||||||
|
bottom: -3px;
|
||||||
|
box-shadow: 1px 1px 0 #dad8de;
|
||||||
|
|
||||||
|
.theme--dark & {
|
||||||
|
box-shadow: 1px 1px 0 #2c2541;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme--ffz & {
|
||||||
|
box-shadow: 1px 1px 0 var(--ffz-color-20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[x-placement^="left"] > .tw-balloon__tail {
|
||||||
|
right: -3px;
|
||||||
|
box-shadow: -1px 1px 0 #dad8de;
|
||||||
|
|
||||||
|
.theme--dark & {
|
||||||
|
box-shadow: -1px 1px 0 #2c2541;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme--ffz & {
|
||||||
|
box-shadow: -1px 1px 0 var(--ffz-color-20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[x-placement^="right"] > .tw-balloon__tail {
|
||||||
|
left: -3px;
|
||||||
|
box-shadow: 1px -1px 0 #dad8de;
|
||||||
|
|
||||||
|
.theme--dark & {
|
||||||
|
box-shadow: 1px -1px 0 #2c2541;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme--ffz & {
|
||||||
|
box-shadow: 1px -1px 0 var(--ffz-color-20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ffz-metadata-balloon,
|
||||||
|
.ffz__tooltip {
|
||||||
|
.loader {
|
||||||
|
text-align: center;
|
||||||
|
opacity: .5;
|
||||||
|
margin: 1rem;
|
||||||
|
font-size: 3rem;
|
||||||
|
animation: ffz-rotateplane 1.2s infinite linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.ffz__tooltip {
|
.ffz__tooltip {
|
||||||
z-index: 999999999;
|
z-index: 999999999;
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
|
@ -16,14 +85,6 @@ body {
|
||||||
&.html { white-space: normal }
|
&.html { white-space: normal }
|
||||||
|
|
||||||
|
|
||||||
.loader {
|
|
||||||
opacity: .5;
|
|
||||||
margin: 1rem;
|
|
||||||
font-size: 3rem;
|
|
||||||
animation: ffz-rotateplane 1.2s infinite linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.ffz__tooltip--arrow {
|
.ffz__tooltip--arrow {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 6px; height: 6px;
|
width: 6px; height: 6px;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue