import {LitElement, html, css, guard, until} from './lit-all.min.js'; import * as tfrpc from '/static/tfrpc.js'; import {styles} from './tf-styles.js'; class TfElement extends LitElement { static get properties() { return { whoami: {type: String}, hash: {type: String}, unread: {type: Array}, tab: {type: String}, broadcasts: {type: Array}, connections: {type: Array}, loading: {type: Boolean}, loaded: {type: Boolean}, following: {type: Array}, users: {type: Object}, }; } static styles = styles; constructor() { super(); let self = this; this.hash = '#'; this.unread = []; this.tab = 'news'; this.broadcasts = []; this.connections = []; this.following = []; this.users = {}; this.loaded = false; tfrpc.rpc.getBroadcasts().then(b => { self.broadcasts = b || [] }); tfrpc.rpc.getConnections().then(c => { self.connections = c || [] }); tfrpc.rpc.getHash().then(hash => self.hash = hash || '#'); tfrpc.register(function hashChanged(hash) { self.hash = hash; }); tfrpc.register(async function notifyNewMessage(id) { await self.fetch_new_message(id); }); tfrpc.register(function set(name, value) { if (name === 'broadcasts') { self.broadcasts = value; } else if (name === 'connections') { self.connections = value; } }); tfrpc.rpc.localStorageGet('whoami').then(whoami => self.whoami = whoami); } async contacts_internal(id, last_row_id, following, max_row_id) { let result = Object.assign({}, following[id] || {}); result.following = result.following || {}; result.blocking = result.blocking || {}; let contacts = await tfrpc.rpc.query( ` SELECT content FROM messages WHERE author = ? AND rowid > ? AND rowid <= ? AND json_extract(content, "$.type") = "contact" ORDER BY sequence `, [id, last_row_id, max_row_id]); for (let row of contacts) { let contact = JSON.parse(row.content); if (contact.following === true) { result.following[contact.contact] = true; } else if (contact.following === false) { delete result.following[contact.contact]; } else if (contact.blocking === true) { result.blocking[contact.contact] = true; } else if (contact.blocking === false) { delete result.blocking[contact.contact]; } } following[id] = result; return result; } async contact(id, last_row_id, following, max_row_id) { return await this.contacts_internal(id, last_row_id, following, max_row_id); } async following_deep_internal(ids, depth, blocking, last_row_id, following, max_row_id) { let contacts = await Promise.all([...new Set(ids)].map(x => this.contact(x, last_row_id, following, max_row_id))); let result = {}; for (let i = 0; i < ids.length; i++) { let id = ids[i]; let contact = contacts[i]; let found = Object.keys(contact.following).filter(y => !contact.blocking[y]); let deeper = depth > 1 ? await this.following_deep_internal(found, depth - 1, Object.assign({}, contact.blocking, blocking), last_row_id, following, max_row_id) : []; result[id] = [id, ...found, ...deeper]; } return [...new Set(Object.values(result).flat())]; } async following_deep(ids, depth, blocking) { const k_cache_version = 5; let cache = await tfrpc.rpc.databaseGet('following'); cache = cache ? JSON.parse(cache) : {}; if (cache.version !== k_cache_version) { cache = { version: k_cache_version, following: {}, last_row_id: 0, }; } let max_row_id = (await tfrpc.rpc.query(` SELECT MAX(rowid) AS max_row_id FROM messages `, []))[0].max_row_id; let result = await this.following_deep_internal(ids, depth, blocking, cache.last_row_id, cache.following, max_row_id); cache.last_row_id = max_row_id; await tfrpc.rpc.databaseSet('following', JSON.stringify(cache)); console.log(cache); return [result, cache.following]; } async fetch_about(ids, users) { const k_cache_version = 1; let cache = await tfrpc.rpc.databaseGet('about'); cache = cache ? JSON.parse(cache) : {}; if (cache.version !== k_cache_version) { cache = { version: k_cache_version, about: {}, last_row_id: 0, }; } let max_row_id = (await tfrpc.rpc.query(` SELECT MAX(rowid) AS max_row_id FROM messages `, []))[0].max_row_id; for (let id of Object.keys(cache.about)) { if (ids.indexOf(id) == -1) { delete cache.about[id]; } } let abouts = await tfrpc.rpc.query( ` SELECT messages.* FROM messages, json_each(?1) AS following WHERE messages.author = following.value AND messages.rowid > ?3 AND messages.rowid <= ?4 AND json_extract(messages.content, '$.type') = 'about' UNION SELECT messages.* FROM messages, json_each(?2) AS following WHERE messages.author = following.value AND messages.rowid <= ?4 AND json_extract(messages.content, '$.type') = 'about' ORDER BY messages.author, messages.sequence `, [ JSON.stringify(ids.filter(id => cache.about[id])), JSON.stringify(ids.filter(id => !cache.about[id])), cache.last_row_id, max_row_id, ]); for (let about of abouts) { let content = JSON.parse(about.content); if (content.about === about.author) { delete content.type; delete content.about; cache.about[about.author] = Object.assign(cache.about[about.author] || {}, content); } } cache.last_row_id = max_row_id; await tfrpc.rpc.databaseSet('about', JSON.stringify(cache)); users = users || {}; for (let id of Object.keys(cache.about)) { users[id] = Object.assign(users[id] || {}, cache.about[id]); } return Object.assign({}, users); } async fetch_new_message(id) { let messages = await tfrpc.rpc.query( ` SELECT messages.* FROM messages JOIN json_each(?) AS following ON messages.author = following.value WHERE messages.id = ? `, [ JSON.stringify(this.following), id, ]); if (messages && messages.length) { this.unread = [...this.unread, ...messages]; } } _handle_whoami_changed(event) { if (this.whoami !== event.srcElement.selected) { this.whoami = event.srcElement.selected; } } async create_identity() { if (confirm("Are you sure you want to create a new identity?")) { await tfrpc.rpc.createIdentity(); this.requestUpdate(); } } async render_id_picker() { this._ids = this._ids || (await tfrpc.rpc.getIdentities()) || []; return html` `; } async load() { let whoami = this.whoami; let [following, users] = await this.following_deep([whoami], 2, {}); users = await this.fetch_about(following.sort(), users); this.following = following; this.users = users; this.whoami = whoami; this.loaded = whoami; } render_tab() { let following = this.following; let users = this.users; if (this.tab === 'news') { return html` this.unread = []}> `; } else if (this.tab === 'connections') { return html` `; } else if (this.tab === 'search') { return html` `; } } add_fake_news() { this.unread = [{ author: this.whoami, placeholder: true, id: '%fake_id', text: 'text', content: 'hello', }, ...this.unread]; } render() { let self = this; if (!this.loading && this.whoami && this.loaded !== this.whoami) { console.log('loading', this.whoami); this.loading = true; this.following = []; this.users = {}; this.load().finally(function() { self.loading = false; console.log('loaded'); }); } let id_picker = html` ${guard([this.whoami], () => until(this.render_id_picker(), html`
Loading...
`))} `; let tabs = html`
self.tab = 'news'}> self.tab = 'connections'}> self.tab = 'search'}>
`; let contents = !this.loaded ? html`
Loading...
` : this.render_tab(); return html` ${id_picker} ${tabs} ${contents} `; } } customElements.define('tf-app', TfElement);