Add some GPS game tabs.

git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4446 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
Cory McWilliams 2023-09-04 19:21:00 +00:00
parent 35475defb5
commit 45231c6ede
2 changed files with 128 additions and 31 deletions

View File

@ -6,11 +6,9 @@
let g_data = ${data}; let g_data = ${data};
</script> </script>
<script src="script.js" type="module"></script> <script src="script.js" type="module"></script>
<link rel="stylesheet" href="leaflet.css"/>
<script src="leaflet.js"></script> <script src="leaflet.js"></script>
</head> </head>
<body style="color: #fff; display: flex; flex-flow: column; height: 100%; width: 100%; margin: 0; padding: 0"> <body style="color: #fff; display: flex; flex-flow: column; height: 100%; width: 100%; margin: 0; padding: 0">
<gg-app style="flex: 0 1 auto; overflow: scroll"></gg-app> <gg-app style="width: 100%; height: 100%"></gg-app>
<div id="map" style="flex: 1 0"></div>
</body> </body>
</html> </html>

View File

@ -24,6 +24,9 @@ class GgAppElement extends LitElement {
world: {type: Object}, world: {type: Object},
id: {type: String}, id: {type: String},
status: {type: Object}, status: {type: Object},
tab: {type: String},
url: {type: String},
currency: {type: Number},
}; };
} }
@ -37,7 +40,9 @@ class GgAppElement extends LitElement {
this.min_lon = Number.MAX_VALUE; this.min_lon = Number.MAX_VALUE;
this.max_lat = -Number.MAX_VALUE; this.max_lat = -Number.MAX_VALUE;
this.max_lon = -Number.MAX_VALUE; this.max_lon = -Number.MAX_VALUE;
this.focus = undefined;
this.status = undefined; this.status = undefined;
this.tab = 'map';
this.load().catch(function(e) { this.load().catch(function(e) {
console.log('load error', e); console.log('load error', e);
}); });
@ -46,6 +51,7 @@ class GgAppElement extends LitElement {
async load() { async load() {
console.log('load'); console.log('load');
this.user = await tfrpc.rpc.getUser(); this.user = await tfrpc.rpc.getUser();
this.url = (await tfrpc.rpc.url()).split('?')[0];
try { try {
await this.update_credentials(); await this.update_credentials();
} catch (e) { } catch (e) {
@ -84,8 +90,8 @@ class GgAppElement extends LitElement {
async get_activities_from_ssb() { async get_activities_from_ssb() {
this.status = {text: 'loading activities'}; this.status = {text: 'loading activities'};
this.loaded_activities = []; this.loaded_activities = [];
let blob_ids = await tfrpc.rpc.query(` let rows = await tfrpc.rpc.query(`
SELECT json_extract(mention.value, '$.link') AS blob_id SELECT messages.author, json_extract(mention.value, '$.link') AS blob_id
FROM messages_fts('"gg-activity"') FROM messages_fts('"gg-activity"')
JOIN messages ON messages.rowid = messages_fts.rowid, JOIN messages ON messages.rowid = messages_fts.rowid,
json_each(messages.content, '$.mentions') as mention json_each(messages.content, '$.mentions') as mention
@ -94,15 +100,26 @@ class GgAppElement extends LitElement {
ORDER BY messages.timestamp DESC ORDER BY messages.timestamp DESC
`, []); `, []);
this.status = {text: 'loading activity data'}; this.status = {text: 'loading activity data'};
let blobs = await this.promise_all(blob_ids.map(x => tfrpc.rpc.get_blob(x.blob_id)), 8); let authors = rows.map(x => x.author);
let blobs = await this.promise_all(rows.map(x => tfrpc.rpc.get_blob(x.blob_id)), 8);
this.status = {text: 'processing activity data'}; this.status = {text: 'processing activity data'};
for (let blob of blobs) { for (let [index, blob] of blobs.entries()) {
let activity;
try { try {
this.loaded_activities.push(JSON.parse(blob)); activity = JSON.parse(blob);
} catch { } catch {
this.loaded_activities.push(gpx_parse(blob)); activity = gpx_parse(blob);
}
if (activity) {
activity.author = authors[index];
this.loaded_activities.push(activity);
} }
} }
this.status = {text: 'calculating balance'};
rows = await tfrpc.rpc.query(`
SELECT count(*) AS currency FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-activity'
`, [this.id]);
this.currency = rows[0].currency;
this.status = undefined; this.status = undefined;
this.update_map(); this.update_map();
} }
@ -278,8 +295,14 @@ class GgAppElement extends LitElement {
} }
async update_map() { async update_map() {
let map = this.shadowRoot.getElementById('map');
if (!map || !this.loaded_activities.length) {
this.leaflet = undefined;
this.grid_layer = undefined;
return;
}
if (!this.leaflet) { if (!this.leaflet) {
this.leaflet = L.map('map', {attributionControl: false, maxZoom: 16, bounceAtZoomLimits: false}); this.leaflet = L.map(map, {attributionControl: false, maxZoom: 16, bounceAtZoomLimits: false});
this.leaflet.on({click: this.on_click.bind(this)}); this.leaflet.on({click: this.on_click.bind(this)});
} }
let self = this; let self = this;
@ -295,9 +318,6 @@ class GgAppElement extends LitElement {
let degrees = 360.0 / (2 ** coords.z); let degrees = 360.0 / (2 ** coords.z);
let ul = bounds.getNorthWest(); let ul = bounds.getNorthWest();
let lr = bounds.getSouthEast(); let lr = bounds.getSouthEast();
//context.fillText(JSON.stringify(coords), 0, 12);
//context.fillText(`${Math.round(ul.lat * 100) / 100} ${Math.round(ul.lng * 100) / 100}`, 0, 24);
//context.fillText(`${Math.round(lr.lat * 100) / 100} ${Math.round(lr.lng * 100) / 100}`, 0, 36);
let mini = document.createElement('canvas'); let mini = document.createElement('canvas');
mini.width = Math.floor(size.x / 16.0); mini.width = Math.floor(size.x / 16.0);
@ -307,7 +327,6 @@ class GgAppElement extends LitElement {
for (let activity of self.loaded_activities) { for (let activity of self.loaded_activities) {
self.draw_activity_to_tile(image_data, mini.width, mini.height, ul, lr, activity); self.draw_activity_to_tile(image_data, mini.width, mini.height, ul, lr, activity);
} }
//mini_context.putImageData(image_data, 0, 0);
for (let x = 0; x < mini.width; x++) { for (let x = 0; x < mini.width; x++) {
for (let y = 0; y < mini.height; y++) { for (let y = 0; y < mini.height; y++) {
let start = (y * mini.width + x) * 4; let start = (y * mini.width + x) * 4;
@ -333,11 +352,19 @@ class GgAppElement extends LitElement {
this.max_lat = Math.max(this.max_lat, bounds.max.lat); this.max_lat = Math.max(this.max_lat, bounds.max.lat);
this.max_lon = Math.max(this.max_lon, bounds.max.lng); this.max_lon = Math.max(this.max_lon, bounds.max.lng);
} }
if (this.focus) {
this.leaflet.fitBounds([
this.focus.min,
this.focus.max,
]);
this.focus = undefined;
} else {
this.leaflet.fitBounds([ this.leaflet.fitBounds([
[this.min_lat, this.min_lon], [this.min_lat, this.min_lon],
[this.max_lat, this.max_lon], [this.max_lat, this.max_lon],
]); ]);
} }
}
activity_to_color(activity) { activity_to_color(activity) {
let color = [0, 0, 0, 255]; let color = [0, 0, 0, 255];
@ -529,24 +556,53 @@ class GgAppElement extends LitElement {
input.click(); input.click();
} }
render() { updated() {
if (!this.user?.credentials?.session?.name) { this.update_map();
return html`<div>Please <a target="_top" href="/login">login</a> to Tilde Friends, first.</div>`;
} }
if (!this.strava?.access_token) {
let strava_url = `https://www.strava.com/oauth/authorize?client_id=${k_client_id}&redirect_uri=${k_redirect_url}&response_type=code&approval_prompt=auto&scope=activity%3Aread&state=${g_data.state}`; focus_map(activity) {
let bounds = this.activity_bounds(activity);
if (bounds.min.lat < bounds.max.lat &&
bounds.min.lng < bounds.max.lng) {
this.tab = 'map';
this.focus = bounds;
}
}
render_news() {
return html` return html`
<div style="display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%"> <ul>
${this.loaded_activities.map(x => html`
<li style="cursor: pointer" @click=${() => this.focus_map(x)}>${x.author} ${x.name ?? x.time}</li>
`)}
</ul>
`;
}
render_store() {
return html`
<h2>Store</h2>
<div><b>Your balance:</b> ${this.currency}</div>
`;
}
render() {
let header;
if (!this.user?.credentials?.session?.name) {
header = html`<div style="flex: 1 0">Please <a target="_top" href="/login?return=${this.url}">login</a> to Tilde Friends, first.</div>`;
} else if (!this.strava?.access_token) {
let strava_url = `https://www.strava.com/oauth/authorize?client_id=${k_client_id}&redirect_uri=${k_redirect_url}&response_type=code&approval_prompt=auto&scope=activity%3Aread&state=${g_data.state}`;
header = html`
<div style="flex: 1 0; display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
<div style="flex: 1 1">Please <a target="_top" href=${strava_url}>login</a> to Strava.</div> <div style="flex: 1 1">Please <a target="_top" href=${strava_url}>login</a> to Strava.</div>
<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.id}</span> <span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.id}</span>
<input type="button" value="📁" @click=${this.upload}></input> <input type="button" value="📁" @click=${this.upload}></input>
</div> </div>
`; `;
} } else {
header = html`
return html`
<div> <div>
<div style="display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%"> <div style="flex: 1 0; display: flex; flex-direction: row; align-items: center; gap: 1em; width: 100%">
<h1>Welcome, ${this.user.credentials.session.name}</h1> <h1>Welcome, ${this.user.credentials.session.name}</h1>
<span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.id}</span> <span style="font-size: xx-small; flex: 1 1; word-break: break-all">${this.id}</span>
<input type="button" value="📁" @click=${this.upload}></input> <input type="button" value="📁" @click=${this.upload}></input>
@ -555,5 +611,48 @@ class GgAppElement extends LitElement {
</div> </div>
`; `;
} }
let navigation = html`
<style>
#navigation input[type="button"] {
min-width: 3em;
min-height: 3em;
flex: 1 0;
font-size: large;
}
</style>
<div id="navigation" style="display: flex; flex-direction: row">
<input type="button" id="button_map" @click=${() => this.tab = 'map'} value="🗺Map"></input>
<input type="button" id="button_news" @click=${() => this.tab = 'news'} value="🏃News"></input>
<input type="button" id="button_friends" @click=${() => this.tab = 'friends'} value="👫Friends"></input>
<input type="button" id="button_store" @click=${() => this.tab = 'store'} value="🏗Store"></input>
</div>
`;
let content;
switch (this.tab) {
case 'map':
content = html`<div id="map" style="width: 100%; height: 100%"></div>`;
break;
case 'news':
content = this.render_news();
break;
case 'friends':
content = html`<div>Friends</div>`;
break;
case 'store':
content = this.render_store();
break;
}
return html`
<link rel="stylesheet" href="leaflet.css"/>
<div style="width: 100%; height: 100%; display: flex; flex-direction: column">
${header}
<div style="flex: 1 0; overflow: scroll">${content}</div>
${navigation}
</div>
`;
}
} }
customElements.define('gg-app', GgAppElement); customElements.define('gg-app', GgAppElement);