forked from cory/tildefriends
		
	Proof of concept of building emojis.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@4464 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
		| @@ -8,6 +8,7 @@ tfrpc.register(async function createIdentity() { | ||||
| 	return ssb.createIdentity(); | ||||
| }); | ||||
| tfrpc.register(async function appendMessage(id, message) { | ||||
| 	print('APPEND', JSON.stringify(message)); | ||||
| 	return ssb.appendMessageWithIdentity(id, message); | ||||
| }); | ||||
| tfrpc.register(function url() { | ||||
|   | ||||
| @@ -9,6 +9,6 @@ | ||||
| 		<script src="leaflet.js"></script> | ||||
| 	</head> | ||||
| 	<body style="color: #fff; display: flex; flex-flow: column; height: 100%; width: 100%; margin: 0; padding: 0"> | ||||
| 		<gg-app style="width: 100%; height: 100%"></gg-app> | ||||
| 		<gg-app style="width: 100%; height: 100%" id="ggapp"></gg-app> | ||||
| 	</body> | ||||
| </html> | ||||
| @@ -14,6 +14,12 @@ const k_color_pavement = [32, 32, 32, 255]; | ||||
| const k_color_grass = [0, 255, 0, 255]; | ||||
| const k_color_default = [128, 128, 128, 255]; | ||||
|  | ||||
| const k_store = { | ||||
| 	'🦞': 15, | ||||
| 	'🛶': 10, | ||||
| 	'🏠': 10, | ||||
| }; | ||||
|  | ||||
| class GgAppElement extends LitElement { | ||||
| 	static get properties() { | ||||
| 		return { | ||||
| @@ -22,11 +28,12 @@ class GgAppElement extends LitElement { | ||||
| 			activities: {type: Array}, | ||||
| 			activity: {type: Object}, | ||||
| 			world: {type: Object}, | ||||
| 			id: {type: String}, | ||||
| 			whoami: {type: String}, | ||||
| 			status: {type: Object}, | ||||
| 			tab: {type: String}, | ||||
| 			url: {type: String}, | ||||
| 			currency: {type: Number}, | ||||
| 			to_build: {type: String}, | ||||
| 		}; | ||||
| 	} | ||||
|  | ||||
| @@ -35,6 +42,7 @@ class GgAppElement extends LitElement { | ||||
| 		this.activities = []; | ||||
| 		this.activity = {}; | ||||
| 		this.loaded_activities = []; | ||||
| 		this.placed_emojis = []; | ||||
| 		this.strava = {}; | ||||
| 		this.min_lat = Number.MAX_VALUE; | ||||
| 		this.min_lon = Number.MAX_VALUE; | ||||
| @@ -63,7 +71,7 @@ class GgAppElement extends LitElement { | ||||
| 			console.log('update_activities failed', e); | ||||
| 		} | ||||
| 		await this.acquire_ssb_identity(); | ||||
| 		if (this.id && this.activities?.length) { | ||||
| 		if (this.whoami && this.activities?.length) { | ||||
| 			await this.sync_activities(); | ||||
| 		} | ||||
| 		await this.get_activities_from_ssb(); | ||||
| @@ -118,8 +126,30 @@ class GgAppElement extends LitElement { | ||||
| 		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.whoami]); | ||||
| 		let currency = rows[0].currency; | ||||
| 		rows = await tfrpc.rpc.query(` | ||||
| 			SELECT SUM(json_extract(content, '$.cost')) AS cost FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'gg-place' | ||||
| 		`, [this.whoami]); | ||||
| 		let spent = rows[0].cost; | ||||
| 		this.currency = currency - spent; | ||||
| 		this.status = {text: 'getting placed emojis'}; | ||||
| 		rows = await tfrpc.rpc.query(` | ||||
| 			SELECT messages.content | ||||
| 			FROM messages_fts('"gg-place"') | ||||
| 			JOIN messages ON messages.rowid = messages_fts.rowid | ||||
| 			WHERE json_extract(messages.content, '$.type') = 'gg-place' | ||||
| 			ORDER BY messages.timestamp | ||||
| 		`); | ||||
| 		for (let row of rows) { | ||||
| 			console.log(row.content); | ||||
| 			let content = JSON.parse(row.content); | ||||
| 			this.placed_emojis.push({ | ||||
| 				position: content.position, | ||||
| 				emoji: content.emoji, | ||||
| 			}); | ||||
| 		} | ||||
| 		console.log(this.placed_emojis); | ||||
| 		this.status = undefined; | ||||
| 		this.update_map(); | ||||
| 	} | ||||
| @@ -137,7 +167,7 @@ class GgAppElement extends LitElement { | ||||
| 			SELECT from_strava.value FROM json_each(?) AS from_strava | ||||
| 			LEFT OUTER JOIN my_activities ON from_strava.value = my_activities.url | ||||
| 			WHERE my_activities.url IS NULL | ||||
| 			`, [this.id, JSON.stringify(ids)]); | ||||
| 			`, [this.whoami, JSON.stringify(ids)]); | ||||
| 		console.log('missing = ', missing); | ||||
| 		for (let [index, row] of missing.entries()) { | ||||
| 			this.status = {text: 'syncing from strava', value: index, max: missing.length}; | ||||
| @@ -163,7 +193,7 @@ class GgAppElement extends LitElement { | ||||
| 					} | ||||
| 				], | ||||
| 			}; | ||||
| 			await tfrpc.rpc.appendMessage(this.id, message); | ||||
| 			await tfrpc.rpc.appendMessage(this.whoami, message); | ||||
| 		} | ||||
| 		this.status = undefined; | ||||
| 	} | ||||
| @@ -182,16 +212,16 @@ class GgAppElement extends LitElement { | ||||
| 			ORDER BY timestamp DESC limit 1 | ||||
| 			`, [JSON.stringify(ids)])).map(row => row.author) : []; | ||||
| 		if (!players.length) { | ||||
| 			this.id = await tfrpc.rpc.createIdentity(); | ||||
| 			if (this.id) { | ||||
| 				await tfrpc.rpc.appendMessage(this.id, { | ||||
| 			this.whoami = await tfrpc.rpc.createIdentity(); | ||||
| 			if (this.whoami) { | ||||
| 				await tfrpc.rpc.appendMessage(this.whoami, { | ||||
| 					type: 'gg-player', | ||||
| 					active: true, | ||||
| 				}); | ||||
| 			} | ||||
| 		} else { | ||||
| 			players.sort(); | ||||
| 			this.id = players[0]; | ||||
| 			this.whoami = players[0]; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| @@ -294,6 +324,57 @@ class GgAppElement extends LitElement { | ||||
| 			.openOn(this.leaflet); | ||||
| 	} | ||||
|  | ||||
| 	async build() { | ||||
| 		if (this.popup) { | ||||
| 			this.popup.remove(); | ||||
| 		} | ||||
| 		if (!this.marker) { | ||||
| 			return; | ||||
| 		} | ||||
| 		let latlng = this.marker.getLatLng(); | ||||
|  | ||||
| 		let cost = k_store[this.to_build]; | ||||
| 		if (cost > this.currency) { | ||||
| 			alert('Insufficient funds.'); | ||||
| 			return; | ||||
| 		} | ||||
| 		let message = { | ||||
| 			type: 'gg-place', | ||||
| 			position: {lat: latlng.lat, lng: latlng.lng}, | ||||
| 			emoji: this.to_build, | ||||
| 			cost: cost, | ||||
| 		}; | ||||
| 		let id = await tfrpc.rpc.appendMessage(this.whoami, message); | ||||
| 		this.marker.remove(); | ||||
| 		this.placed_emojis.push({ | ||||
| 			position: {lat: latlng.lat, lng: latlng.lng}, | ||||
| 			emoji: this.to_build, | ||||
| 		}); | ||||
| 		this.currency -= cost; | ||||
| 		return this.update_map(); | ||||
| 	} | ||||
|  | ||||
| 	on_marker_click(event) { | ||||
| 		this.popup = L.popup() | ||||
| 			.setLatLng(event.latlng) | ||||
| 			.setContent(` | ||||
| 				${this.to_build} (-${k_store[this.to_build]}) <input type="button" value="Build" onclick="document.getElementById('ggapp').build()"></input> | ||||
| 			`) | ||||
| 			.openOn(this.leaflet); | ||||
| 	} | ||||
|  | ||||
| 	on_mouse_down(event) { | ||||
| 		if (this.marker) { | ||||
| 			this.marker.remove(); | ||||
| 			this.marker = undefined; | ||||
| 		} | ||||
|  | ||||
| 		if (this.to_build) { | ||||
| 			this.marker = L.marker(event.latlng, {icon: L.divIcon({className: 'build-icon'}), draggable: true}).addTo(this.leaflet); | ||||
| 			this.marker.on({click: this.on_marker_click.bind(this)}); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async update_map() { | ||||
| 		let map = this.shadowRoot.getElementById('map'); | ||||
| 		if (!map || !this.loaded_activities.length) { | ||||
| @@ -304,6 +385,7 @@ class GgAppElement extends LitElement { | ||||
| 		if (!this.leaflet) { | ||||
| 			this.leaflet = L.map(map, {attributionControl: false, maxZoom: 16, bounceAtZoomLimits: false}); | ||||
| 			this.leaflet.on({contextmenu: this.on_click.bind(this)}); | ||||
| 			this.leaflet.on({click: this.on_mouse_down.bind(this)}); | ||||
| 		} | ||||
| 		let self = this; | ||||
| 		let grid_layer = L.GridLayer.extend({ | ||||
| @@ -336,6 +418,16 @@ class GgAppElement extends LitElement { | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 				for (let placed of self.placed_emojis) { | ||||
| 					let position = self.leaflet.options.crs.latLngToPoint(placed.position, coords.z); | ||||
| 					let tile_x = Math.floor(position.x / size.x); | ||||
| 					let tile_y = Math.floor(position.y / size.y); | ||||
| 					position.x = position.x - tile_x * size.x; | ||||
| 					position.y = position.y - tile_y * size.y; | ||||
| 					if (tile_x == coords.x && tile_y == coords.y) { | ||||
| 						context.fillText(placed.emoji, position.x, position.y + 10); | ||||
| 					} | ||||
| 				} | ||||
| 				return tile; | ||||
| 			} | ||||
| 		}); | ||||
| @@ -539,8 +631,8 @@ class GgAppElement extends LitElement { | ||||
| 					} | ||||
| 				], | ||||
| 			}; | ||||
| 			console.log('id =', this.id, 'message = ', message); | ||||
| 			let id = await tfrpc.rpc.appendMessage(this.id, message); | ||||
| 			console.log('id =', this.whoami, 'message = ', message); | ||||
| 			let id = await tfrpc.rpc.appendMessage(this.whoami, message); | ||||
| 			console.log('appended message', id); | ||||
| 			alert('Activity uploaded.'); | ||||
| 			await this.get_activities_from_ssb(); | ||||
| @@ -579,10 +671,20 @@ class GgAppElement extends LitElement { | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	render_store_item(item) { | ||||
| 		let [emoji, cost] = item; | ||||
| 		return html` | ||||
| 			<div> | ||||
| 				<input type="button" value="${emoji}" @click=${() => this.to_build = emoji}></input> ${cost} ${emoji == this.to_build ? '<-- Will be built next' : undefined} | ||||
| 			</div> | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| 	render_store() { | ||||
| 		return html` | ||||
| 			<h2>Store</h2> | ||||
| 			<div><b>Your balance:</b> ${this.currency}</div> | ||||
| 			${Object.entries(k_store).map(this.render_store_item.bind(this))} | ||||
| 		`; | ||||
| 	} | ||||
|  | ||||
| @@ -595,7 +697,7 @@ class GgAppElement extends LitElement { | ||||
| 			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> | ||||
| 					<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.whoami}</span> | ||||
| 					<input type="button" value="📁" @click=${this.upload}></input> | ||||
| 				</div> | ||||
| 			`; | ||||
| @@ -604,7 +706,7 @@ class GgAppElement extends LitElement { | ||||
| 				<div> | ||||
| 					<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> | ||||
| 						<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.whoami}</span> | ||||
| 						<input type="button" value="📁" @click=${this.upload}></input> | ||||
| 					</div> | ||||
| 					<h3 ?hidden=${!this.status?.text}>${this.status?.text} <progress ?hidden=${!this.status?.max} value=${this.status?.value} max=${this.status?.max}>${this.status?.value}</progress></h3> | ||||
| @@ -646,6 +748,12 @@ class GgAppElement extends LitElement { | ||||
| 		} | ||||
|  | ||||
| 		return html` | ||||
| 			<style> | ||||
| 			.build-icon::before { | ||||
| 				content: '📍'; | ||||
| 				border: 2px solid red; | ||||
| 			} | ||||
| 			</style> | ||||
| 			<link rel="stylesheet" href="leaflet.css"/> | ||||
| 			<div style="width: 100%; height: 100%; display: flex; flex-direction: column"> | ||||
| 				${header} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user