Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.10 Map Selector Part 1 #157

Merged
merged 19 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions layout/pages/map-selector/map-selector.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,16 @@
<Image class="search__clearicon" src="file://{images}/close.svg" textureheight="32" />
</Button>
</Panel>
<Panel class="mapselector-header__filters">
<Button class="button button--green ml-2" onactivate="MapSelectorHandler.requestMapUpdate()">
<Label class="button__text" text="#Common_Update" />
<TooltipPanel class="mr-2" tooltip="#MapSelector_Updates_Check">
<Button class="button" onactivate="MapSelectorHandler.checkForUpdates()">
<Image id="RefreshIcon" class="button__icon" src="file://{images}/refresh.svg" textureheight="32" />
</Button>
<Button id="FilterErase" class="ml-2 button button--red" onactivate="MapSelectorHandler.clearFilters()">
</TooltipPanel>
<TooltipPanel tooltip="#MapSelector_Filters_Reset">
<Button class="button button--red" onactivate="MapSelectorHandler.clearFilters()">
<Image class="button__icon" src="file://{images}/filter-remove.svg" textureheight="32" />
</Button>
</Panel>
</TooltipPanel>
</Panel>
<Panel id="MapFilters" class="mapselector-filters">
<Panel class="mapselector-filters__row mapselector-filters__gamemodes">
Expand Down
99 changes: 98 additions & 1 deletion scripts/pages/map-selector/map-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class MapSelectorHandler implements OnPanelLoad {
unranked: $<Button>('#MapListUnranked'),
beta: $<Button>('#MapListBeta')
},
refreshIcon: $<Image>('#RefreshIcon')
};

// Describing which data on which type of panel we want to store out to PS.
Expand Down Expand Up @@ -345,7 +346,7 @@ class MapSelectorHandler implements OnPanelLoad {

info.SetDialogVariableInt('tier', Maps.getTier(staticData, gamemode) ?? 0);
info.SetDialogVariableInt('numZones', Leaderboards.getNumZones(staticData));
info.SetDialogVariable('type', mainTrack.linear ? this.strings.staged : this.strings.linear);
info.SetDialogVariable('layout', mainTrack.linear ? this.strings.staged : this.strings.linear);

info.SetDialogVariable('description', staticData.info?.description);
this.panels.descriptionContainer.SetHasClass('hide', !staticData.info?.description);
Expand Down Expand Up @@ -504,4 +505,100 @@ class MapSelectorHandler implements OnPanelLoad {
this.panels.leaderboardContainer.SetHasClass('mapselector-leaderboards--open', open);
$.persistentStorage.setItem('mapSelector.leaderboardsOpen', open);
}

checkingUpdates = false;
lastUpdateCheck = 0;

checkForUpdates() {
if (this.checkingUpdates || this.lastUpdateCheck + REFRESH_COOLDOWN > Date.now()) {
Gocnak marked this conversation as resolved.
Show resolved Hide resolved
return;
}

this.lastUpdateCheck = Date.now();

this.panels.refreshIcon.AddClass('spin-clockwise');

// Has to handle both private and static map updates, where we only need private if we're in the beta, and we
// could need 0, 1 or 2 static updates, depending on the response from the version check. So logic gets quite
// complicated, all for one loading spinner. I want RxJS!
let updatesNeeded = 0;
let fetchedStaticVersions = false;
let errored = false;
if (this.panels.listTypes.beta.IsSelected()) {
updatesNeeded++;

const privHandle = $.RegisterForUnhandledEvent('MapCache_PrivateMapsUpdate', (success: boolean) => {
$.UnregisterForUnhandledEvent('MapCache_PrivateMapsUpdate', privHandle);

if (!success) {
errored = true;
}

updatesNeeded--;

if (updatesNeeded === 0 && fetchedStaticVersions) {
this.onFinishUpdate(errored, {
message: '#MapSelector_Updates_Updated',
style: ToastStyle.SUCCESS
});
}
});

MapCacheAPI.FetchPrivateMaps();
}

const versionsHandle = $.RegisterForUnhandledEvent(
'MapCache_StaticCacheVersionChecked',
(staticUpdatesNeeded) => {
$.UnregisterForUnhandledEvent('MapCache_StaticCacheVersionChecked', versionsHandle);

fetchedStaticVersions = true;

if (staticUpdatesNeeded === 0) {
if (updatesNeeded === 0) {
this.onFinishUpdate(errored, {
message: '#MapSelector_Updates_UpToDate',
style: ToastStyle.INFO
});
}
return;
}

updatesNeeded += staticUpdatesNeeded;
let staticUpdates = 0;
const staticHandle = $.RegisterForUnhandledEvent('MapCache_StaticCacheUpdate', (_type, success) => {
if (!success) {
errored = true;
}

staticUpdates++;
if (staticUpdates === staticUpdatesNeeded) {
$.UnregisterForUnhandledEvent('MapCache_StaticCacheUpdate', staticHandle);
}

--updatesNeeded;
if (updatesNeeded === 0) {
this.onFinishUpdate(errored, {
message: '#MapSelector_Updates_Updated',
style: ToastStyle.SUCCESS
});
}
});
}
);

this.checkingUpdates = true;
MapCacheAPI.CheckForUpdates();
}

onFinishUpdate(errored: boolean, toast: ToastCreateArgs) {
// If we errored at any point, C++ will show a toast. Even if some requests were successful, don't show
// both success and error toasts, would be confusing.
if (!errored) {
ToastManager.createToast(toast);
}

this.panels.refreshIcon.RemoveClass('spin-clockwise');
this.checkingUpdates = false;
}
}
6 changes: 6 additions & 0 deletions scripts/types-mom/apis.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,12 @@ declare namespace MapCacheAPI {

/** Returns true if the given mapID is queued from download */
function MapQueuedForDownload(mapID: int32): boolean;

/** Checks backend for latest static cache versions, download if out-of-date */
function CheckForUpdates(): void;

/** Fetches private maps visible to the user */
function FetchPrivateMaps(): void;
}

declare namespace SpectatorAPI {
Expand Down
17 changes: 17 additions & 0 deletions scripts/types-mom/events.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ interface GlobalEventNameMap {
/** Fired when the selected map has its data update */
MapSelector_SelectedDataUpdate: (mapData: MapCacheAPI.MapData) => void;

/**
* Fired when lives stats for the selected map have been updated.
* These are fetched from the backend when a map is selected, with a 60s cooldown. If we're outside the cooldown,
* the event fires once we get a response from backend, otherwise it fires immediately after
* `MapSelector_SelectedDataUpdate`.
*/
MapSelector_SelectedOnlineDataUpdate: (stats: import('common/web').MapStats) => void;

PanoramaComponent_SteamLobby_OnListUpdated: (lobbyList: import('common/online').GroupedLobbyLists) => void;

PanoramaComponent_SteamLobby_OnDataUpdated: (lobbyData: import('common/online').LobbyList) => void;
Expand Down Expand Up @@ -81,6 +89,15 @@ interface GlobalEventNameMap {

MapCache_SearchComplete: (success: boolean) => void;

/** Fired when a static map list is updated from backend */
MapCache_StaticCacheUpdate: (type: import('common/maps').MapListType, success: boolean) => void;

/** Fired when finished checking latest static cache versions */
MapCache_StaticCacheVersionChecked: (updatesNeeded: 0 | 1 | 2) => void;

/** Fired when the private map lists are updated from online */
MapCache_PrivateMapsUpdate: (success: boolean) => void;

MapDownload_Queued: (mapID: uint32, added: boolean) => void;

MapDownload_Start: (mapID: uint32, mapName: string) => void;
Expand Down