1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-08 23:30:53 +00:00
FrankerFaceZ/src/modules/main_menu/components/menu-tree.vue
SirStendec f0d68527b8 4.22.5
* Added: Setting to hide unrelated results from the FFZ Control Center when searching. This is now enabled by default.
* Added: Setting to control whether the height of the Emote Menu is expanded. This is now disabled by default.
* Changed: When searching in the FFZ Control Center, pills are displayed in the navigation tree showing how many matching results there are.
* Changed: Update the method used for searching for channels and games, hopefully resulting in more accurate results.
* Changed: When searching for users in an auto-complete field, display a check-mark for verified users.
* Changed: When searching for users in an auto-complete field, respect the user's preference for rounded avatars.
* Fixed: Lazy load Markdown when possible to save on initial download size.
2021-05-13 16:24:39 -04:00

234 lines
No EOL
4.7 KiB
Vue

<template lang="html">
<ul
v-if="modal"
:role="[root ? 'group' : 'tree']"
:tabindex="tabIndex"
class="ffz--menu-tree"
@keyup.up="prevItem"
@keyup.down="nextItem"
@keyup.left="prevLevel"
@keyup.right="nextLevel"
@keyup.*="expandAll"
>
<li
v-for="item in displayed"
:key="item.full_key"
:class="[currentItem === item ? 'active' : '']"
:data-key="item.full_key"
role="presentation"
>
<div
:aria-expanded="item.expanded"
:aria-selected="currentItem === item"
class="tw-flex__item tw-flex tw-flex-nowrap tw-align-items-center tw-pd-y-05 tw-pd-r-05"
role="treeitem"
@click="clickItem(item)"
>
<span
:class="[
item.items ? '' : 'ffz--invisible',
item.expanded ? 'ffz-i-down-dir' : 'ffz-i-right-dir'
]"
role="presentation"
class="arrow"
/>
<span class="tw-flex-grow-1">
{{ t(item.i18n_key, item.title) }}
</span>
<span v-if="filter" class="ffz-pill ffz-pill--overlay">
{{ countMatches(item) }}
</span>
<span v-else-if="item.pill" class="ffz-pill ffz-pill--overlay">
{{ item.pill_i18n_key ? t(item.pill_i18n_key, item.pill) : item.pill }}
</span>
<span v-else-if="item.unseen" class="ffz-pill ffz-pill--overlay">
{{ item.unseen }}
</span>
</div>
<menu-tree
v-if="item.items && item.expanded"
:root="item"
:current-item="currentItem"
:modal="item.items"
:filter="filter"
@change-item="i => $emit('change-item', i)"
@mark-seen="i => $emit('mark-seen', i)"
@mark-expanded="i => $emit('mark-expanded', i)"
/>
</li>
</ul>
</template>
<script>
function findLastVisible(node) {
if ( node.expanded && node.items )
return findLastVisible(node.items[node.items.length - 1]);
return node;
}
function findNextVisible(node, modal) {
const items = node.parent ? node.parent.items : modal,
idx = items.indexOf(node);
if ( items[idx + 1] )
return items[idx+1];
if ( node.parent )
return findNextVisible(node.parent, modal);
return null;
}
function recursiveExpand(node, vue) {
if ( ! node.expanded ) {
node.expanded = true;
if ( vue )
vue.$emit('mark-expanded', node);
}
if ( node.items )
for(const item of node.items)
recursiveExpand(item, vue);
}
export default {
props: ['root', 'modal', 'currentItem', 'filter'],
computed: {
tabIndex() {
return this.root ? undefined : 0;
},
displayed() {
return this.modal.filter(item => this.shouldShow(item));
}
},
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;
},
countMatches(item, seen) {
if ( ! this.filter || ! this.filter.length || ! item )
return 0;
if ( seen && seen.has(item) )
return 0;
if ( ! seen )
seen = new Set;
seen.add(item);
let count = 0;
for(const key of ['tabs', 'contents', 'items'])
if ( item[key] )
for(const thing of item[key])
count += this.countMatches(thing, seen);
if ( item.setting && item.search_terms && item.search_terms.includes(this.filter) )
count++;
return count;
},
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;
else if ( this.currentItem === item )
item.expanded = false;
this.$emit('mark-expanded', item);
this.$emit('change-item', item);
},
expandAll() {
for(const item of this.modal)
recursiveExpand(item, this);
},
prevItem() {
if ( this.root ) return;
const i = this.currentItem,
items = i.parent ? i.parent.items : this.modal,
idx = items.indexOf(i);
if ( idx > 0 )
this.$emit('change-item', findLastVisible(items[idx-1]));
else if ( i.parent )
this.$emit('change-item', i.parent);
},
nextItem() {
if ( this.root ) return;
const i = this.currentItem;
let target;
if ( i.expanded && i.items )
target = i.items[0];
else
target = findNextVisible(i, this.modal);
if ( target )
this.$emit('change-item', target);
},
prevLevel() {
if ( this.root ) return;
const i = this.currentItem;
if ( i.expanded && i.items ) {
i.expanded = false;
this.$emit('mark-expanded', i);
} else if ( i.parent )
this.$emit('change-item', i.parent);
},
nextLevel() {
if ( this.root ) return;
const i = this.currentItem;
if ( i.expanded && i.items )
this.$emit('change-item', i.items[0]);
else {
i.expanded = true;
this.$emit('mark-expanded', i);
}
if ( event.ctrlKey )
recursiveExpand(this.currentItem, this);
}
}
}
</script>