Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: PoC Render to string #656

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/platform/web/ui/general/ListView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class ListView<T, V extends IView> implements IView, IListObserver<T> {
const root = this._root = el(this._tagName, attr);
this.loadList();
if (this._onItemClick) {
root.addEventListener("click", this);
//root.addEventListener("click", this);
}
return root;
}
Expand Down
78 changes: 66 additions & 12 deletions src/platform/web/ui/general/TemplateView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export abstract class TemplateView<T extends IObservableValue> extends BaseUpdat
const builder = new TemplateBuilder(this) as Builder<T>;
try {
this._root = this.render(builder, this._value);
console.log('this._root', this._root)
} finally {
builder.close();
}
Expand Down Expand Up @@ -287,6 +288,55 @@ export class TemplateBuilder<T extends IObservableValue> {
}

const node = document.createElementNS(ns, name);

const attrMap = {};
if(attributes) {
for(let [key, value] of Object.entries(attributes)) {
let attrName = key;
if (key === "className") {
attrName = "class";
}

// 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[attrName] = classNames(value, value);
} else {
attrMap[attrName] = classNames(value, this._value);
}
} else if (this._isEventHandler(key, value)) {
// no-op
} else if (typeof value === "function") {
//this._addAttributeBinding(node, key, value);
attrMap[attrName] = value(this._value);
} else {
attrMap[attrName] = 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('')}</${name}>`;

if (attributes) {
this._setNodeAttributes(node, attributes);
Expand All @@ -303,6 +353,8 @@ export class TemplateBuilder<T extends IObservableValue> {
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
Expand All @@ -322,7 +374,8 @@ export class TemplateBuilder<T extends IObservableValue> {
if (view) {
return this.view(view);
} else {
return document.createComment("node binding placeholder");
//return document.createComment("node binding placeholder");
return '<!-- node binding placeholder -->';
}
});
}
Expand All @@ -337,7 +390,8 @@ export class TemplateBuilder<T extends IObservableValue> {
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 '<!-- map placeholder -->';
}
return rootNode;
});
Expand Down Expand Up @@ -365,16 +419,16 @@ export class TemplateBuilder<T extends IObservableValue> {
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<R>(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);
}
}

Expand Down
25 changes: 24 additions & 1 deletion src/platform/web/ui/general/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,35 @@ export function el(elementName: string, attributes?: BasicAttributes<never> | Ch
}

export function elNS(ns: string, elementName: string, attributes?: BasicAttributes<never> | Child | Child[], children?: Child | Child[]): Element {
//console.log('html elNS', new Error().stack);
if (attributes && isChildren(attributes)) {
children = attributes;
attributes = undefined;
}

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('')}</${elementName}>`;


if (attributes) {
for (let [name, value] of Object.entries(attributes)) {
if (typeof value === "object") {
Expand All @@ -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";
Expand Down
46 changes: 32 additions & 14 deletions src/platform/web/ui/session/room/TimelineView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,34 @@ export class TimelineView extends TemplateView<TimelineViewModel> {

render(t: Builder<TimelineViewModel>, 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,
Expand All @@ -77,12 +95,12 @@ export class TimelineView extends TemplateView<TimelineViewModel> {
})
]);

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;
}
Expand Down
53 changes: 33 additions & 20 deletions src/platform/web/ui/session/room/timeline/TextMessageView.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -54,6 +64,9 @@ export class TextMessageView extends BaseMessageView {
container.appendChild(time);
});




return container;
}
}
Expand Down