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.register(function setActiveIdentity(id) { self.whoami = id; }); 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.getActiveIdentity(); 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` <link rel="stylesheet" href="tildefriends.css"/> <style> .toc-item { white-space: nowrap; cursor: pointer; } .toc-item:hover { background-color: #0cc; } .toc-item.selected { background-color: #088; font-weight: bold; } .table-of-contents { flex: 0 0; margin-right: 16px; } </style> <div> ${keyed( this.whoami, html`<tf-collection .collection=${this.wikis} whoami=${this.whoami} category="wiki" 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} category="document" 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 class="red" ?hidden=${id == this.whoami} @click=${() => self.on_remove_editor(id)}>x</button> ${id}</li>`)} <li> <button class="green" @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 class="flex-row"> <div class="box table-of-contents"> ${Object.values(this.wikis || {}) .sort((x, y) => x.name.localeCompare(y.name)) .map( (wiki) => html` <div class="toc-item ${self.wiki?.id === wiki.id ? 'selected' : ''}" @click=${() => self.on_wiki_changed({detail: {value: wiki}})} > ${self.wiki?.id === wiki.id ? '' : '>'} ${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-item ${self.wiki_doc?.id === doc.id ? 'selected' : ''}" style="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);