the browser-facing portion of osu!
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 146 lines 4.1 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 2// See the LICENCE file in the repository root for full licence text. 3 4import DispatcherAction from 'actions/dispatcher-action'; 5import { UserLoginAction } from 'actions/user-login-actions'; 6import { dispatchListener } from 'app-dispatcher'; 7import ResultSet from 'beatmaps/result-set'; 8import SearchResults from 'beatmaps/search-results'; 9import { BeatmapsetSearchFilters } from 'beatmapset-search-filters'; 10import DispatchListener from 'dispatch-listener'; 11import BeatmapsetExtendedJson from 'interfaces/beatmapset-extended-json'; 12import { route } from 'laroute'; 13import { action, makeObservable, observable, runInAction } from 'mobx'; 14import { BeatmapsetStore } from 'stores/beatmapset-store'; 15 16export interface SearchResponse { 17 beatmapsets: BeatmapsetExtendedJson[]; 18 cursor_string: string | null; 19 error?: string; 20 recommended_difficulty: number; 21 total: number; 22} 23 24@dispatchListener 25export class BeatmapsetSearch implements DispatchListener { 26 @observable readonly recommendedDifficulties = new Map<string|null, number>(); 27 @observable readonly resultSets = new Map<string, ResultSet>(); 28 29 private xhr?: JQueryXHR; 30 31 constructor(private readonly beatmapsetStore: BeatmapsetStore) { 32 makeObservable(this); 33 } 34 35 cancel() { 36 if (this.xhr) { 37 this.xhr.abort(); 38 } 39 } 40 41 @action 42 get(filters: BeatmapsetSearchFilters, from = 0): PromiseLike<SearchResults> { 43 if (from < 0) { 44 throw Error('from must be > 0'); 45 } 46 47 const key = filters.toKeyString(); 48 const resultSet = this.getOrCreate(key); 49 const sufficient = (from > 0 && from < resultSet.beatmapsetIds.size) || (from === 0 && !resultSet.isExpired); 50 if (sufficient) { 51 return Promise.resolve(resultSet); 52 } 53 54 return this.fetch(filters, from).then((data) => { 55 if (data != null) { 56 runInAction(() => { 57 if (from === 0) { 58 resultSet.reset(); 59 } 60 61 this.updateBeatmapsetStore(data); 62 resultSet.append(data); 63 this.recommendedDifficulties.set(filters.mode, data.recommended_difficulty); 64 }); 65 } 66 67 return resultSet; 68 }); 69 } 70 71 getResultSet(filters: BeatmapsetSearchFilters) { 72 const key = filters.toKeyString(); 73 74 return this.getOrCreate(key); 75 } 76 77 handleDispatchAction(dispatcherAction: DispatcherAction) { 78 if (dispatcherAction instanceof UserLoginAction) { 79 this.clear(); 80 } 81 } 82 83 @action 84 initialize(filters: BeatmapsetSearchFilters, data: SearchResponse) { 85 this.updateBeatmapsetStore(data); 86 87 const key = filters.toKeyString(); 88 const resultSet = this.getOrCreate(key); 89 // skip if already tracking. 90 if (resultSet.fetchedAt != null) { 91 return; 92 } 93 94 resultSet.append(data); 95 this.recommendedDifficulties.set(filters.mode, data.recommended_difficulty); 96 } 97 98 @action 99 private clear() { 100 this.resultSets.clear(); 101 this.recommendedDifficulties.clear(); 102 } 103 104 private fetch(filters: BeatmapsetSearchFilters, from: number): PromiseLike<SearchResponse | null> { 105 this.cancel(); 106 107 const params = filters.queryParams; 108 const key = filters.toKeyString(); 109 const cursorString = this.getOrCreate(key).cursorString; 110 111 // undefined cursor should just do a cursorless query. 112 if (from > 0) { 113 if (cursorString != null) { 114 params.cursor_string = cursorString; 115 } else if (cursorString === null) { 116 return Promise.resolve(null); 117 } 118 } 119 120 const url = route('beatmapsets.search'); 121 this.xhr = $.ajax(url, { 122 data: params, 123 dataType: 'json', 124 method: 'get', 125 }); 126 127 return this.xhr; 128 } 129 130 private getOrCreate(key: string) { 131 let resultSet = this.resultSets.get(key); 132 if (resultSet == null) { 133 resultSet = new ResultSet(); 134 135 this.resultSets.set(key, resultSet); 136 } 137 138 return resultSet; 139 } 140 141 private updateBeatmapsetStore(response: SearchResponse) { 142 for (const json of response.beatmapsets) { 143 this.beatmapsetStore.update(json); 144 } 145 } 146}