ssb: Hint at follow depth with profile image shape. Also, reload follow information the same way we re-determine channel unread status. Let's see if this feels good.
This commit is contained in:
		| @@ -1,5 +1,5 @@ | ||||
| { | ||||
| 	"type": "tildefriends-app", | ||||
| 	"emoji": "🦀", | ||||
| 	"previous": "&UOHm9X6pa1L0G0QdtiIqV/UW4vH/4rTA4i30IPqMzeQ=.sha256" | ||||
| 	"previous": "&5YZ9ja1NymBZsDaHIrpgZCzn95R34Y1RQug6gy4EULU=.sha256" | ||||
| } | ||||
|   | ||||
| @@ -38,8 +38,8 @@ class TfElement extends LitElement { | ||||
| 		this.channels = []; | ||||
| 		this.channels_unread = {}; | ||||
| 		this.channels_latest = {}; | ||||
| 		this.loading_channels_latest = 0; | ||||
| 		this.loading_channels_latest_scheduled = 0; | ||||
| 		this.loading_latest = 0; | ||||
| 		this.loading_latest_scheduled = 0; | ||||
| 		tfrpc.rpc.getBroadcasts().then((b) => { | ||||
| 			self.broadcasts = b || []; | ||||
| 		}); | ||||
| @@ -147,7 +147,8 @@ class TfElement extends LitElement { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async fetch_about(ids, users) { | ||||
| 	async fetch_about(following, users) { | ||||
| 		let ids = Object.keys(following).sort(); | ||||
| 		const k_cache_version = 1; | ||||
| 		let cache = await tfrpc.rpc.databaseGet('about'); | ||||
| 		let original_cache = cache; | ||||
| @@ -225,7 +226,11 @@ class TfElement extends LitElement { | ||||
| 		} | ||||
| 		users = users || {}; | ||||
| 		for (let id of Object.keys(cache.about)) { | ||||
| 			users[id] = Object.assign(users[id] || {}, cache.about[id]); | ||||
| 			users[id] = Object.assign( | ||||
| 				{follow_depth: following[id]?.d}, | ||||
| 				users[id] || {}, | ||||
| 				cache.about[id] | ||||
| 			); | ||||
| 		} | ||||
| 		return Object.assign({}, users); | ||||
| 	} | ||||
| @@ -248,7 +253,7 @@ class TfElement extends LitElement { | ||||
| 				this.load_channels(); | ||||
| 			} | ||||
| 		} | ||||
| 		this.schedule_load_channels_latest(); | ||||
| 		this.schedule_load_latest(); | ||||
| 	} | ||||
|  | ||||
| 	async _handle_whoami_changed(event) { | ||||
| @@ -307,7 +312,6 @@ class TfElement extends LitElement { | ||||
| 				ranges.push([i, Math.min(i + k_chunk_size, latest), true]); | ||||
| 			} | ||||
| 		} | ||||
| 		console.log(cache); | ||||
| 		for (let range of ranges) { | ||||
| 			let messages = await tfrpc.rpc.query( | ||||
| 				` | ||||
| @@ -340,112 +344,111 @@ class TfElement extends LitElement { | ||||
| 				JSON.stringify(cache) | ||||
| 			); | ||||
| 		} | ||||
| 		console.log(cache); | ||||
| 		return cache.latest; | ||||
| 	} | ||||
|  | ||||
| 	async load_channels_latest(following) { | ||||
| 		this.loading_channels_latest++; | ||||
| 		try { | ||||
| 			let start_time = new Date(); | ||||
| 			let latest_private = this.get_latest_private(following); | ||||
| 			let channels = await tfrpc.rpc.query( | ||||
| 				` | ||||
| 				SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 				JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value | ||||
| 				JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 				WHERE | ||||
| 					messages.content ->> 'type' = 'post' AND | ||||
| 					messages.content ->> 'root' IS NULL AND | ||||
| 					messages.author != ?4 | ||||
| 				GROUP by channel | ||||
| 				UNION | ||||
| 				SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 				JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 				WHERE | ||||
| 					messages.content ->> 'type' = 'post' AND | ||||
| 					messages.content ->> 'root' IS NULL AND | ||||
| 					messages.author != ?4 | ||||
| 				UNION | ||||
| 				SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3) | ||||
| 				JOIN messages ON messages.rowid = messages_fts.rowid | ||||
| 				JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 				WHERE messages.author != ?4 | ||||
| 			`, | ||||
| 				[ | ||||
| 					JSON.stringify(this.channels), | ||||
| 					JSON.stringify(following), | ||||
| 					'"' + this.whoami.replace('"', '""') + '"', | ||||
| 					this.whoami, | ||||
| 				] | ||||
| 			); | ||||
| 			this.channels_latest = Object.fromEntries( | ||||
| 				channels.map((x) => [x.channel, x.rowid]) | ||||
| 			); | ||||
| 			console.log('latest', this.channels_latest); | ||||
| 			console.log('unread', this.channels_unread); | ||||
| 			console.log('channels took', (new Date() - start_time) / 1000.0); | ||||
| 			let self = this; | ||||
| 			latest_private.then(function (latest) { | ||||
| 				self.channels_latest = Object.assign({}, self.channels_latest, { | ||||
| 					'🔐': latest, | ||||
| 				}); | ||||
| 				console.log('private took', (new Date() - start_time) / 1000.0); | ||||
| 		let start_time = new Date(); | ||||
| 		let latest_private = this.get_latest_private(following); | ||||
| 		let channels = await tfrpc.rpc.query( | ||||
| 			` | ||||
| 			SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 			JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value | ||||
| 			JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 			WHERE | ||||
| 				messages.content ->> 'type' = 'post' AND | ||||
| 				messages.content ->> 'root' IS NULL AND | ||||
| 				messages.author != ?4 | ||||
| 			GROUP by channel | ||||
| 			UNION | ||||
| 			SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages | ||||
| 			JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 			WHERE | ||||
| 				messages.content ->> 'type' = 'post' AND | ||||
| 				messages.content ->> 'root' IS NULL AND | ||||
| 				messages.author != ?4 | ||||
| 			UNION | ||||
| 			SELECT '@' AS channel, MAX(messages.rowid) AS rowid FROM messages_fts(?3) | ||||
| 			JOIN messages ON messages.rowid = messages_fts.rowid | ||||
| 			JOIN json_each(?2) AS following ON messages.author = following.value | ||||
| 			WHERE messages.author != ?4 | ||||
| 		`, | ||||
| 			[ | ||||
| 				JSON.stringify(this.channels), | ||||
| 				JSON.stringify(following), | ||||
| 				'"' + this.whoami.replace('"', '""') + '"', | ||||
| 				this.whoami, | ||||
| 			] | ||||
| 		); | ||||
| 		this.channels_latest = Object.fromEntries( | ||||
| 			channels.map((x) => [x.channel, x.rowid]) | ||||
| 		); | ||||
| 		console.log('latest', this.channels_latest); | ||||
| 		console.log('unread', this.channels_unread); | ||||
| 		console.log('channels took', (new Date() - start_time) / 1000.0); | ||||
| 		let self = this; | ||||
| 		latest_private.then(function (latest) { | ||||
| 			self.channels_latest = Object.assign({}, self.channels_latest, { | ||||
| 				'🔐': latest, | ||||
| 			}); | ||||
| 		} finally { | ||||
| 			this.loading_channels_latest--; | ||||
| 		} | ||||
| 			console.log('private took', (new Date() - start_time) / 1000.0); | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	_schedule_load_channels_latest_timer() { | ||||
| 		--this.loading_channels_latest_scheduled; | ||||
| 		this.schedule_load_channels_latest(); | ||||
| 	_schedule_load_latest_timer() { | ||||
| 		--this.loading_latest_scheduled; | ||||
| 		this.schedule_load_latest(); | ||||
| 	} | ||||
|  | ||||
| 	schedule_load_channels_latest() { | ||||
| 		if (!this.loading_channels_latest) { | ||||
| 	schedule_load_latest() { | ||||
| 		if (!this.loading_latest) { | ||||
| 			this.shadowRoot.getElementById('tf-tab-news')?.load_latest(); | ||||
| 			this.load_channels_latest(this.following); | ||||
| 		} else if (!this.loading_channels_latest_scheduled) { | ||||
| 			this.loading_channels_latest_scheduled++; | ||||
| 			setTimeout(this._schedule_load_channels_latest_timer.bind(this), 5000); | ||||
| 			this.load(); | ||||
| 		} else if (!this.loading_latest_scheduled) { | ||||
| 			this.loading_latest_scheduled++; | ||||
| 			setTimeout(this._schedule_load_latest_timer.bind(this), 5000); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	async load() { | ||||
| 		let start_time = new Date(); | ||||
| 		let whoami = this.whoami; | ||||
| 		let following = await tfrpc.rpc.following([whoami], 2); | ||||
| 		let users = {}; | ||||
| 		let by_count = []; | ||||
| 		for (let [id, v] of Object.entries(following)) { | ||||
| 			users[id] = { | ||||
| 				following: v.of, | ||||
| 				blocking: v.ob, | ||||
| 				followed: v.if, | ||||
| 				blocked: v.ib, | ||||
| 			}; | ||||
| 			by_count.push({count: v.of, id: id}); | ||||
| 		this.loading_latest = true; | ||||
| 		try { | ||||
| 			let start_time = new Date(); | ||||
| 			let whoami = this.whoami; | ||||
| 			let following = await tfrpc.rpc.following([whoami], 2); | ||||
| 			let users = {}; | ||||
| 			let by_count = []; | ||||
| 			for (let [id, v] of Object.entries(following)) { | ||||
| 				users[id] = { | ||||
| 					following: v.of, | ||||
| 					blocking: v.ob, | ||||
| 					followed: v.if, | ||||
| 					blocked: v.ib, | ||||
| 				}; | ||||
| 				by_count.push({count: v.of, id: id}); | ||||
| 			} | ||||
| 			this.load_channels_latest(Object.keys(following)); | ||||
| 			this.channels_unread = JSON.parse( | ||||
| 				(await tfrpc.rpc.databaseGet('unread')) ?? '{}' | ||||
| 			); | ||||
| 			this.following = Object.keys(following); | ||||
| 			users = await this.fetch_about(following, users); | ||||
| 			console.log( | ||||
| 				'about took', | ||||
| 				(new Date() - start_time) / 1000.0, | ||||
| 				'seconds for', | ||||
| 				Object.keys(users).length, | ||||
| 				'users' | ||||
| 			); | ||||
| 			this.users = users; | ||||
| 			console.log( | ||||
| 				`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}` | ||||
| 			); | ||||
| 			this.whoami = whoami; | ||||
| 			this.loaded = whoami; | ||||
| 		} finally { | ||||
| 			this.loading_latest = false; | ||||
| 		} | ||||
| 		this.load_channels_latest(Object.keys(following)); | ||||
| 		this.channels_unread = JSON.parse( | ||||
| 			(await tfrpc.rpc.databaseGet('unread')) ?? '{}' | ||||
| 		); | ||||
| 		users = await this.fetch_about(Object.keys(following).sort(), users); | ||||
| 		console.log( | ||||
| 			'about took', | ||||
| 			(new Date() - start_time) / 1000.0, | ||||
| 			'seconds for', | ||||
| 			Object.keys(users).length, | ||||
| 			'users' | ||||
| 		); | ||||
| 		this.following = Object.keys(following); | ||||
| 		this.users = users; | ||||
| 		console.log( | ||||
| 			`load finished ${whoami} => ${this.whoami} in ${(new Date() - start_time) / 1000}` | ||||
| 		); | ||||
| 		this.whoami = whoami; | ||||
| 		this.loaded = whoami; | ||||
| 	} | ||||
|  | ||||
| 	channel_set_unread(event) { | ||||
|   | ||||
| @@ -589,9 +589,7 @@ class TfMessageElement extends LitElement { | ||||
| 				let image; | ||||
| 				let description; | ||||
| 				if (content.name !== undefined) { | ||||
| 					name = html`<div> | ||||
| 						<b>Name:</b> ${content.name} | ||||
| 					</div>`; | ||||
| 					name = html`<div><b>Name:</b> ${content.name}</div>`; | ||||
| 				} | ||||
| 				if (content.image !== undefined) { | ||||
| 					image = html` | ||||
| @@ -600,18 +598,14 @@ class TfMessageElement extends LitElement { | ||||
| 				} | ||||
| 				if (content.description !== undefined) { | ||||
| 					description = html` | ||||
| 						<div | ||||
| 							style="flex: 1 0 50%; overflow-wrap: anywhere" | ||||
| 						> | ||||
| 						<div style="flex: 1 0 50%; overflow-wrap: anywhere"> | ||||
| 							<div>${unsafeHTML(tfutils.markdown(content.description))}</div> | ||||
| 						</div> | ||||
| 					`; | ||||
| 				} | ||||
| 				let update = | ||||
| 					content.about == this.message.author | ||||
| 						? html`<div style="font-weight: bold"> | ||||
| 								Updated profile. | ||||
| 							</div>` | ||||
| 						? html`<div style="font-weight: bold">Updated profile.</div>` | ||||
| 						: html`<div style="font-weight: bold"> | ||||
| 								Updated profile for | ||||
| 								<tf-user id=${content.about} .users=${this.users}></tf-user>. | ||||
| @@ -759,10 +753,10 @@ class TfMessageElement extends LitElement { | ||||
| 				return this.render_small_frame(html` | ||||
| 					<div class="w3-container"> | ||||
| 						<p> | ||||
| 						${content.subscribed ? 'subscribed to' : 'unsubscribed from'} | ||||
| 						<a href=${'#' + encodeURIComponent('#' + content.channel)} | ||||
| 							>#${content.channel}</a | ||||
| 						> | ||||
| 							${content.subscribed ? 'subscribed to' : 'unsubscribed from'} | ||||
| 							<a href=${'#' + encodeURIComponent('#' + content.channel)} | ||||
| 								>#${content.channel}</a | ||||
| 							> | ||||
| 						</p> | ||||
| 					</div> | ||||
| 				`); | ||||
|   | ||||
| @@ -142,14 +142,15 @@ class TfTabConnectionsElement extends LitElement { | ||||
| 			}, {}) | ||||
| 		); | ||||
| 		return html` | ||||
| 			${connection.connected ? html` | ||||
| 				<button | ||||
| 					class="w3-button w3-theme-d1" | ||||
| 					@click=${() => tfrpc.rpc.closeConnection(connection.id)} | ||||
| 				> | ||||
| 					Close | ||||
| 				</button> | ||||
| 				` | ||||
| 			${connection.connected | ||||
| 				? html` | ||||
| 						<button | ||||
| 							class="w3-button w3-theme-d1" | ||||
| 							@click=${() => tfrpc.rpc.closeConnection(connection.id)} | ||||
| 						> | ||||
| 							Close | ||||
| 						</button> | ||||
| 					` | ||||
| 				: undefined} | ||||
| 			${connection.flags.one_shot ? '🔃' : undefined} | ||||
| 			<tf-user id=${connection.id} .users=${this.users}></tf-user> | ||||
| @@ -270,16 +271,19 @@ class TfTabConnectionsElement extends LitElement { | ||||
| 				<div class="w3-container"> | ||||
| 					${this.identities.map( | ||||
| 						(x) => | ||||
| 							html`<div class="w3-tag w3-round w3-theme-l3" style="padding: 4px; margin: 2px; max-width: 100%; text-wrap: nowrap; overflow: hidden"> | ||||
| 							html`<div | ||||
| 								class="w3-tag w3-round w3-theme-l3" | ||||
| 								style="padding: 4px; margin: 2px; max-width: 100%; text-wrap: nowrap; overflow: hidden" | ||||
| 							> | ||||
| 								${x == this.server_identity | ||||
| 									? html`<div class="w3-tag w3-medium w3-round w3-theme-l1" | ||||
| 											>🖥 local server</div | ||||
| 										>` | ||||
| 									? html`<div class="w3-tag w3-medium w3-round w3-theme-l1"> | ||||
| 											🖥 local server | ||||
| 										</div>` | ||||
| 									: undefined} | ||||
| 								${this.my_identities.indexOf(x) != -1 | ||||
| 									? html`<div class="w3-tag w3-medium w3-round w3-theme-d1" | ||||
| 											>😎 you</div | ||||
| 										>` | ||||
| 									? html`<div class="w3-tag w3-medium w3-round w3-theme-d1"> | ||||
| 											😎 you | ||||
| 										</div>` | ||||
| 									: undefined} | ||||
| 								<tf-user id=${x} .users=${this.users}></tf-user> | ||||
| 							</div>` | ||||
|   | ||||
| @@ -19,8 +19,10 @@ class TfUserElement extends LitElement { | ||||
| 	} | ||||
|  | ||||
| 	render() { | ||||
| 		let user = this.users[this.id]; | ||||
| 		let shape = user && user.follow_depth >= 2 ? 'w3-circle' : 'w3-round'; | ||||
| 		let image = html`<span | ||||
| 			class="w3-theme-light w3-circle" | ||||
| 			class=${'w3-theme-l4 ' + shape} | ||||
| 			style="display: inline-block; width: 2em; height: 2em; text-align: center; line-height: 2em" | ||||
| 			>?</span | ||||
| 		>`; | ||||
| @@ -29,13 +31,13 @@ class TfUserElement extends LitElement { | ||||
| 			>${name !== undefined ? name : this.id}</a | ||||
| 		>`; | ||||
|  | ||||
| 		if (this.users[this.id]) { | ||||
| 			let image_link = this.users[this.id].image; | ||||
| 		if (user) { | ||||
| 			let image_link = user.image; | ||||
| 			image_link = | ||||
| 				typeof image_link == 'string' ? image_link : image_link?.link; | ||||
| 			if (image_link !== undefined) { | ||||
| 				image = html`<img | ||||
| 					class="w3-circle" | ||||
| 					class=${'w3-theme-l4 ' + shape} | ||||
| 					style="width: 2em; height: 2em; vertical-align: middle; object-fit: cover" | ||||
| 					src="/${image_link}/view" | ||||
| 				/>`; | ||||
|   | ||||
| @@ -1356,8 +1356,7 @@ bool tf_ssb_connection_is_client(tf_ssb_connection_t* connection) | ||||
|  | ||||
| bool tf_ssb_connection_is_connected(tf_ssb_connection_t* connection) | ||||
| { | ||||
| 	return (connection->state == k_tf_ssb_state_verified || connection->state == k_tf_ssb_state_server_verified) && | ||||
| 		!connection->is_closing; | ||||
| 	return (connection->state == k_tf_ssb_state_verified || connection->state == k_tf_ssb_state_server_verified) && !connection->is_closing; | ||||
| } | ||||
|  | ||||
| bool tf_ssb_connection_is_closing(tf_ssb_connection_t* connection) | ||||
|   | ||||
| @@ -1455,6 +1455,7 @@ tf_ssb_following_t* tf_ssb_db_following_deep(tf_ssb_t* ssb, const char** ids, in | ||||
| 			result[write_index].blocking_count = following[i]->blocking_count; | ||||
| 			result[write_index].followed_by_count = following[i]->ref_count; | ||||
| 			result[write_index].blocked_by_count = following[i]->block_ref_count; | ||||
| 			result[write_index].depth = following[i]->depth; | ||||
| 			write_index++; | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -286,6 +286,8 @@ typedef struct _tf_ssb_following_t | ||||
| 	int followed_by_count; | ||||
| 	/** The number of known users blocking the account. */ | ||||
| 	int blocked_by_count; | ||||
| 	/** Degree of separation between initial accounts and this account. */ | ||||
| 	int depth; | ||||
| 	/** The account's identity. */ | ||||
| 	char id[k_id_base64_len]; | ||||
| } tf_ssb_following_t; | ||||
|   | ||||
| @@ -2187,6 +2187,7 @@ static void _tf_ssb_following_after_work(tf_ssb_t* ssb, int status, void* user_d | ||||
| 			JS_SetPropertyStr(context, entry, "ob", JS_NewInt32(context, following->out_following[i].blocking_count)); | ||||
| 			JS_SetPropertyStr(context, entry, "if", JS_NewInt32(context, following->out_following[i].followed_by_count)); | ||||
| 			JS_SetPropertyStr(context, entry, "ib", JS_NewInt32(context, following->out_following[i].blocked_by_count)); | ||||
| 			JS_SetPropertyStr(context, entry, "d", JS_NewInt32(context, following->out_following[i].depth)); | ||||
| 			JS_SetPropertyStr(context, object, following->out_following[i].id, entry); | ||||
| 		} | ||||
| 		JSValue result = JS_Call(context, following->promise[0], JS_UNDEFINED, 1, &object); | ||||
|   | ||||
| @@ -446,8 +446,7 @@ static void _tf_ssb_rpc_send_endpoints(tf_ssb_t* ssb) | ||||
| 	for (int i = 0; i < count; i++) | ||||
| 	{ | ||||
| 		char id[k_id_base64_len] = { 0 }; | ||||
| 		if ((tf_ssb_connection_is_attendant(connections[i]) || tf_ssb_connection_is_endpoint(connections[i])) && | ||||
| 			tf_ssb_connection_is_connected(connections[i]) && | ||||
| 		if ((tf_ssb_connection_is_attendant(connections[i]) || tf_ssb_connection_is_endpoint(connections[i])) && tf_ssb_connection_is_connected(connections[i]) && | ||||
| 			tf_ssb_connection_get_id(connections[i], id, sizeof(id))) | ||||
| 		{ | ||||
| 			JS_SetPropertyUint32(context, endpoints, id_count++, JS_NewString(context, id)); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user