Compare commits

...

4 commits

Author SHA1 Message Date
Eric Eastwood ca1e45e04e Fix class attribute 2022-02-03 00:43:59 -06:00
Eric Eastwood 8356fbf70f Rough changes to make render to string work 2022-02-03 00:26:38 -06:00
Eric Eastwood 5805ce0310 Remove some scratch changes 2022-02-02 01:17:07 -06:00
Eric Eastwood dcc508c037 Changes added to work on the Matrix public archive
See plan https://docs.google.com/document/d/1wP_TIqmBQjtt862vb2CWWmnmVxTyolcF3J1scuiYMdg/edit#

 1. Trying to make it faster/easier to build `hydrogen.es.js` for local linking and dev in `matrix-public-arhive` project
 1. Some random changes to accomodate using raw `EventEntry`'s
2022-02-02 01:08:54 -06:00
14 changed files with 179 additions and 57 deletions

View file

@ -2,6 +2,6 @@
"name": "hydrogen-view-sdk", "name": "hydrogen-view-sdk",
"description": "Embeddable matrix client library, including view components", "description": "Embeddable matrix client library, including view components",
"version": "0.0.4", "version": "0.0.4",
"main": "./hydrogen.es.js", "main": "./lib-build/hydrogen.es.js",
"type": "module" "type": "module"
} }

View file

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
rm -rf target rm -rf target/*
yarn run vite build -c vite.sdk-assets-config.js yarn run vite build -c vite.sdk-assets-config.js
yarn run vite build -c vite.sdk-lib-config.js yarn run vite build -c vite.sdk-lib-config.js
yarn tsc -p tsconfig-declaration.json yarn tsc -p tsconfig-declaration.json

View file

@ -21,6 +21,7 @@ import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../
import {tilesCreator} from "./timeline/tilesCreator.js"; import {tilesCreator} from "./timeline/tilesCreator.js";
import {ViewModel} from "../../ViewModel.js"; import {ViewModel} from "../../ViewModel.js";
console.log('RoomViewModel asdfwafeawfefewfewaeafwafewefw');
export class RoomViewModel extends ViewModel { export class RoomViewModel extends ViewModel {
constructor(options) { constructor(options) {
super(options); super(options);
@ -45,6 +46,7 @@ export class RoomViewModel extends ViewModel {
this._room.on("change", this._onRoomChange); this._room.on("change", this._onRoomChange);
try { try {
const timeline = await this._room.openTimeline(); const timeline = await this._room.openTimeline();
console.log('timeline', timeline.entries);
this._tilesCreator = tilesCreator(this.childOptions({ this._tilesCreator = tilesCreator(this.childOptions({
roomVM: this, roomVM: this,
timeline, timeline,

View file

@ -36,6 +36,7 @@ import {ViewModel} from "../../../ViewModel.js";
export class TimelineViewModel extends ViewModel { export class TimelineViewModel extends ViewModel {
constructor(options) { constructor(options) {
console.log('TimelineViewModel asdf', options)
super(options); super(options);
const {timeline, tilesCreator} = options; const {timeline, tilesCreator} = options;
this._timeline = this.track(timeline); this._timeline = this.track(timeline);

View file

@ -87,7 +87,7 @@ export class BaseMessageTile extends SimpleTile {
} }
get isOwn() { get isOwn() {
return this._entry.sender === this._ownMember.userId; return this._entry.sender === this._ownMember?.userId;
} }
get isContinuation() { get isContinuation() {

View file

@ -24,5 +24,12 @@ export {SessionViewModel} from "./domain/session/SessionViewModel.js";
export {SessionView} from "./platform/web/ui/session/SessionView.js"; export {SessionView} from "./platform/web/ui/session/SessionView.js";
export {RoomViewModel} from "./domain/session/room/RoomViewModel.js"; export {RoomViewModel} from "./domain/session/room/RoomViewModel.js";
export {RoomView} from "./platform/web/ui/session/room/RoomView.js"; export {RoomView} from "./platform/web/ui/session/room/RoomView.js";
export {MediaRepository} from "./matrix/net/MediaRepository";
export {TilesCollection} from "./domain/session/room/timeline/TilesCollection.js";
export {tilesCreator} from "./domain/session/room/timeline/tilesCreator.js";
export {FragmentIdComparer} from "./matrix/room/timeline/FragmentIdComparer.js";
export {EventEntry} from "./matrix/room/timeline/entries/EventEntry.js";
export {encodeKey, decodeKey, encodeEventIdKey, decodeEventIdKey} from "./matrix/storage/idb/stores/TimelineEventStore";
export {Timeline} from "./matrix/room/timeline/Timeline.js";
export {TimelineViewModel} from "./domain/session/room/timeline/TimelineViewModel.js"; export {TimelineViewModel} from "./domain/session/room/timeline/TimelineViewModel.js";
export {TimelineView} from "./platform/web/ui/session/room/TimelineView"; export {TimelineView} from "./platform/web/ui/session/room/TimelineView";

View file

@ -86,6 +86,7 @@ export class Timeline {
const readerRequest = this._disposables.track(this._timelineReader.readFromEnd(20, txn, log)); const readerRequest = this._disposables.track(this._timelineReader.readFromEnd(20, txn, log));
try { try {
const entries = await readerRequest.complete(); const entries = await readerRequest.complete();
console.log('entries', entries)
this._loadContextEntriesWhereNeeded(entries); this._loadContextEntriesWhereNeeded(entries);
this._setupEntries(entries); this._setupEntries(entries);
} finally { } finally {
@ -199,8 +200,10 @@ export class Timeline {
if (!this._localEntries?.hasSubscriptions) { if (!this._localEntries?.hasSubscriptions) {
return; return;
} }
// find any local relations to this new remote event // find any local relations to these new remote events or maybe these
for (const pee of this._localEntries) { // new remote events reference one of the other new remote events we have.
const entryList = new ConcatList(entries, this._localEntries);
for (const pee of entryList) {
// this will work because we set relatedEventId when removing remote echos // this will work because we set relatedEventId when removing remote echos
if (pee.relatedEventId) { if (pee.relatedEventId) {
const relationTarget = entries.find(e => e.id === pee.relatedEventId); const relationTarget = entries.find(e => e.id === pee.relatedEventId);

View file

@ -54,6 +54,7 @@ async function readRawTimelineEntriesWithTxn(roomId, eventKey, direction, amount
} else { } else {
eventsWithinFragment = await timelineStore.eventsBefore(roomId, eventKey, amount); eventsWithinFragment = await timelineStore.eventsBefore(roomId, eventKey, amount);
} }
console.log('readRawTimelineEntriesWithTxn eventsWithinFragment', eventsWithinFragment)
let eventEntries = eventsWithinFragment.map(e => new EventEntry(e, fragmentIdComparer)); let eventEntries = eventsWithinFragment.map(e => new EventEntry(e, fragmentIdComparer));
entries = directionalConcat(entries, eventEntries, direction); entries = directionalConcat(entries, eventEntries, direction);
// prepend or append eventsWithinFragment to entries, and wrap them in EventEntry // prepend or append eventsWithinFragment to entries, and wrap them in EventEntry

View file

@ -40,20 +40,20 @@ interface TimelineEventEntry {
type TimelineEventStorageEntry = TimelineEventEntry & { key: string, eventIdKey: string }; type TimelineEventStorageEntry = TimelineEventEntry & { key: string, eventIdKey: string };
function encodeKey(roomId: string, fragmentId: number, eventIndex: number): string { export function encodeKey(roomId: string, fragmentId: number, eventIndex: number): string {
return `${roomId}|${encodeUint32(fragmentId)}|${encodeUint32(eventIndex)}`; return `${roomId}|${encodeUint32(fragmentId)}|${encodeUint32(eventIndex)}`;
} }
function decodeKey(key: string): { roomId: string, eventKey: EventKey } { export function decodeKey(key: string): { roomId: string, eventKey: EventKey } {
const [roomId, fragmentId, eventIndex] = key.split("|"); const [roomId, fragmentId, eventIndex] = key.split("|");
return {roomId, eventKey: new EventKey(decodeUint32(fragmentId), decodeUint32(eventIndex))}; return {roomId, eventKey: new EventKey(decodeUint32(fragmentId), decodeUint32(eventIndex))};
} }
function encodeEventIdKey(roomId: string, eventId: string): string { export function encodeEventIdKey(roomId: string, eventId: string): string {
return `${roomId}|${eventId}`; return `${roomId}|${eventId}`;
} }
function decodeEventIdKey(eventIdKey: string): { roomId: string, eventId: string } { export function decodeEventIdKey(eventIdKey: string): { roomId: string, eventId: string } {
const [roomId, eventId] = eventIdKey.split("|"); const [roomId, eventId] = eventIdKey.split("|");
return {roomId, eventId}; return {roomId, eventId};
} }

View file

@ -81,7 +81,7 @@ export class ListView<T, V extends IView> implements IView, IListObserver<T> {
const root = this._root = el(this._tagName, attr); const root = this._root = el(this._tagName, attr);
this.loadList(); this.loadList();
if (this._onItemClick) { if (this._onItemClick) {
root.addEventListener("click", this); //root.addEventListener("click", this);
} }
return root; return root;
} }

View file

@ -80,6 +80,7 @@ export abstract class TemplateView<T extends IObservableValue> extends BaseUpdat
const builder = new TemplateBuilder(this) as Builder<T>; const builder = new TemplateBuilder(this) as Builder<T>;
try { try {
this._root = this.render(builder, this._value); this._root = this.render(builder, this._value);
console.log('this._root', this._root)
} finally { } finally {
builder.close(); builder.close();
} }
@ -287,6 +288,55 @@ export class TemplateBuilder<T extends IObservableValue> {
} }
const node = document.createElementNS(ns, name); 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) { if (attributes) {
this._setNodeAttributes(node, attributes); this._setNodeAttributes(node, attributes);
@ -303,6 +353,8 @@ export class TemplateBuilder<T extends IObservableValue> {
view(view: IView, mountOptions?: IMountArgs): ViewNode { view(view: IView, mountOptions?: IMountArgs): ViewNode {
this._templateView.addSubView(view); this._templateView.addSubView(view);
return mountView(view, mountOptions); return mountView(view, mountOptions);
//return view.render(this, this._value)
} }
// map a value to a view, every time the value changes // map a value to a view, every time the value changes
@ -322,7 +374,8 @@ export class TemplateBuilder<T extends IObservableValue> {
if (view) { if (view) {
return this.view(view); return this.view(view);
} else { } else {
return document.createComment("node binding placeholder"); //return document.createComment("node binding placeholder");
return '<!-- node binding placeholder -->';
} }
}); });
} }
@ -337,7 +390,8 @@ export class TemplateBuilder<T extends IObservableValue> {
if (!rootNode) { if (!rootNode) {
// TODO: this will confuse mapView which assumes that // TODO: this will confuse mapView which assumes that
// a comment node means there is no view to clean up // 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; return rootNode;
}); });
@ -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, 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. */ 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) { mapSideEffect<R>(mapFn: (value: T) => R, sideEffect: (newV: R, oldV: R | undefined) => void) {
let prevValue = mapFn(this._value); // let prevValue = mapFn(this._value);
const binding = () => { // const binding = () => {
const newValue = mapFn(this._value); // const newValue = mapFn(this._value);
if (prevValue !== newValue) { // if (prevValue !== newValue) {
sideEffect(newValue, prevValue); // sideEffect(newValue, prevValue);
prevValue = newValue; // prevValue = newValue;
} // }
}; // };
this._addBinding(binding); // this._addBinding(binding);
sideEffect(prevValue, undefined); // sideEffect(prevValue, undefined);
} }
} }

View file

@ -60,6 +60,7 @@ 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 { 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)) { if (attributes && isChildren(attributes)) {
children = attributes; children = attributes;
attributes = undefined; attributes = undefined;
@ -67,6 +68,27 @@ export function elNS(ns: string, elementName: string, attributes?: BasicAttribut
const e = document.createElementNS(ns, elementName); 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) { if (attributes) {
for (let [name, value] of Object.entries(attributes)) { for (let [name, value] of Object.entries(attributes)) {
if (typeof value === "object") { if (typeof value === "object") {
@ -93,7 +115,8 @@ export function elNS(ns: string, elementName: string, attributes?: BasicAttribut
} }
export function text(str: string): Text { 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"; export const HTML_NS: string = "http://www.w3.org/1999/xhtml";

View file

@ -57,16 +57,34 @@ export class TimelineView extends TemplateView<TimelineViewModel> {
render(t: Builder<TimelineViewModel>, vm: TimelineViewModel) { render(t: Builder<TimelineViewModel>, vm: TimelineViewModel) {
// assume this view will be mounted in the parent DOM straight away // assume this view will be mounted in the parent DOM straight away
requestAnimationFrame(() => { // requestAnimationFrame(() => {
// do initial scroll positioning // // do initial scroll positioning
this.restoreScrollPosition(); // 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()); this.tilesView = new TilesListView(vm.tiles, () => this.restoreScrollPosition());
const root = t.div({className: "Timeline"}, [ const root = t.div({className: "Timeline"}, [
t.div({ t.div(
className: "Timeline_scroller bottom-aligned-scroll", {
onScroll: () => this.onScroll() className: "Timeline_scroller bottom-aligned-scroll",
}, t.view(this.tilesView)), onScroll: () => this.onScroll()
},
//t.view(this.tilesView)
childrenRenders
),
t.button({ t.button({
className: { className: {
"Timeline_jumpDown": true, "Timeline_jumpDown": true,
@ -77,12 +95,12 @@ export class TimelineView extends TemplateView<TimelineViewModel> {
}) })
]); ]);
if (typeof ResizeObserver === "function") { // if (typeof ResizeObserver === "function") {
this.resizeObserver = new ResizeObserver(() => { // this.resizeObserver = new ResizeObserver(() => {
this.restoreScrollPosition(); // this.restoreScrollPosition();
}); // });
this.resizeObserver.observe(root); // this.resizeObserver.observe(root);
} // }
return root; return root;
} }

View file

@ -21,26 +21,36 @@ import {ReplyPreviewError, ReplyPreviewView} from "./ReplyPreviewView.js";
export class TextMessageView extends BaseMessageView { export class TextMessageView extends BaseMessageView {
renderMessageBody(t, vm) { renderMessageBody(t, vm) {
const time = t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time); const time = t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time);
const container = t.div({
className: { const parts = [];
"Timeline_messageBody": true, for (const part of vm.body.parts) {
statusMessage: vm => vm.shape === "message-status", parts.push(renderPart(part));
} }
}, t.mapView(vm => vm.replyTile, replyTile => {
if (this._isReplyPreview) { const container = t.div(
// if this._isReplyPreview = true, this is already a reply preview, don't nest replies for now. {
return null; className: {
} "Timeline_messageBody": true,
else if (vm.isReply && !replyTile) { statusMessage: vm => vm.shape === "message-status",
return new ReplyPreviewError(); }
} },
else if (replyTile) { parts,
return new ReplyPreviewView(replyTile); // t.mapView(vm => vm.replyTile, replyTile => {
} // if (this._isReplyPreview) {
else { // // if this._isReplyPreview = true, this is already a reply preview, don't nest replies for now.
return null; // 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"; const shouldRemove = (element) => element?.nodeType === Node.ELEMENT_NODE && element.className !== "ReplyPreviewView";
@ -54,6 +64,9 @@ export class TextMessageView extends BaseMessageView {
container.appendChild(time); container.appendChild(time);
}); });
return container; return container;
} }
} }