mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-23 06:20:54 +00:00
4.0.0-rc4.3
* Added: Settings Search * Added: Button to open the FFZ Control Center in a new window. * Added: Button to fade the FFZ Control Center.
This commit is contained in:
parent
a23bc74ae4
commit
0775cd1e77
23 changed files with 214 additions and 53 deletions
|
@ -100,7 +100,7 @@ class FrankerFaceZ extends Module {
|
|||
FrankerFaceZ.Logger = Logger;
|
||||
|
||||
const VER = FrankerFaceZ.version_info = {
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc4.2',
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc4.3',
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
`${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}`
|
||||
|
|
|
@ -67,7 +67,9 @@ const FFZ_ICONS = [
|
|||
'pin',
|
||||
'block',
|
||||
'ok',
|
||||
'clock'
|
||||
'clock',
|
||||
'eye',
|
||||
'eye-off'
|
||||
];
|
||||
|
||||
const FFZ_ALIASES = {
|
||||
|
|
|
@ -41,7 +41,7 @@ export default class Actions extends Module {
|
|||
|
||||
type: 'array_merge',
|
||||
ui: {
|
||||
path: 'Chat > In-Line Actions',
|
||||
path: 'Chat > In-Line Actions @{"description": "Here, you can define custom actions that will appear along messages in chat. If you aren\'t seeing an action you\'ve defined here in chat, please make sure that you have enabled Mod Icons in the chat settings menu."}',
|
||||
component: 'chat-actions',
|
||||
inline: true,
|
||||
|
||||
|
|
|
@ -151,6 +151,7 @@ export default class Badges extends Module {
|
|||
type: 'object_merge',
|
||||
ui: {
|
||||
path: 'Chat > Badges >> tabs ~> Visibility',
|
||||
title: 'Visibility',
|
||||
component: 'badge-visibility',
|
||||
data: () => {
|
||||
const twitch = [],
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
|
||||
<ul class="tw-mg-b-2">
|
||||
<li>Settings from the old version are not being imported.</li>
|
||||
<li>Settings cannot be searched.</li>
|
||||
<li>Advanced input (better tab completion, history) isn't available.</li>
|
||||
</ul>
|
||||
|
||||
|
|
|
@ -1,23 +1,44 @@
|
|||
<template lang="html">
|
||||
<div
|
||||
:class="{ maximized: maximized || exclusive, exclusive }"
|
||||
:class="{ maximized: maximized || exclusive, exclusive, faded }"
|
||||
class="ffz-main-menu tw-elevation-3 tw-c-background-alt tw-c-text tw-border tw-flex tw-flex-nowrap tw-flex-column"
|
||||
>
|
||||
<header class="tw-c-background tw-full-width tw-align-items-center tw-flex tw-flex-nowrap" @dblclick="resize">
|
||||
<h3 class="ffz-i-zreknarf ffz-i-pd-1">FrankerFaceZ</h3>
|
||||
<div class="tw-flex-grow-1 tw-pd-x-2">
|
||||
<!--div class="tw-search-input">
|
||||
<label for="ffz-main-menu.search" class="hide-accessible">{{ t('main-menu.search', 'Search Settings') }}</label>
|
||||
<div class="relative">
|
||||
<div class="tw-input__icon-group">
|
||||
<div class="tw-input__icon">
|
||||
<figure class="ffz-i-search" />
|
||||
</div>
|
||||
<div class="tw-search-input">
|
||||
<label for="ffz-main-menu.search" class="tw-hide-accessible">{{ t('main-menu.search', 'Search Settings') }}</label>
|
||||
<div class="tw-relative">
|
||||
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height tw-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<figure class="ffz-i-search" />
|
||||
</div>
|
||||
<input type="search" class="tw-input tw-input--icon-left" :placeholder="t('main-menu.search', 'Search Settings')" autocapitalize="off" autocorrect="off" autocomplete="off" id="ffz-main-menu.search">
|
||||
<input
|
||||
id="ffz-main-menu.search"
|
||||
v-model="query"
|
||||
:placeholder="t('main-menu.search', 'Search Settings')"
|
||||
type="search"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-l-3 tw-pd-r-1 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
>
|
||||
</div>
|
||||
</div-->
|
||||
</div>
|
||||
</div>
|
||||
<button v-if="!maximized && !exclusive" class="tw-button-icon tw-mg-x-05" @click="faded = ! faded">
|
||||
<span class="tw-button-icon__icon">
|
||||
<figure :class="faded ? 'ffz-i-eye-off' : 'ffz-i-eye'" />
|
||||
</span>
|
||||
</button>
|
||||
<button v-if="!exclusive" class="tw-button-icon tw-mg-x-05 tw-tooltip-wrapper" @click="popout">
|
||||
<span class="tw-button-icon__icon">
|
||||
<figure class="ffz-i-link-ext" />
|
||||
</span>
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-center">
|
||||
{{ t('main-menu.popout', 'Open Settings in a New Window') }}
|
||||
</div>
|
||||
</button>
|
||||
<button v-if="!exclusive" class="tw-button-icon tw-mg-x-05" @click="resize">
|
||||
<span class="tw-button-icon__icon">
|
||||
<figure :class="{'ffz-i-window-maximize': !maximized, 'ffz-i-window-restore': maximized}" />
|
||||
|
@ -42,6 +63,7 @@
|
|||
<menu-tree
|
||||
:current-item="currentItem"
|
||||
:modal="nav"
|
||||
:filter="filter"
|
||||
@change-item="changeItem"
|
||||
@navigate="navigate"
|
||||
/>
|
||||
|
@ -62,6 +84,7 @@
|
|||
ref="page"
|
||||
:context="context"
|
||||
:item="currentItem"
|
||||
:filter="filter"
|
||||
@change-item="changeItem"
|
||||
@navigate="navigate"
|
||||
/>
|
||||
|
@ -79,6 +102,12 @@ export default {
|
|||
return this.$vnode.data;
|
||||
},
|
||||
|
||||
computed: {
|
||||
filter() {
|
||||
return this.query.toLowerCase()
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
maximized() {
|
||||
this.updateDrag();
|
||||
|
|
|
@ -8,27 +8,45 @@
|
|||
class="tw-pd-b-1"
|
||||
v-html="t(item.desc_i18n_key, item.description, item)"
|
||||
/>
|
||||
<component
|
||||
<div
|
||||
v-for="i in item.contents"
|
||||
:is="i.component"
|
||||
:context="context"
|
||||
:item="i"
|
||||
:key="i.full_key"
|
||||
/>
|
||||
:class="{'ffz-unmatched-item': showing && ! shouldShow(i)}"
|
||||
>
|
||||
<component
|
||||
:is="i.component"
|
||||
:context="context"
|
||||
:item="i"
|
||||
:filter="filter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item', 'context'],
|
||||
props: ['item', 'context', 'filter'],
|
||||
|
||||
computed: {
|
||||
showing() {
|
||||
return this.shouldShow(this.item);
|
||||
},
|
||||
|
||||
classes() {
|
||||
return [
|
||||
'ffz--menu-container',
|
||||
this.item.full_box ? 'tw-border' : 'tw-border-t'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
shouldShow(item) {
|
||||
if ( ! this.filter || ! this.filter.length || ! item.search_terms )
|
||||
return true;
|
||||
|
||||
return item.search_terms.includes(this.filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -26,30 +26,40 @@
|
|||
/>
|
||||
<template v-if="! item.contents || ! item.contents.length">
|
||||
<ul class="tw-border-t tw-pd-y-1">
|
||||
<li v-for="i in item.items" :key="i.full_key" class="tw-pd-x-1">
|
||||
<li
|
||||
v-for="i in item.items"
|
||||
:key="i.full_key"
|
||||
:class="{'ffz-unmatched-item': ! shouldShow(i)}"
|
||||
class="tw-pd-x-1"
|
||||
>
|
||||
<a href="#" @click="$emit('change-item', i, false)">
|
||||
{{ t(i.i18n_key, i.title, i) }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
<component
|
||||
<div
|
||||
v-for="i in item.contents"
|
||||
ref="children"
|
||||
:is="i.component"
|
||||
:context="context"
|
||||
:item="i"
|
||||
:key="i.full_key"
|
||||
@change-item="changeItem"
|
||||
@navigate="navigate"
|
||||
/>
|
||||
:class="{'ffz-unmatched-item': ! shouldShow(i)}"
|
||||
>
|
||||
<component
|
||||
ref="children"
|
||||
:is="i.component"
|
||||
:context="context"
|
||||
:item="i"
|
||||
:filter="filter"
|
||||
@change-item="changeItem"
|
||||
@navigate="navigate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: ['item', 'context'],
|
||||
props: ['item', 'context', 'filter'],
|
||||
|
||||
computed: {
|
||||
breadcrumbs() {
|
||||
|
@ -65,6 +75,13 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
shouldShow(item) {
|
||||
if ( ! this.filter || ! this.filter.length || ! item.search_terms )
|
||||
return true;
|
||||
|
||||
return item.search_terms.includes(this.filter);
|
||||
},
|
||||
|
||||
changeItem(item) {
|
||||
this.$emit('change-item', item);
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
>
|
||||
<li
|
||||
v-for="item in modal"
|
||||
v-if="shouldShow(item)"
|
||||
:key="item.full_key"
|
||||
:class="[currentItem === item ? 'active' : '']"
|
||||
role="presentation"
|
||||
|
@ -34,7 +35,7 @@
|
|||
<span class="tw-flex-grow-1">
|
||||
{{ t(item.i18n_key, item.title, item) }}
|
||||
</span>
|
||||
<span v-if="item.pill" class="pill">
|
||||
<span v-if="item.pill" class="tw-pill">
|
||||
{{ item.pill_i18n_key ? t(item.pill_i18n_key, item.pill, item) : item.pill }}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -43,6 +44,7 @@
|
|||
:root="item"
|
||||
:current-item="currentItem"
|
||||
:modal="item.items"
|
||||
:filter="filter"
|
||||
@change-item="i => $emit('change-item', i)"
|
||||
/>
|
||||
</li>
|
||||
|
@ -82,7 +84,7 @@ function recursiveExpand(node) {
|
|||
|
||||
|
||||
export default {
|
||||
props: ['root', 'modal', 'currentItem'],
|
||||
props: ['root', 'modal', 'currentItem', 'filter'],
|
||||
|
||||
computed: {
|
||||
tabIndex() {
|
||||
|
@ -91,6 +93,28 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
shouldShow(item) {
|
||||
if ( ! this.filter || ! this.filter.length || this.containsCurrent(item) )
|
||||
return true;
|
||||
|
||||
if ( item.search_terms && item.search_terms.includes(this.filter) )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
containsCurrent(item) {
|
||||
let i = this.currentItem;
|
||||
while ( i ) {
|
||||
if ( item === i )
|
||||
return true;
|
||||
|
||||
i = i.parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
clickItem(item) {
|
||||
if ( ! item.expanded )
|
||||
item.expanded = true;
|
||||
|
|
|
@ -65,6 +65,22 @@ export default class MainMenu extends Module {
|
|||
|
||||
}
|
||||
|
||||
openPopout() {
|
||||
const win = window.open(
|
||||
'https://twitch.tv/popout/frankerfacez/chat?ffz-settings',
|
||||
'_blank',
|
||||
'resizable=yes,scrollbars=yes,width=850,height=600'
|
||||
);
|
||||
|
||||
if ( win ) {
|
||||
win.focus();
|
||||
return true;
|
||||
} else {
|
||||
this.log.warn('Unable to open popout settings window.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async onLoad() {
|
||||
this.vue.component(
|
||||
(await import(/* webpackChunkName: "main-menu" */ './components.js')).default
|
||||
|
@ -542,6 +558,9 @@ export default class MainMenu extends Module {
|
|||
return {
|
||||
context,
|
||||
|
||||
query: '',
|
||||
faded: false,
|
||||
|
||||
nav: settings,
|
||||
currentItem: settings.keys['home'], // settings[0],
|
||||
nav_keys: settings.keys,
|
||||
|
@ -549,6 +568,14 @@ export default class MainMenu extends Module {
|
|||
maximized: this._maximized,
|
||||
resize: e => !this.exclusive && this.toggleSize(e),
|
||||
close: e => !this.exclusive && this.toggleVisible(e),
|
||||
popout: e => {
|
||||
if ( this.exclusive )
|
||||
return;
|
||||
|
||||
this.toggleVisible(e);
|
||||
if ( ! this.openPopout() )
|
||||
alert(this.i18n.t('popup.error', 'We tried opening a pop-up window and could not. Make sure to allow pop-ups from Twitch.'));
|
||||
},
|
||||
version: window.FrankerFaceZ.version_info,
|
||||
|
||||
exclusive: this.exclusive
|
||||
|
|
|
@ -175,8 +175,8 @@ export default class Directory extends SiteModule {
|
|||
channel_url = `/${channel.login}`,
|
||||
game_url = game && `/directory/game/${stream.game.name}`,
|
||||
|
||||
user_link = <a href={channel_url} data-href={channel_url} onClick={t.routeClick} class="tw-link tw-link--inherit">{channel.displayName}</a>,
|
||||
game_link = game && <a href={game_url} data-href={game_url} onClick={t.routeClick} class="tw-link tw-link--inherit">{game.name}</a>;
|
||||
user_link = <a href={channel_url} data-href={channel_url} onClick={t.routeClick} title={channel.displayName} class="tw-link tw-link--inherit">{channel.displayName}</a>,
|
||||
game_link = game && <a href={game_url} data-href={game_url} onClick={t.routeClick} title={game.name} class="tw-link tw-link--inherit">{game.name}</a>;
|
||||
|
||||
return (<div>
|
||||
<a href={channel_url} data-href={channel_url} onClick={t.routeClick} class="tw-link tw-link--inherit" data-test-selector="preview-card-titles__primary-link">
|
||||
|
@ -203,7 +203,7 @@ export default class Directory extends SiteModule {
|
|||
nodes.length > 1 ?
|
||||
t.i18n.t('directory.hosted.by-many', 'Hosted by %{count} channel%{count|en_plural}', nodes.length) :
|
||||
t.i18n.tList('directory.hosted.by-one', 'Hosted by %{user}', {
|
||||
user: <a href={`/${nodes[0].login}`} data-href={`/${nodes[0].login}`} onClick={t.routeClick} class="tw-link tw-link--inherit">{nodes[0].displayName}</a>
|
||||
user: <a href={`/${nodes[0].login}`} data-href={`/${nodes[0].login}`} onClick={t.routeClick} title={nodes[0].displayName} class="tw-link tw-link--inherit">{nodes[0].displayName}</a>
|
||||
})
|
||||
}</p>
|
||||
</div>
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
}
|
||||
|
||||
|
||||
.ffz-featured-follow,
|
||||
.ffz-auto-host-options {
|
||||
.ffz-channel-avatar {
|
||||
max-width: 3.2rem;
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
> header {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
&.faded {
|
||||
opacity: 0.5
|
||||
}
|
||||
}
|
||||
|
||||
&.maximized {
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
:id="'tab-for-' + i.full_key"
|
||||
:aria-selected="selected === idx"
|
||||
:aria-controls="'tab-panel-' + i.full_key"
|
||||
:class="[selected === idx ? 'active' : '']"
|
||||
:class="{'active': selected === idx, 'ffz-unmatched-item': showing && ! shouldShow(i)}"
|
||||
role="tab"
|
||||
class="tab tw-pd-y-05 tw-pd-x-1"
|
||||
@click="selected = idx"
|
||||
|
@ -43,22 +43,27 @@
|
|||
<section v-if="tab.description" class="tw-pd-b-1">
|
||||
{{ t(tab.desc_i18n_key, tab.description, tab) }}
|
||||
</section>
|
||||
<component
|
||||
<div
|
||||
v-for="i in tab.contents"
|
||||
:is="i.component"
|
||||
:current-profile="currentProfile"
|
||||
:profiles="profiles"
|
||||
:context="context"
|
||||
:item="i"
|
||||
:key="i.full_key"
|
||||
/>
|
||||
:class="{'ffz-unmatched-item': showing && ! shouldShow(i)}"
|
||||
>
|
||||
<component
|
||||
:is="i.component"
|
||||
:current-profile="currentProfile"
|
||||
:profiles="profiles"
|
||||
:context="context"
|
||||
:item="i"
|
||||
:filter="filter"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item', 'profiles', 'currentProfile', 'context'],
|
||||
props: ['item', 'profiles', 'currentProfile', 'context', 'filter'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
@ -67,6 +72,10 @@ export default {
|
|||
},
|
||||
|
||||
computed: {
|
||||
showing() {
|
||||
return this.shouldShow(this.item)
|
||||
},
|
||||
|
||||
tab() {
|
||||
return this.item.tabs[this.selected];
|
||||
}
|
||||
|
@ -113,6 +122,13 @@ export default {
|
|||
nextTab() {
|
||||
if ( this.selected + 1 < this.item.tabs.length )
|
||||
this.selected++;
|
||||
},
|
||||
|
||||
shouldShow(item) {
|
||||
if ( ! this.filter || ! this.filter.length || ! item.search_terms )
|
||||
return true;
|
||||
|
||||
return item.search_terms.includes(this.filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,6 +146,21 @@ export function filter_match(filter, target) {
|
|||
}
|
||||
|
||||
|
||||
export function substr_count(str, needle) {
|
||||
let i = 0, idx = 0;
|
||||
while( idx < str.length ) {
|
||||
const x = str.indexOf(needle, idx);
|
||||
if ( x === -1 )
|
||||
break;
|
||||
|
||||
i++;
|
||||
idx = x + 1;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a value from an object at a path.
|
||||
* @param {string|Array} path The path to follow, using periods to go down a level.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue