import {LitElement, html, unsafeHTML} from './lit-all.min.js'; import * as tfrpc from '/static/tfrpc.js'; import * as tfutils from './tf-utils.js'; import {styles} from './tf-styles.js'; class TfProfileElement extends LitElement { static get properties() { return { editing: {type: Object}, whoami: {type: String}, id: {type: String}, users: {type: Object}, size: {type: Number}, server_follows_me: {type: Boolean}, following: {type: Boolean}, blocking: {type: Boolean}, }; } static styles = styles; constructor() { super(); let self = this; this.editing = null; this.whoami = null; this.id = null; this.users = {}; this.size = 0; this.server_follows_me = undefined; } async load() { if (this.whoami !== this._follow_whoami) { this._follow_whoami = this.whoami; this.following = undefined; this.blocking = undefined; let result = await tfrpc.rpc.query( ` SELECT json_extract(content, '$.following') AS following FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'contact' AND json_extract(content, '$.contact') = ? AND following IS NOT NULL ORDER BY sequence DESC LIMIT 1 `, [this.whoami, this.id] ); this.following = result?.[0]?.following ?? false; result = await tfrpc.rpc.query( ` SELECT json_extract(content, '$.blocking') AS blocking FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'contact' AND json_extract(content, '$.contact') = ? AND blocking IS NOT NULL ORDER BY sequence DESC LIMIT 1 `, [this.whoami, this.id] ); this.blocking = result?.[0]?.blocking ?? false; } } async initial_load() { this.server_follows_me = undefined; let server_id = await tfrpc.rpc.getServerIdentity(); let followed = await tfrpc.rpc.query( ` SELECT json_extract(content, '$.following') AS following FROM messages WHERE author = ? AND json_extract(content, '$.type') = 'contact' AND json_extract(content, '$.contact') = ? ORDER BY sequence DESC LIMIT 1 `, [server_id, this.whoami] ); let is_followed = false; for (let row of followed) { is_followed = row.following != 0; } this.server_follows_me = is_followed; } modify(change) { tfrpc.rpc .appendMessage( this.whoami, Object.assign( { type: 'contact', contact: this.id, }, change ) ) .catch(function (error) { alert(error?.message); }); } follow() { this.modify({following: true}); } unfollow() { this.modify({following: false}); } block() { this.modify({blocking: true}); } unblock() { this.modify({blocking: false}); } edit() { let original = this.users[this.id]; this.editing = { name: original.name, description: original.description, image: original.image, publicWebHosting: original.publicWebHosting, }; console.log(this.editing); } save_edits() { let self = this; let message = { type: 'about', about: this.whoami, }; for (let key of Object.keys(this.editing)) { if (this.editing[key] !== this.users[this.id][key]) { message[key] = this.editing[key]; } } tfrpc.rpc .appendMessage(this.whoami, message) .then(function () { self.editing = null; }) .catch(function (error) { alert(error?.message); }); } discard_edits() { this.editing = null; } attach_image() { let self = this; let input = document.createElement('input'); input.type = 'file'; input.onchange = function (event) { let file = event.target.files[0]; file .arrayBuffer() .then(function (buffer) { let bin = Array.from(new Uint8Array(buffer)); return tfrpc.rpc.store_blob(bin); }) .then(function (id) { self.editing = Object.assign({}, self.editing, {image: id}); console.log(self.editing); }) .catch(function (e) { alert(e.message); }); }; input.click(); } async server_follow_me(follow) { try { await tfrpc.rpc.setServerFollowingMe(this.whoami, follow); } catch (e) { console.log(e); } try { await this.initial_load(); } catch (e) { console.log(e); } } render() { if ( this.id == this.whoami && this.editing && this.server_follows_me === undefined ) { this.initial_load(); } this.load(); let self = this; let profile = this.users[this.id] || {}; tfrpc.rpc .query( `SELECT SUM(LENGTH(content)) AS size FROM messages WHERE author = ?`, [this.id] ) .then(function (result) { self.size = result[0].size; }); let edit; let follow; let block; if (this.id === this.whoami) { if (this.editing) { let server_follow; if (this.server_follows_me === true) { server_follow = html`<button class="w3-button w3-theme-d1" @click=${() => this.server_follow_me(false)} > Server, Stop Following Me </button>`; } else if (this.server_follows_me === false) { server_follow = html`<button class="w3-button w3-theme-d1" @click=${() => this.server_follow_me(true)} > Server, Follow Me </button>`; } edit = html` <button class="w3-button w3-theme-d1" @click=${this.save_edits}> Save Profile </button> <button class="w3-button w3-theme-d1" @click=${this.discard_edits}> Discard </button> ${server_follow} `; } else { edit = html`<button class="w3-button w3-theme-d1" @click=${this.edit}> Edit Profile </button>`; } } if (this.id !== this.whoami && this.following !== undefined) { follow = this.following ? html`<button class="w3-button w3-theme-d1" @click=${this.unfollow}> Unfollow </button>` : html`<button class="w3-button w3-theme-d1" @click=${this.follow}> Follow </button>`; } if (this.id !== this.whoami && this.blocking !== undefined) { block = this.blocking ? html`<button class="w3-button w3-theme-d1" @click=${this.unblock}> Unblock </button>` : html`<button class="w3-button w3-theme-d1" @click=${this.block}> Block </button>`; } let edit_profile = this.editing ? html` <div style="flex: 1 0 50%; display: flex; flex-direction: column; gap: 8px"> <div class="w3-container"> <div> <label for="name">Name:</label> <input class="w3-input w3-theme-d1" type="text" id="name" value=${this.editing.name} @input=${(event) => (this.editing = Object.assign({}, this.editing, {name: event.srcElement.value}))}></input> </div> <div><label for="description">Description:</label></div> <textarea class="w3-input w3-theme-d1" style="resize: vertical" rows="8" id="description" @input=${(event) => (this.editing = Object.assign({}, this.editing, {description: event.srcElement.value}))}>${this.editing.description}</textarea> <div> <label for="public_web_hosting">Public Web Hosting:</label> <input class="w3-check w3-theme-d1" type="checkbox" id="public_web_hosting" ?checked=${this.editing.publicWebHosting} @input=${(event) => (self.editing = Object.assign({}, self.editing, {publicWebHosting: event.srcElement.checked}))}></input> </div> <div> <button class="w3-button w3-theme-d1" @click=${this.attach_image}>Attach Image</button> </div> </div> </div>` : null; let image = typeof profile.image == 'string' ? profile.image : profile.image?.link; image = this.editing?.image ?? image; let description = this.editing?.description ?? profile.description; return html`<div style="border: 2px solid black; background-color: rgba(255, 255, 255, 0.2); padding: 16px"> <tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)}) <div style="display: flex; flex-direction: row; gap: 1em"> ${edit_profile} <div style="flex: 1 0 50%"> <div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div> <div>${unsafeHTML(tfutils.markdown(description))}</div> </div> </div> <div> Following ${profile.following} identities. Followed by ${profile.followed} identities. Blocking ${profile.blocking} identities. Blocked by ${profile.blocked} identities. </div> <div> ${edit} ${follow} ${block} </div> </div>`; } } customElements.define('tf-profile', TfProfileElement);