import {LitElement, html, keyed} from './lit-all.min.js'; import * as tfrpc from '/static/tfrpc.js'; class TfCollectionsAppElement extends LitElement { static get properties() { return { ids: {type: Array}, owner_ids: {type: Array}, whoami: {type: String}, following: {type: Array}, wikis: {type: Object}, wiki_docs: {type: Object}, wiki: {type: Object}, wiki_doc: {type: Object}, hash: {type: String}, adding_editor: {type: Boolean}, expand_editors: {type: Boolean}, }; } constructor() { super(); this.ids = []; this.owner_ids = []; this.following = []; this.load(); let self = this; tfrpc.register(function hash_changed(hash) { self.notify_hash_changed(hash); }); tfrpc.rpc.get_hash().then(hash => self.notify_hash_changed(hash)); } async load() { this.ids = await tfrpc.rpc.getIdentities(); this.owner_ids = await tfrpc.rpc.getOwnerIdentities(); this.whoami = await tfrpc.rpc.localStorageGet('collections_whoami'); let ids = [...new Set([...this.owner_ids, this.whoami])].sort(); this.following = Object.keys(await tfrpc.rpc.following(ids, 1)).sort(); await this.read_wikis(); await this.read_Wiki_docs(); } async read_wikis() { let max_rowid; let wikis; let start_whoami = this.whoami; while (true) { console.log('read_wikis', this.whoami); [max_rowid, wikis] = await tfrpc.rpc.collection(this.following, 'wiki', undefined, max_rowid, wikis, false); console.log('read ->', wikis); if (this.whoami !== start_whoami) { break; } console.log('wikis =>', wikis); this.wikis = wikis; this.update_wiki(); } } async read_wiki_docs() { if (!this.wiki?.id) { return; } let start_id = this.wiki.id; let max_rowid; let wiki_docs; while (true) { [max_rowid, wiki_docs] = await tfrpc.rpc.collection(this.wiki?.editors, 'wiki-doc', this.wiki?.id, max_rowid, wiki_docs); if (this.wiki?.id !== start_id) { break; } this.wiki_docs = wiki_docs; this.update_wiki_doc(); } } hash_wiki() { let hash = this.hash ?? ''; hash = hash.charAt(0) == '#' ? hash.substring(1) : hash; let parts = hash.split('/'); return parts[0]; } hash_wiki_doc() { let hash = this.hash ?? ''; hash = hash.charAt(0) == '#' ? hash.substring(1) : hash; let slash = hash.indexOf('/'); return slash != -1 ? hash.substring(slash + 1) : undefined; } update_wiki() { let want_wiki = this.hash_wiki(); for (let wiki of Object.values(this.wikis ?? {})) { if (wiki.name === want_wiki) { this.wiki = wiki; this.read_wiki_docs(); break; } } } update_wiki_doc() { let want_wiki_doc = this.hash_wiki_doc(); for (let wiki_doc of Object.values(this.wiki_docs ?? {})) { if (wiki_doc.name === want_wiki_doc) { this.wiki_doc = wiki_doc; } } } notify_hash_changed(hash) { this.hash = hash; this.update_wiki(); this.update_wiki_doc(); } async on_whoami_changed(event) { let new_id = event.srcElement.selected; await tfrpc.rpc.localStorageSet('collections_whoami', new_id); this.whoami = new_id; } update_hash() { tfrpc.rpc.set_hash(this.wiki_doc ? `${this.wiki.name}/${this.wiki_doc.name}` : `${this.wiki.name}`); } async on_wiki_changed(event) { this.wiki = event.detail.value; this.wiki_doc = undefined; this.wiki_docs = undefined; this.adding_editor = false; this.update_hash(); this.read_wiki_docs(); } async on_wiki_create(event) { await tfrpc.rpc.appendMessage(this.whoami, { type: 'wiki', name: event.detail.name, }); } async on_wiki_rename(event) { await tfrpc.rpc.appendMessage(this.whoami, { type: 'wiki', key: event.detail.id, name: event.detail.name, }); } async on_add_editor(event) { let id = this.shadowRoot.getElementById('add_editor').value; let editors = [...this.wiki.editors]; if (editors.indexOf(id) == -1) { editors.push(id); editors.sort(); } await tfrpc.rpc.appendMessage(this.whoami, { type: 'wiki', key: this.wiki.id, editors: editors, }); this.adding_editor = false; } async on_remove_editor(id) { if (confirm(`Are you sure you want to remove ${id} as an editor?`)) { let editors = [...this.wiki.editors]; if (editors.indexOf(id) != -1) { editors = editors.filter(x => x !== id); } await tfrpc.rpc.appendMessage(this.whoami, { type: 'wiki', key: this.wiki.id, editors: editors, }); } } async on_wiki_tombstone(event) { await tfrpc.rpc.appendMessage(this.whoami, { type: 'wiki', key: event.detail.id, tombstone: { date: new Date().valueOf(), reason: 'tombstoned by user', }, }); } async on_wiki_doc_create(event) { await tfrpc.rpc.appendMessage(this.whoami, { type: 'wiki-doc', parent: this.wiki.id, name: event.detail.name, }); } async on_wiki_doc_rename(event) { await tfrpc.rpc.appendMessage(this.whoami, { type: 'wiki-doc', parent: this.wiki.id, key: event.detail.id, name: event.detail.name, }); } async on_wiki_doc_tombstone(event) { await tfrpc.rpc.appendMessage(this.whoami, { type: 'wiki-doc', parent: this.wiki.id, key: event.detail.id, tombstone: { date: new Date().valueOf(), reason: 'tombstoned by user', }, }); } async on_wiki_doc_changed(event) { this.wiki_doc = event.detail.value; this.update_hash(); } updated(changed_properties) { if (changed_properties.has('whoami')) { this.wikis = {}; this.wiki_docs = {}; this.read_wikis(); } } render() { let self = this; return html` <style> .toc:hover { background-color: #0cc; } .toc.selected { background-color: #088; } </style> <div> <tf-id-picker .ids=${this.ids} selected=${this.whoami} @change=${this.on_whoami_changed} ?hidden=${!this.ids?.length}></tf-id-picker> </div> <div> ${keyed(this.whoami, html`<tf-collection .collection=${this.wikis} whoami=${this.whoami} selected_id=${this.wiki?.id} @create=${this.on_wiki_create} @rename=${this.on_wiki_rename} @tombstone=${this.on_wiki_tombstone} @change=${this.on_wiki_changed}></tf-collection>`)} ${keyed(this.wiki_doc?.id, html`<tf-collection .collection=${this.wiki_docs} whoami=${this.whoami} selected_id=${(this.wiki_doc && this.wiki_doc?.parent == this.wiki?.id) ? this.wiki_doc?.id : ''} @create=${this.on_wiki_doc_create} @rename=${this.on_wiki_doc_rename} @tombstone=${this.on_wiki_doc_tombstone} @change=${this.on_wiki_doc_changed}></tf-collection>`)} <button @click=${() => self.expand_editors = !self.expand_editors}>${this.wiki?.editors?.length} editor${this.wiki?.editors?.length > 1 ? 's' : ''}</button> <div ?hidden=${!this.wiki?.editors || !this.expand_editors}> <div> <ul> ${this.wiki?.editors.map(id => html`<li><button ?hidden=${id == this.whoami} @click=${() => self.on_remove_editor(id)}>x</button> ${id}</li>`)} <li> <button @click=${() => self.adding_editor = true} ?hidden=${this.wiki?.editors?.indexOf(this.whoami) == -1 || this.adding_editor}>+</button> <div ?hidden=${!this.adding_editor}> <label for="add_editor">Add Editor:</label> <input type="text" id="add_editor"></input> <button @click=${this.on_add_editor}>Add Editor</button> <button @click=${() => self.adding_editor = false}>x</button> </div> </li> </ul> </div> </div> </div> <div style="display: flex; flex-direction: row"> <div style="flex: 0 0"> ${Object.values(this.wikis || {}).sort((x, y) => x.name.localeCompare(y.name)).map(wiki => html` <div class="toc ${self.wiki?.id === wiki.id ? 'selected' : ''}" style="white-space: nowrap; cursor: pointer" @click=${() => self.on_wiki_changed({detail: {value: wiki}})}>${wiki.name}</div> <ul> ${Object.values(self.wiki_docs || {}).filter(doc => doc.parent === wiki?.id).sort((x, y) => x.name.localeCompare(y.name)).map(doc => html` <li class="toc ${self.wiki_doc?.id === doc.id ? 'selected' : ''}" style="white-space: nowrap; cursor: pointer; list-style: none; text-indent: -1rem" @click=${() => self.on_wiki_doc_changed({detail: {value: doc}})}>${doc?.private ? '🔒' : '📄'} ${doc.name}</li> `)} </ul> `)} </div> ${this.wiki_doc && this.wiki_doc.parent === this.wiki?.id ? html` <tf-wiki-doc style="width: 100%" whoami=${this.whoami} .wiki=${this.wiki} .value=${this.wiki_doc}></tf-wiki-doc> ` : undefined} </div> `; } } customElements.define('tf-collections-app', TfCollectionsAppElement);