From 8356fbf70f8dcedb778d31795ab92eb603b6bb03 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 3 Feb 2022 00:26:38 -0600 Subject: [PATCH] Rough changes to make render to string work --- src/platform/web/ui/general/ListView.ts | 2 +- src/platform/web/ui/general/TemplateView.ts | 72 +++++++++++++++---- src/platform/web/ui/general/html.ts | 25 ++++++- .../web/ui/session/room/TimelineView.ts | 46 ++++++++---- .../session/room/timeline/TextMessageView.js | 53 ++++++++------ 5 files changed, 150 insertions(+), 48 deletions(-) diff --git a/src/platform/web/ui/general/ListView.ts b/src/platform/web/ui/general/ListView.ts index 9d639098..357eed8f 100644 --- a/src/platform/web/ui/general/ListView.ts +++ b/src/platform/web/ui/general/ListView.ts @@ -81,7 +81,7 @@ export class ListView implements IView, IListObserver { const root = this._root = el(this._tagName, attr); this.loadList(); if (this._onItemClick) { - root.addEventListener("click", this); + //root.addEventListener("click", this); } return root; } diff --git a/src/platform/web/ui/general/TemplateView.ts b/src/platform/web/ui/general/TemplateView.ts index ce593f75..6082077c 100644 --- a/src/platform/web/ui/general/TemplateView.ts +++ b/src/platform/web/ui/general/TemplateView.ts @@ -80,6 +80,7 @@ export abstract class TemplateView extends BaseUpdat const builder = new TemplateBuilder(this) as Builder; try { this._root = this.render(builder, this._value); + console.log('this._root', this._root) } finally { builder.close(); } @@ -287,6 +288,49 @@ export class TemplateBuilder { } const node = document.createElementNS(ns, name); + + const attrMap = {}; + if(attributes) { + for(let [key, value] of Object.entries(attributes)) { + // binding for className as object of className => enabled + if (typeof value === "object") { + if (key !== "className" || value === null) { + // Ignore non-className objects. + continue; + } + if (objHasFns(value)) { + //this._addClassNamesBinding(node, value); + attrMap[key] = classNames(value, value); + } else { + attrMap[key] = classNames(value, this._value); + } + } else if (this._isEventHandler(key, value)) { + // no-op + } else if (typeof value === "function") { + this._addAttributeBinding(node, key, value); + } else { + attrMap[key] = value; + } + } + } + + const attrString = Object.keys(attrMap) + .map((attrKey) => { + return `${attrKey}="${attrMap[attrKey]}"`; + }) + .join(' '); + + const childenStrings = []; + for (let child of [].concat(children)) { + console.log('child', child) + if (typeof child === "function") { + //child = this._addTextBinding(child); + childenStrings.push('todo'); + } + childenStrings.push(child); + } + + return `<${name} ${attrString}>${childenStrings.join('')}`; if (attributes) { this._setNodeAttributes(node, attributes); @@ -303,6 +347,8 @@ export class TemplateBuilder { view(view: IView, mountOptions?: IMountArgs): ViewNode { this._templateView.addSubView(view); return mountView(view, mountOptions); + + //return view.render(this, this._value) } // map a value to a view, every time the value changes @@ -322,7 +368,8 @@ export class TemplateBuilder { if (view) { return this.view(view); } else { - return document.createComment("node binding placeholder"); + //return document.createComment("node binding placeholder"); + return ''; } }); } @@ -337,7 +384,8 @@ export class TemplateBuilder { if (!rootNode) { // TODO: this will confuse mapView which assumes that // a comment node means there is no view to clean up - return document.createComment("map placeholder"); + //return document.createComment("map placeholder"); + return ''; } return rootNode; }); @@ -365,16 +413,16 @@ export class TemplateBuilder { You should not call the TemplateBuilder (e.g. `t.xxx()`) at all from the side effect, instead use tags from html.ts to help you construct any DOM you need. */ mapSideEffect(mapFn: (value: T) => R, sideEffect: (newV: R, oldV: R | undefined) => void) { - let prevValue = mapFn(this._value); - const binding = () => { - const newValue = mapFn(this._value); - if (prevValue !== newValue) { - sideEffect(newValue, prevValue); - prevValue = newValue; - } - }; - this._addBinding(binding); - sideEffect(prevValue, undefined); + // let prevValue = mapFn(this._value); + // const binding = () => { + // const newValue = mapFn(this._value); + // if (prevValue !== newValue) { + // sideEffect(newValue, prevValue); + // prevValue = newValue; + // } + // }; + // this._addBinding(binding); + // sideEffect(prevValue, undefined); } } diff --git a/src/platform/web/ui/general/html.ts b/src/platform/web/ui/general/html.ts index 9ebcfaaf..32270914 100644 --- a/src/platform/web/ui/general/html.ts +++ b/src/platform/web/ui/general/html.ts @@ -60,6 +60,7 @@ export function el(elementName: string, attributes?: BasicAttributes | Ch } export function elNS(ns: string, elementName: string, attributes?: BasicAttributes | Child | Child[], children?: Child | Child[]): Element { + //console.log('html elNS', new Error().stack); if (attributes && isChildren(attributes)) { children = attributes; attributes = undefined; @@ -67,6 +68,27 @@ export function elNS(ns: string, elementName: string, attributes?: BasicAttribut const e = document.createElementNS(ns, elementName); + const attrMap = {}; + if (attributes) { + for (let [name, value] of Object.entries(attributes)) { + if (typeof value === "object") { + // Only className should ever be an object; be careful + // here anyway and ignore object-valued non-className attributes. + value = (value !== null && name === "className") ? classNames(value, undefined) : false; + } + attrMap[name] = value; + } + } + + const attrString = Object.keys(attrMap) + .map((attrKey) => { + return `${attrKey}="${attrMap[attrKey]}"`; + }) + .join(' '); + + return `<${elementName} ${attrString}>${[].concat(children).join('')}`; + + if (attributes) { for (let [name, value] of Object.entries(attributes)) { if (typeof value === "object") { @@ -93,7 +115,8 @@ export function elNS(ns: string, elementName: string, attributes?: BasicAttribut } export function text(str: string): Text { - return document.createTextNode(str); + return str; + //return document.createTextNode(str); } export const HTML_NS: string = "http://www.w3.org/1999/xhtml"; diff --git a/src/platform/web/ui/session/room/TimelineView.ts b/src/platform/web/ui/session/room/TimelineView.ts index 936b8c7c..9340292f 100644 --- a/src/platform/web/ui/session/room/TimelineView.ts +++ b/src/platform/web/ui/session/room/TimelineView.ts @@ -57,16 +57,34 @@ export class TimelineView extends TemplateView { render(t: Builder, vm: TimelineViewModel) { // assume this view will be mounted in the parent DOM straight away - requestAnimationFrame(() => { - // do initial scroll positioning - this.restoreScrollPosition(); - }); + // requestAnimationFrame(() => { + // // do initial scroll positioning + // this.restoreScrollPosition(); + // }); + console.log('vm.tiles', vm.tiles) + + const childrenRenders = []; + for(const entry of vm.tiles) { + const View = viewClassForEntry(entry); + if (View) { + const view = new View(entry); + const childrenRender = view.render(t, entry); + console.log('childrenRender', childrenRender) + childrenRenders.push(childrenRender); + //childrenViews.push(); + } + } + this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition()); const root = t.div({className: "Timeline"}, [ - t.div({ - className: "Timeline_scroller bottom-aligned-scroll", - onScroll: () => this.onScroll() - }, t.view(this.tilesView)), + t.div( + { + className: "Timeline_scroller bottom-aligned-scroll", + onScroll: () => this.onScroll() + }, + //t.view(this.tilesView) + childrenRenders + ), t.button({ className: { "Timeline_jumpDown": true, @@ -77,12 +95,12 @@ export class TimelineView extends TemplateView { }) ]); - if (typeof ResizeObserver === "function") { - this.resizeObserver = new ResizeObserver(() => { - this.restoreScrollPosition(); - }); - this.resizeObserver.observe(root); - } + // if (typeof ResizeObserver === "function") { + // this.resizeObserver = new ResizeObserver(() => { + // this.restoreScrollPosition(); + // }); + // this.resizeObserver.observe(root); + // } return root; } diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js index 510a676f..ee10f6b1 100644 --- a/src/platform/web/ui/session/room/timeline/TextMessageView.js +++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js @@ -21,26 +21,36 @@ import {ReplyPreviewError, ReplyPreviewView} from "./ReplyPreviewView.js"; export class TextMessageView extends BaseMessageView { renderMessageBody(t, vm) { const time = t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time); - const container = t.div({ - className: { - "Timeline_messageBody": true, - statusMessage: vm => vm.shape === "message-status", - } - }, t.mapView(vm => vm.replyTile, replyTile => { - if (this._isReplyPreview) { - // if this._isReplyPreview = true, this is already a reply preview, don't nest replies for now. - return null; - } - else if (vm.isReply && !replyTile) { - return new ReplyPreviewError(); - } - else if (replyTile) { - return new ReplyPreviewView(replyTile); - } - else { - return null; - } - })); + + const parts = []; + for (const part of vm.body.parts) { + parts.push(renderPart(part)); + } + + const container = t.div( + { + className: { + "Timeline_messageBody": true, + statusMessage: vm => vm.shape === "message-status", + } + }, + parts, + // t.mapView(vm => vm.replyTile, replyTile => { + // if (this._isReplyPreview) { + // // if this._isReplyPreview = true, this is already a reply preview, don't nest replies for now. + // return null; + // } + // else if (vm.isReply && !replyTile) { + // return new ReplyPreviewError(); + // } + // else if (replyTile) { + // return new ReplyPreviewView(replyTile); + // } + // else { + // return null; + // } + // }) + ); const shouldRemove = (element) => element?.nodeType === Node.ELEMENT_NODE && element.className !== "ReplyPreviewView"; @@ -54,6 +64,9 @@ export class TextMessageView extends BaseMessageView { container.appendChild(time); }); + + + return container; } }