feat: extended map popup
This commit is contained in:
parent
fafbf587da
commit
a2f8b65399
|
@ -1,6 +1,11 @@
|
|||
import differenceInSeconds from 'date-fns/differenceInSeconds';
|
||||
import addSeconds from 'date-fns/addSeconds';
|
||||
import { ServerConfig, TWHelpClient, UnitInfo } from './lib/twhelp';
|
||||
import {
|
||||
Ennoblement,
|
||||
ServerConfig,
|
||||
TWHelpClient,
|
||||
UnitInfo,
|
||||
} from './lib/twhelp';
|
||||
import { Cache, InMemoryStorage } from './lib/cache';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
@ -27,7 +32,11 @@ class TWHelpConnector {
|
|||
private static readonly SERVER_CONFIG_CACHE_KEY =
|
||||
'extended_map_popup_server_config';
|
||||
private static readonly UNIT_INFO_CACHE_KEY = 'extended_map_popup_unit_info';
|
||||
private static readonly VILLAGE_CACHE_KEY_PREFIX =
|
||||
'extended_map_popup_village_';
|
||||
|
||||
private readonly localStorageCache = new Cache(localStorage);
|
||||
private readonly inMemoryCache = new Cache(new InMemoryStorage());
|
||||
constructor(
|
||||
private readonly client: TWHelpClient,
|
||||
private readonly version: string,
|
||||
|
@ -35,44 +44,44 @@ class TWHelpConnector {
|
|||
) {}
|
||||
|
||||
serverConfig() {
|
||||
return this.withCache(TWHelpConnector.SERVER_CONFIG_CACHE_KEY, 3600, () => {
|
||||
return this.client.serverConfig(this.version, this.server);
|
||||
});
|
||||
return this.localStorageCache.load(
|
||||
TWHelpConnector.SERVER_CONFIG_CACHE_KEY,
|
||||
3600,
|
||||
() => {
|
||||
return this.client.serverConfig(this.version, this.server);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
unitInfo() {
|
||||
return this.withCache(TWHelpConnector.UNIT_INFO_CACHE_KEY, 3600, () => {
|
||||
return this.client.unitInfo(this.version, this.server);
|
||||
});
|
||||
return this.localStorageCache.load(
|
||||
TWHelpConnector.UNIT_INFO_CACHE_KEY,
|
||||
3600,
|
||||
() => {
|
||||
return this.client.unitInfo(this.version, this.server);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async withCache<T>(
|
||||
key: string,
|
||||
ttl: number,
|
||||
func: () => Promise<T>
|
||||
): Promise<T> {
|
||||
try {
|
||||
const fromCache = JSON.parse(localStorage.getItem(key) ?? '');
|
||||
if (
|
||||
fromCache.date &&
|
||||
differenceInSeconds(new Date(), new Date(fromCache.date)) <= ttl
|
||||
) {
|
||||
return fromCache.data;
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (err) {}
|
||||
async latestEnnoblement(id: number, cacheOnly?: boolean) {
|
||||
const key = TWHelpConnector.VILLAGE_CACHE_KEY_PREFIX + id.toString();
|
||||
|
||||
const res = await func();
|
||||
if (cacheOnly) {
|
||||
return this.inMemoryCache.get<Ennoblement | null>(key);
|
||||
}
|
||||
|
||||
localStorage.setItem(
|
||||
key,
|
||||
JSON.stringify({
|
||||
data: res,
|
||||
date: new Date(),
|
||||
})
|
||||
);
|
||||
|
||||
return res;
|
||||
return this.inMemoryCache.load(key, 60, async () => {
|
||||
const ennoblements = await this.client.villageEnnoblements(
|
||||
this.version,
|
||||
this.server,
|
||||
id,
|
||||
{
|
||||
limit: 1,
|
||||
sort: ['createdAt:DESC'],
|
||||
}
|
||||
);
|
||||
return ennoblements.data.length > 0 ? ennoblements.data[0] : null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,14 +101,16 @@ class Popup {
|
|||
window.TWMap.popup.displayForVillage = this.display.bind(this);
|
||||
}
|
||||
|
||||
private loadVillage(villageId: string) {
|
||||
private async loadVillage(villageId: string) {
|
||||
window.TWMap.popup._loadVillage(villageId);
|
||||
console.log(villageId);
|
||||
|
||||
await this.displayLatestEnnoblement(parseInt(villageId), false);
|
||||
}
|
||||
|
||||
private display(village: { id: string }, x: number, y: number) {
|
||||
private async display(village: { id: string }, x: number, y: number) {
|
||||
window.TWMap.popup._displayForVillage(village, x, y);
|
||||
this.displayArrivalTimes(x, y);
|
||||
await this.displayLatestEnnoblement(parseInt(village.id), true);
|
||||
}
|
||||
|
||||
private displayArrivalTimes(x: number, y: number) {
|
||||
|
@ -148,6 +159,33 @@ class Popup {
|
|||
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
|
||||
private async displayLatestEnnoblement(id: number, cacheOnly: boolean) {
|
||||
const ennoblement = await this.connector.latestEnnoblement(id, cacheOnly);
|
||||
|
||||
const tbody = document.querySelector('#map_popup #info_content tbody');
|
||||
if (!(tbody instanceof HTMLTableSectionElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tr = tbody.querySelector('#extended_map_popup_ennobled_at');
|
||||
if (!tr) {
|
||||
tr = document.createElement('tr');
|
||||
tr.id = 'extended_map_popup_ennobled_at';
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
|
||||
tr.innerHTML = `
|
||||
<td>
|
||||
Ennobled at:
|
||||
</td>
|
||||
<td>
|
||||
${
|
||||
ennoblement ? new Date(ennoblement.createdAt).toLocaleString() : 'Never'
|
||||
}
|
||||
</td>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
class ExtendedMapPopup {
|
||||
|
@ -163,7 +201,6 @@ class ExtendedMapPopup {
|
|||
async run() {
|
||||
const config = await this.connector.serverConfig();
|
||||
const unitInfo = await this.connector.unitInfo();
|
||||
console.log(config, unitInfo);
|
||||
|
||||
new Popup(
|
||||
config,
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
import addSeconds from 'date-fns/addSeconds';
|
||||
import isAfter from 'date-fns/isAfter';
|
||||
|
||||
export interface Storage {
|
||||
getItem(key: string): string | null;
|
||||
setItem(key: string, value: string): void;
|
||||
removeItem(key: string): void;
|
||||
}
|
||||
|
||||
export class InMemoryStorage {
|
||||
private readonly data = new Map<string, string>();
|
||||
|
||||
getItem(key: string): string | null {
|
||||
const d = this.data.get(key);
|
||||
return d ? d : null;
|
||||
}
|
||||
|
||||
setItem(key: string, value: string) {
|
||||
this.data.set(key, value);
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
this.data.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
export class Cache {
|
||||
constructor(private readonly storage: Storage) {}
|
||||
|
||||
// load first tries to load data from storage, if there is no data or it is expired then it executes the given function and caches the result
|
||||
async load<T>(key: string, ttl: number, func: () => Promise<T>): Promise<T> {
|
||||
const data = this.get<T>(key);
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const res = await func();
|
||||
|
||||
this.storage.setItem(
|
||||
key,
|
||||
JSON.stringify({
|
||||
data: res,
|
||||
exp: addSeconds(new Date(), ttl),
|
||||
})
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
get<T>(key: string): T | null {
|
||||
try {
|
||||
const fromStorage = JSON.parse(this.storage.getItem(key) ?? '');
|
||||
if (!fromStorage.exp || !fromStorage.data) {
|
||||
return null;
|
||||
}
|
||||
if (isAfter(new Date(), new Date(fromStorage.exp))) {
|
||||
this.storage.removeItem(key);
|
||||
return null;
|
||||
}
|
||||
return fromStorage.data;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -208,24 +208,9 @@ export class TWHelpClient {
|
|||
id: number,
|
||||
queryParams?: ListTribeChangesQueryParams
|
||||
): Promise<ListResult<TribeChange>> {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (queryParams?.limit) {
|
||||
params.set('limit', queryParams.limit.toString());
|
||||
}
|
||||
|
||||
if (queryParams?.offset) {
|
||||
params.set('offset', queryParams.offset.toString());
|
||||
}
|
||||
|
||||
if (Array.isArray(queryParams?.sort)) {
|
||||
queryParams?.sort.forEach((s) => {
|
||||
params.append('sort', s);
|
||||
});
|
||||
}
|
||||
|
||||
const queryString = queryParams ? this.buildQueryString(queryParams) : '';
|
||||
const resp = await this.client.get(
|
||||
`/api/v1/versions/${version}/servers/${server}/players/${id}/tribe-changes?${params.toString()}`
|
||||
`/api/v1/versions/${version}/servers/${server}/players/${id}/tribe-changes?${queryString}`
|
||||
);
|
||||
return {
|
||||
data: resp.data.data,
|
||||
|
@ -239,24 +224,9 @@ export class TWHelpClient {
|
|||
id: number,
|
||||
queryParams?: ListEnnoblementsParams
|
||||
): Promise<ListResult<Ennoblement>> {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (queryParams?.limit) {
|
||||
params.set('limit', queryParams.limit.toString());
|
||||
}
|
||||
|
||||
if (queryParams?.offset) {
|
||||
params.set('offset', queryParams.offset.toString());
|
||||
}
|
||||
|
||||
if (Array.isArray(queryParams?.sort)) {
|
||||
queryParams?.sort.forEach((s) => {
|
||||
params.append('sort', s);
|
||||
});
|
||||
}
|
||||
|
||||
const queryString = queryParams ? this.buildQueryString(queryParams) : '';
|
||||
const resp = await this.client.get(
|
||||
`/api/v1/versions/${version}/servers/${server}/players/${id}/ennoblements?${params.toString()}`
|
||||
`/api/v1/versions/${version}/servers/${server}/players/${id}/ennoblements?${queryString}`
|
||||
);
|
||||
return {
|
||||
data: resp.data.data,
|
||||
|
@ -270,24 +240,9 @@ export class TWHelpClient {
|
|||
id: number,
|
||||
queryParams?: ListPlayerSnapshotsParams
|
||||
): Promise<ListResult<PlayerSnapshot>> {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (queryParams?.limit) {
|
||||
params.set('limit', queryParams.limit.toString());
|
||||
}
|
||||
|
||||
if (queryParams?.offset) {
|
||||
params.set('offset', queryParams.offset.toString());
|
||||
}
|
||||
|
||||
if (Array.isArray(queryParams?.sort)) {
|
||||
queryParams?.sort.forEach((s) => {
|
||||
params.append('sort', s);
|
||||
});
|
||||
}
|
||||
|
||||
const queryString = queryParams ? this.buildQueryString(queryParams) : '';
|
||||
const resp = await this.client.get(
|
||||
`/api/v1/versions/${version}/servers/${server}/players/${id}/history?${params.toString()}`
|
||||
`/api/v1/versions/${version}/servers/${server}/players/${id}/history?${queryString}`
|
||||
);
|
||||
return {
|
||||
data: resp.data.data,
|
||||
|
@ -301,18 +256,9 @@ export class TWHelpClient {
|
|||
id: number,
|
||||
queryParams?: ListPlayerOtherServersParams
|
||||
): Promise<ListResult<PlayerWithServer>> {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (queryParams?.limit) {
|
||||
params.set('limit', queryParams.limit.toString());
|
||||
}
|
||||
|
||||
if (queryParams?.offset) {
|
||||
params.set('offset', queryParams.offset.toString());
|
||||
}
|
||||
|
||||
const queryString = queryParams ? this.buildQueryString(queryParams) : '';
|
||||
const resp = await this.client.get(
|
||||
`/api/v1/versions/${version}/servers/${server}/players/${id}/other-servers?${params.toString()}`
|
||||
`/api/v1/versions/${version}/servers/${server}/players/${id}/other-servers?${queryString}`
|
||||
);
|
||||
return {
|
||||
data: resp.data.data,
|
||||
|
@ -320,6 +266,46 @@ export class TWHelpClient {
|
|||
};
|
||||
}
|
||||
|
||||
public async villageEnnoblements(
|
||||
version: string,
|
||||
server: string,
|
||||
id: number,
|
||||
queryParams?: ListEnnoblementsParams
|
||||
): Promise<ListResult<Ennoblement>> {
|
||||
const queryString = queryParams ? this.buildQueryString(queryParams) : '';
|
||||
const resp = await this.client.get(
|
||||
`/api/v1/versions/${version}/servers/${server}/villages/${id}/ennoblements?${queryString}`
|
||||
);
|
||||
return {
|
||||
data: resp.data.data,
|
||||
total: this.parseTotal(resp.headers),
|
||||
};
|
||||
}
|
||||
|
||||
private buildQueryString(
|
||||
queryParams: Record<string, string | boolean | number | Date | string[]>
|
||||
): string {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
for (const [name, val] of Object.entries(queryParams)) {
|
||||
if (Array.isArray(val)) {
|
||||
val.forEach((s) => {
|
||||
params.append(name, s);
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (val instanceof Date) {
|
||||
params.set(name, val.toISOString());
|
||||
continue;
|
||||
}
|
||||
|
||||
params.set(name, val.toString());
|
||||
}
|
||||
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
private parseTotal(
|
||||
headers: RawAxiosResponseHeaders | AxiosResponseHeaders
|
||||
): number {
|
||||
|
|
Loading…
Reference in New Issue