1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-10-14 23:11:58 +00:00
* Added: `Current Channel` rule for profiles, to match all pages associated with a certain channel without needing many page rules.
* Fixed: Unreadable text in light theme when importing a profile.
* Changed: Display a matching page URL in the `Current Page` rule for profiles.
* Changed: Do not display an inactive profile warning on the Add-Ons settings page, since those are not affected by profiles.
* Changed: Update Vue to a more recent version.
* Maintenance: Update the chat types enum based on the latest version of Twitch.
* API Added: `TwitchData` module (`site.twitch_data`) for querying Twitch's API for data.
This commit is contained in:
SirStendec 2019-06-14 21:24:48 -04:00
parent c34b7e30e2
commit 275248ca36
24 changed files with 819 additions and 88 deletions

View file

@ -0,0 +1,137 @@
<template>
<section class="tw-flex-grow-1 tw-align-self-start">
<div class="tw-flex tw-align-items-center">
<label :for="'channel$' + id">
{{ t(type.i18n, type.title) }}
</label>
<div class="ffz--search-avatar tw-mg-x-05">
<figure class="tw-avatar tw-avatar--size-30">
<div class="tw-border-radius-rounded tw-overflow-hidden">
<img
v-if="current"
:alt="current.displayName"
:src="current.profileImageURL"
class="tw-avatar__img tw-image"
>
</div>
</figure>
</div>
<autocomplete
v-slot="slot"
:input-id="'channel$' + id"
:items="fetchUsers"
:value="search"
:suggest-on-focus="true"
:escape-to-clear="false"
class="tw-flex-grow-1"
@selected="onSelected"
>
<div class="tw-pd-x-1 tw-pd-y-05">
<div class="tw-card tw-relative">
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row">
<div class="tw-card-img tw-card-img--size-3 tw-flex-shrink-0 tw-overflow-hidden">
<aspect :ratio="1">
<img
:alt="slot.item.displayName"
:src="slot.item.profileImageURL"
class="tw-image"
>
</aspect>
</div>
<div class="tw-card-body tw-overflow-hidden tw-relative">
<p class="tw-pd-x-1">{{ slot.item.displayName }}</p>
</div>
</div>
</div>
</div>
</autocomplete>
</div>
</section>
</template>
<script>
import {debounce, deep_copy} from 'utilities/object';
let last_id = 0;
export default {
props: ['value', 'type', 'filters', 'context'],
data() {
return {
id: last_id++,
current: null,
loaded_id: null
}
},
computed: {
search() {
return this.current && this.current.displayName || this.value.data.login;
}
},
watch: {
value: {
handler() {
this.cacheUser();
},
deep: true
}
},
created() {
const ffz = FrankerFaceZ.get();
this.loader = ffz.resolve('site.twitch_data');
this.cacheUser = debounce(this.cacheUser, 50);
},
beforeDestroy() {
this.cacheUser = null;
},
mounted() {
this.cacheUser();
},
methods: {
async cacheUser() {
if ( ! this.loader || this.loaded_id === this.value.data.id )
return;
this.current = null;
this.loaded_id = this.value.data.id;
if ( ! this.loaded_id )
return;
const data = await this.loader.getUser(this.loaded_id);
if ( data )
this.current = deep_copy(data);
else
this.current = null;
},
async fetchUsers(query) {
if ( ! this.loader )
return [];
const data = await this.loader.getMatchingUsers(query);
if ( ! data || ! data.items )
return [];
return deep_copy(data.items);
},
onSelected(item) {
this.current = item;
this.value.data.login = item && item.login || null;
this.value.data.id = item && item.id || null;
}
}
}
</script>

View file

@ -8,7 +8,7 @@
<select
:id="'page$' + id"
v-model="value.data.route"
class="tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-select"
class="tw-flex-grow-1 tw-mg-l-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-select"
>
<option
v-for="(route, key) in routes"
@ -21,10 +21,18 @@
</select>
</div>
<div
v-if="parts && parts.length"
class="tw-border-t tw-mg-t-05"
>
<div class="tw-border-t tw-mg-t-05">
<div class="tw-pd-y-05">
<t-list
phrase="setting.filter.page.url"
default="URL: {url}"
>
<template #url>
<span class="tw-c-text-alt">{{ url }}</span>
</template>
</t-list>
</div>
<div
v-for="part in parts"
:key="part.key"
@ -37,7 +45,7 @@
<input
:id="'page$' + id + '$part-' + part.key"
v-model="value.data.values[part.key]"
class="tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
class="tw-mg-l-1 tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
>
</div>
</div>
@ -66,6 +74,25 @@ export default {
return this.routes[this.value.data.route];
},
url() {
if ( ! this.route )
return null;
const parts = {};
for(const part of this.parts) {
const value = this.value.data.values[part.key];
parts[part.key] = value || `<${part.key}${part.optional ? '*' : ''}>`;
}
try {
return decodeURI(new URL(this.route.url(parts), location));
} catch(err) {
console.error(err);
return null;
}
},
parts() {
const out = [];
if ( ! this.route || ! this.route.parts )
@ -78,7 +105,8 @@ export default {
out.push({
key: part.name,
i18n: `settings.filter.page.route.${this.route.name}.${part.name}`,
title: name[0].toLocaleUpperCase() + name.substr(1)
title: name[0].toLocaleUpperCase() + name.substr(1),
optional: part.optional
});
}
}

View file

@ -109,8 +109,9 @@ export const Page = {
let i = 1;
for(const part of route.parts) {
if ( typeof part === 'object' ) {
if ( config.values[part.name] != null )
parts.push([i, config.values[part.name]]);
const val = config.values[part.name];
if ( val && val.length )
parts.push([i, val]);
i++;
}
@ -141,4 +142,22 @@ export const Page = {
values: {}
}),
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/page.vue')
};
export const Channel = {
createTest(config = {}) {
const login = config.login,
id = config.id;
return ctx => ctx.channelID === id || (ctx.channelID == null && ctx.channelLogin === login);
},
title: 'Current Channel',
i18n: 'settings.filter.channel',
default: () => ({
login: null,
id: null
}),
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/channel.vue')
};

View file

@ -79,6 +79,10 @@ export default class SettingsManager extends Module {
// Before we do anything else, make sure the provider is ready.
await this.provider.awaitReady();
// When the router updates we additional routes, make sure to
// trigger a rebuild of profile context and re-select profiles.
this.on('site.router:updated-routes', this.updateRoutes, this);
// Load profiles, but don't run any events because we haven't done
// migrations yet.
this.loadProfiles(true);
@ -198,6 +202,17 @@ export default class SettingsManager extends Module {
// Profile Management
// ========================================================================
updateRoutes() {
// Clear the existing matchers.
for(const profile of this.__profiles)
profile.matcher = null;
// And then re-select the active profiles.
for(const context of this.__contexts)
context.selectProfiles();
}
/**
* Get an existing {@link SettingsProfile} instance.
* @param {number} id - The id of the profile.