tildefriends/apps/cory/ssblit/tf-app.js
2022-09-11 17:42:41 +00:00

269 lines
8.2 KiB
JavaScript

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},
};
}
static styles = styles;
constructor() {
super();
let self = this;
this.hash = '#';
this.unread = [];
this.tab = 'news';
this.broadcasts = [];
this.connections = [];
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(function(value) {
self.whoami = value;
});
}
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];
}
}
return result;
}
async contact(id, last_row_id, following, max_row_id, contact_cache) {
let result = await this.contacts_internal(id, last_row_id, following, max_row_id);
contact_cache[id] = Object.assign(contact_cache[id] || {}, result);
following[id] = contact_cache[id];
return result;
}
async following_deep_internal(ids, depth, blocking, last_row_id, following, max_row_id, contact_cache) {
let contacts = await Promise.all([...new Set(ids)].map(x => this.contact(x, last_row_id, following, max_row_id, contact_cache)));
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, contact_cache) : [];
result[id] = [id, ...found, ...deeper];
}
return [...new Set(Object.values(result).flat())];
}
async following_deep(ids, depth, blocking) {
const k_cache_version = 4;
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 contact_cache = {};
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, contact_cache);
cache.last_row_id = max_row_id;
await tfrpc.rpc.databaseSet('following', JSON.stringify(cache));
return result;
}
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.allFollowing),
id,
]);
let self = this;
let mine = messages.filter(m => m.author === self.whoami);
if (mine.length) {
this.process_messages(mine);
await this.finalize_messages();
}
let other = messages.filter(m => m.author !== self.whoami);
if (other.length) {
this.unread = [...this.unread, ...other];
}
}
_handle_whoami_changed(event) {
if (this.whoami !== event.srcElement.selected) {
console.log('whoami changed', 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`
<tf-id-picker id="picker" selected=${this.whoami} .ids=${this._ids} @change=${this._handle_whoami_changed}></tf-id-picker>
<button @click=${this.create_identity}>Create Identity</button>
`;
}
async render_tab() {
let following = await this.following_deep([this.whoami], 2, {});
let users = await this.fetch_about(following.sort());
if (this.tab === 'news') {
return html`
<tf-tab-news .following=${following} whoami=${this.whoami} .users=${users} hash=${this.hash} .unread=${this.unread} @refresh=${() => this.unread = []}></tf-tab-news>
`;
} else if (this.tab === 'connections') {
return html`
<tf-tab-connections .users=${users} .connections=${this.connections} .broadcasts=${this.broadcasts}></tf-tab-connections>
`;
} else if (this.tab === 'search') {
return html`
<tf-tab-search .following=${following} whoami=${this.whoami} .users=${users}></tf-tab-search>
`;
}
}
render() {
let self = this;
let id_picker = html`
${guard([this.whoami], () => until(this.render_id_picker(), html`<div>Loading...</div>`))}
`;
let tabs = html`
<div>
<input type="button" value="News" ?disabled=${self.tab == 'news'} @click=${event => self.tab = 'news'}></input>
<input type="button" value="Connections" ?disabled=${self.tab == 'connections'} @click=${event => self.tab = 'connections'}></input>
<input type="button" value="Search" ?disabled=${self.tab == 'search'} @click=${event => self.tab = 'search'}></input>
</div>
`;
return html`
${id_picker}
${tabs}
${until(this.render_tab(), html`<div>Loading...</div>`)}
`;
}
}
customElements.define('tf-app', TfElement);