1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-05 18:48:31 +00:00

4.0.0-rc13.4

* Fixed: Game directory pages not loading.
* Changed: Rewrite GraphQL query merging logic to hopefully perform in a more robust manner less likely to break in the future.
This commit is contained in:
SirStendec 2018-10-16 15:12:47 -04:00
parent 7fc27ff025
commit 551a08bfd0
12 changed files with 158 additions and 186 deletions

View file

@ -100,7 +100,7 @@ class FrankerFaceZ extends Module {
FrankerFaceZ.Logger = Logger; FrankerFaceZ.Logger = Logger;
const VER = FrankerFaceZ.version_info = { const VER = FrankerFaceZ.version_info = {
major: 4, minor: 0, revision: 0, extra: '-rc13.3', major: 4, minor: 0, revision: 0, extra: '-rc13.4',
commit: __git_commit__, commit: __git_commit__,
build: __webpack_hash__, build: __webpack_hash__,
toString: () => toString: () =>

View file

@ -1,9 +1,5 @@
query { fragment browsePagePopularStreamsWithTagsEdge2 on StreamEdge {
streams { node {
edges { createdAt
node {
createdAt
}
}
} }
} }

View file

@ -1,9 +1,18 @@
query { query FollowedChannels_RENAME2 {
currentUser { currentUser {
follows {
edges {
node {
stream {
createdAt
type
}
}
}
}
followedLiveUsers { followedLiveUsers {
nodes { nodes {
stream { stream {
type
createdAt createdAt
} }
} }

View file

@ -1,4 +1,4 @@
query { query FollowingHosts_CurrentUser {
currentUser { currentUser {
followedHosts { followedHosts {
nodes { nodes {

View file

@ -1,4 +1,4 @@
query { query FollowedIndex_CurrentUser {
currentUser { currentUser {
followedLiveUsers { followedLiveUsers {
nodes { nodes {

View file

@ -1,4 +1,4 @@
query { query FollowingLive_CurrentUser {
currentUser { currentUser {
followedLiveUsers { followedLiveUsers {
edges { edges {

View file

@ -1,24 +1,9 @@
query { query {
directory { __type
... on Community { }
streams {
edges { fragment directoryPageGameStreamWithTagsEdge on StreamEdge {
node { node {
createdAt createdAt
type
}
}
}
}
... on Game {
streams {
edges {
node {
createdAt
type
}
}
}
}
} }
} }

View file

@ -21,71 +21,6 @@ export default class Game extends SiteModule {
this.inject('i18n'); this.inject('i18n');
this.inject('settings'); this.inject('settings');
/*this.metadata.definitions.block_game = {
type: 'directory',
button(data) {
return `ffz-directory-toggle-block${data.blocked ? ' active' : ''}`
},
setup(data) {
if ( data.type !== 'GAMES' )
return null;
const blocked_games = this.settings.provider.get('directory.game.blocked-games', []),
blocked = blocked_games.includes(data.name);
data.blocked = blocked;
return data;
},
label(data) {
if ( ! data )
return null;
return data.blocked ?
this.i18n.t('directory.unblock', 'Unblock') :
this.i18n.t('directory.block', 'Block')
},
tooltip() {
return this.i18n.t('directory.block-explain', 'This will let you block streams playing this game from showing up in the directory.');
},
click: this.generateClickHandler('directory.game.blocked-games')
}
this.metadata.definitions.hide_thumbnails = {
type: 'directory',
button(data) {
return `ffz-directory-toggle-thumbnail${data.hidden ? ' active' : ''}`
},
setup(data) {
if ( data.type !== 'GAMES' )
return null;
const hidden_games = this.settings.provider.get('directory.game.hidden-thumbnails', []);
data.hidden = hidden_games.includes(data.name);
return data;
},
label(data) {
if ( ! data )
return null;
return data.hidden ?
this.i18n.t('directory.show-thumbnails', 'Show Thumbnails') :
this.i18n.t('directory.hide-thumbnails', 'Hide Thumbnails');
},
tooltip() {
return this.i18n.t('directory.thumbnails-explain', 'Enabling this will hide thumbnails of this game everywhere in the directory.');
},
click: this.generateClickHandler('directory.game.hidden-thumbnails')
}*/
this.GameHeader = this.fine.define( this.GameHeader = this.fine.define(
'game-header', 'game-header',
n => n.props && n.props.data && n.renderDropsAvailable, n => n.props && n.props.data && n.renderDropsAvailable,
@ -93,6 +28,16 @@ export default class Game extends SiteModule {
); );
this.apollo.registerModifier('DirectoryPage_Game', GAME_QUERY); this.apollo.registerModifier('DirectoryPage_Game', GAME_QUERY);
this.apollo.registerModifier('DirectoryPage_Game', res => this.modifyStreams(res), false);
}
modifyStreams(res) { // eslint-disable-line class-methods-use-this
const edges = get('data.game.streams.edges', res);
if ( ! edges || ! edges.length )
return res;
res.data.game.streams.edges = this.parent.processNodes(edges);
return res;
} }
onEnable() { onEnable() {

View file

@ -46,8 +46,6 @@ export default class Directory extends SiteModule {
this.inject(Game); this.inject(Game);
this.inject(BrowsePopular); this.inject(BrowsePopular);
this.apollo.registerModifier('DirectoryPage_Game', res => this.modifyStreams(res), false);
this.DirectoryCard = this.fine.define( this.DirectoryCard = this.fine.define(
'directory-card', 'directory-card',
n => n.renderTitles && n.renderIconicImage, n => n.renderTitles && n.renderIconicImage,
@ -217,7 +215,7 @@ export default class Directory extends SiteModule {
// TODO: Better query handling. // TODO: Better query handling.
this.apollo.ensureQuery( this.apollo.ensureQuery(
'DirectoryPage_Game', 'DirectoryPage_Game',
'data.directory.streams.edges.0.node.createdAt' 'data.game.streams.edges.0.node.createdAt'
); );
//for(const inst of instances) //for(const inst of instances)
@ -294,18 +292,6 @@ export default class Directory extends SiteModule {
} }
modifyStreams(res) { // eslint-disable-line class-methods-use-this
const is_game_query = get('data.directory.__typename', res) === 'Game',
edges = get('data.directory.streams.edges', res);
if ( ! edges || ! edges.length )
return res;
res.data.directory.streams.edges = this.processNodes(edges, is_game_query);
return res;
}
clearUptime(inst) { // eslint-disable-line class-methods-use-this clearUptime(inst) { // eslint-disable-line class-methods-use-this
if ( inst.ffz_update_timer ) { if ( inst.ffz_update_timer ) {
clearInterval(inst.ffz_update_timer); clearInterval(inst.ffz_update_timer);

View file

@ -1,4 +1,4 @@
query { query RecommendedChannels {
currentUser { currentUser {
recommendations { recommendations {
liveRecommendations { liveRecommendations {

View file

@ -6,10 +6,11 @@
// ============================================================================ // ============================================================================
import Module from 'utilities/module'; import Module from 'utilities/module';
import {has, get} from 'utilities/object'; import {get, deep_copy} from 'utilities/object';
import merge from 'utilities/graphql';
const BAD_ERRORS = [ /*const BAD_ERRORS = [
'timeout', 'timeout',
'unable to load', 'unable to load',
'error internal', 'error internal',
@ -31,7 +32,7 @@ function skip_error(err) {
for(const m of BAD_ERRORS) for(const m of BAD_ERRORS)
if ( err.message.includes(m) ) if ( err.message.includes(m) )
return true; return true;
} }*/
export class GQLError extends Error { export class GQLError extends Error {
@ -212,6 +213,9 @@ export default class Apollo extends Module {
query = query_map && query_map.get(id), query = query_map && query_map.get(id),
modifiers = this.modifiers[operation]; modifiers = this.modifiers[operation];
const pre_modification = deep_copy(request.query);
if ( modifiers ) { if ( modifiers ) {
for(const mod of modifiers) { for(const mod of modifiers) {
if ( typeof mod === 'function' ) if ( typeof mod === 'function' )
@ -245,6 +249,8 @@ export default class Apollo extends Module {
this.log.info('Unable to find GQL Print. Clearing store for query:', operation); this.log.info('Unable to find GQL Print. Clearing store for query:', operation);
this.client.queryManager.queryStore.store[id] = null; this.client.queryManager.queryStore.store[id] = null;
} }
this.log.info('Query', operation, pre_modification, request.query, request.variables);
} }
apolloPostFlight(response) { apolloPostFlight(response) {
@ -406,71 +412,3 @@ export default class Apollo extends Module {
} }
} }
// ============================================================================
// Query Merging
// ============================================================================
function canMerge(a, b) {
return a.kind === b.kind &&
a.kind !== 'FragmentDefinition' &&
(a.selectionSet == null) === (b.selectionSet == null);
}
function merge(a, b) {
if ( ! canMerge(a, b) )
return a;
if ( a.definitions ) {
const a_def = a.definitions,
b_def = b.definitions;
for(let i=0; i < a_def.length && i < b_def.length; i++)
a_def[i] = merge(a_def[i], b_def[i]);
}
if ( a.selectionSet ) {
const s = a.selectionSet.selections,
selects = {};
for(const sel of b.selectionSet.selections) {
const name = sel.kind === 'InlineFragment' ?
(sel.typeCondition.name ?
sel.typeCondition.name.value : null) :
(sel.name ? sel.name.value : null),
alias = sel.alias ? sel.alias.value : null,
key = `${name}:${alias}`;
if ( name )
selects[key] = sel;
}
for(let i=0, l = s.length; i < l; i++) {
const sel = s[i],
name = sel.kind === 'InlineFragment' ?
(sel.typeCondition.name ?
sel.typeCondition.name.value : null) :
(sel.name ? sel.name.value : null),
alias = sel.alias ? sel.alias.value : null,
key = `${name}:${alias}`,
other = selects[key];
if ( other ) {
s[i] = merge(sel, other);
selects[key] = null;
}
}
for(const key in selects)
if ( has(selects, key) ) {
const val = selects[key];
if ( val )
s.push(val);
}
}
// TODO: Variables?
return a;
}

113
src/utilities/graphql.js Normal file
View file

@ -0,0 +1,113 @@
import { deep_copy } from "./object";
'use strict';
// ============================================================================
// GraphQL Document Manipulation
// ============================================================================
export const MERGE_METHODS = {
Document: (a, b) => {
if ( a.definitions && b.definitions )
a.definitions = mergeList(a.definitions, b.definitions);
else if ( b.definitions )
a.definitions = b.definitions;
return a;
},
Field: (a, b) => {
if ( a.name && (! b.name || b.name.value !== a.name.value) )
return a;
// TODO: arguments
// TODO: directives
if ( a.selectionSet && b.selectionSet )
a.selectionSet = merge(a.selectionSet, b.selectionSet);
else if ( b.selectionSet )
a.selectionSet = b.selectionSet;
return a;
},
OperationDefinition: (a, b) => {
if ( a.operation !== b.operation )
return a;
// TODO: variableDefinitions
// TODO: directives
if ( a.selectionSet && b.selectionSet )
a.selectionSet = merge(a.selectionSet, b.selectionSet);
else if ( b.selectionSet )
a.selectionSet = b.selectionSet;
return a;
},
FragmentDefinition: (a, b) => {
if ( a.typeCondition && b.typeCondition ) {
if ( a.typeCondition.kind !== b.typeCondition.kind )
return a;
if ( a.typeCondition.name.value != b.typeCondition.name.value )
return a;
}
// TODO: directives
if ( a.selectionSet && b.selectionSet )
a.selectionSet = merge(a.selectionSet, b.selectionSet);
else if ( b.selectionSet )
a.selectionSet = b.selectionSet;
return a;
},
SelectionSet: (a, b) => {
if ( a.selections && b.selections )
a.selections = mergeList(a.selections, b.selections);
else if ( b.selections )
a.selections = b.selections;
return a;
}
}
export function mergeList(a, b) {
const a_names = {};
for(const item of a) {
if ( ! item || ! item.name || item.name.kind !== 'Name' )
continue;
a_names[item.name.value] = item;
}
for(const item of b) {
if ( ! item || ! item.name || item.name.kind !== 'Name' )
continue;
const name = item.name.value,
idx = a_names[name] ? a.indexOf(a_names[name]) : -1;
if ( idx !== -1 )
a[idx] = merge(a[idx], item);
else
a.push(item);
}
return a;
}
export default function merge(a, b) {
if ( a.kind !== b.kind )
return a;
if ( MERGE_METHODS[a.kind] )
return MERGE_METHODS[a.kind](a, b);
return a;
}