mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-12-22 19:14:52 +01:00
wip on collections and listview
This commit is contained in:
parent
952f1abddf
commit
5bff41c1ee
@ -21,6 +21,8 @@
|
||||
- build a very basic interface with
|
||||
- a start/stop sync button
|
||||
- a room list sorted alphabetically
|
||||
- do some preprocessing on sync response which can then be used by persister, summary, timeline
|
||||
- support timeline
|
||||
- clicking on a room list, you see messages (userId -> body)
|
||||
- send messages
|
||||
- fill gaps with call to /messages
|
||||
@ -28,3 +30,4 @@
|
||||
- lazy loading members
|
||||
- decide denormalized data in summary vs reading from multiple stores PER room on load
|
||||
- allow Room/Summary class to be subclassed and store additional data?
|
||||
- store account data, support read markers
|
||||
|
@ -1,21 +1,28 @@
|
||||
import RoomSummary from "./summary.js";
|
||||
import RoomPersister from "./persister.js";
|
||||
import EventEmitter from "../../EventEmitter.js";
|
||||
|
||||
export default class Room {
|
||||
constructor(roomId, storage) {
|
||||
export default class Room extends EventEmitter {
|
||||
constructor(roomId, storage, emitCollectionChange) {
|
||||
super();
|
||||
this._roomId = roomId;
|
||||
this._storage = storage;
|
||||
this._summary = new RoomSummary(roomId);
|
||||
this._persister = new RoomPersister(roomId);
|
||||
this._emitCollectionChange = emitCollectionChange;
|
||||
}
|
||||
|
||||
async applySync(roomResponse, membership, txn) {
|
||||
this._summary.applySync(roomResponse, membership, txn);
|
||||
const changed = this._summary.applySync(roomResponse, membership, txn);
|
||||
this._persister.persistSync(roomResponse, txn);
|
||||
if (changed) {
|
||||
this.emit("change");
|
||||
(this._emitCollectionChange)();
|
||||
}
|
||||
}
|
||||
|
||||
load(summary, txn) {
|
||||
this._summary.load(summary);
|
||||
return this._persister.load(txn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import Room from "./room/room.js";
|
||||
import ObservableMap from "../observable/map.js";
|
||||
|
||||
export default class Session {
|
||||
constructor(storage) {
|
||||
this._storage = storage;
|
||||
this._session = null;
|
||||
// use Map here?
|
||||
this._rooms = {};
|
||||
this._rooms = new ObservableMap();
|
||||
}
|
||||
// should be called before load
|
||||
// loginData has device_id, user_id, home_server, access_token
|
||||
@ -37,13 +38,18 @@ export default class Session {
|
||||
}));
|
||||
}
|
||||
|
||||
get rooms() {
|
||||
return this._rooms;
|
||||
}
|
||||
|
||||
getRoom(roomId) {
|
||||
return this._rooms[roomId];
|
||||
return this._rooms.get(roomId);
|
||||
}
|
||||
|
||||
createRoom(roomId) {
|
||||
const room = new Room(roomId, this._storage);
|
||||
this._rooms[roomId] = room;
|
||||
const updateCallback = (params) => this._rooms.update(roomId, params);
|
||||
const room = new Room(roomId, this._storage, updateCallback);
|
||||
this._rooms.add(roomId, room);
|
||||
return room;
|
||||
}
|
||||
|
||||
@ -61,4 +67,4 @@ export default class Session {
|
||||
get accessToken() {
|
||||
return this._session.loginData.access_token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import {
|
||||
HomeServerError,
|
||||
StorageError
|
||||
} from "./error.js";
|
||||
import EventEmitter from "../event-emitter.js";
|
||||
import EventEmitter from "../EventEmitter.js";
|
||||
|
||||
const INCREMENTAL_TIMEOUT = 30000;
|
||||
const SYNC_EVENT_LIMIT = 10;
|
||||
@ -117,4 +117,4 @@ export default class Sync extends EventEmitter {
|
||||
this._currentRequest = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
55
src/observable/map.js
Normal file
55
src/observable/map.js
Normal file
@ -0,0 +1,55 @@
|
||||
import BaseObservableMap from "./map/BaseObservableMap.js";
|
||||
|
||||
export default class ObservableMap extends BaseObservableMap {
|
||||
constructor(initialValues) {
|
||||
super();
|
||||
this._values = new Map(initialValues);
|
||||
}
|
||||
|
||||
update(key, params) {
|
||||
const value = this._values.get(key);
|
||||
if (value !== undefined) {
|
||||
this._values.add(key, value);
|
||||
this.emitChange(key, value, params);
|
||||
return true;
|
||||
}
|
||||
return false; // or return existing value?
|
||||
}
|
||||
|
||||
add(key, value) {
|
||||
if (!this._values.has(key)) {
|
||||
this._values.add(key, value);
|
||||
this.emitAdd(key, value);
|
||||
return true;
|
||||
}
|
||||
return false; // or return existing value?
|
||||
}
|
||||
|
||||
remove(key) {
|
||||
const value = this._values.get(key);
|
||||
if (value !== undefined) {
|
||||
this._values.delete(key);
|
||||
this.emitRemove(key, value);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._values.clear();
|
||||
this.emitReset();
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this._values.get(key);
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this._values.size;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this._values.entries()[Symbol.iterator];
|
||||
}
|
||||
}
|
66
src/observable/map/BaseObservableMap.js
Normal file
66
src/observable/map/BaseObservableMap.js
Normal file
@ -0,0 +1,66 @@
|
||||
import MapOperator from "./operators/MapOperator.js";
|
||||
import SortOperator from "./operators/SortOperator.js";
|
||||
|
||||
export default class BaseObservableMap {
|
||||
constructor() {
|
||||
this._handlers = new Set();
|
||||
}
|
||||
|
||||
emitReset() {
|
||||
for(let h of this._handlers) {
|
||||
h.onReset();
|
||||
}
|
||||
}
|
||||
// we need batch events, mostly on index based collection though?
|
||||
// maybe we should get started without?
|
||||
emitAdd(key, value) {
|
||||
for(let h of this._handlers) {
|
||||
h.onAdd(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
emitChange(key, value, ...params) {
|
||||
for(let h of this._handlers) {
|
||||
h.onChange(key, value, ...params);
|
||||
}
|
||||
}
|
||||
|
||||
emitRemove(key, value) {
|
||||
for(let h of this._handlers) {
|
||||
h.onRemove(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
onSubscribeFirst() {
|
||||
|
||||
}
|
||||
|
||||
onUnsubscribeLast() {
|
||||
|
||||
}
|
||||
|
||||
subscribe(handler) {
|
||||
this._handlers.add(handler);
|
||||
if (this._handlers.length === 1) {
|
||||
this.onSubscribeFirst();
|
||||
}
|
||||
return () => {
|
||||
if (handler) {
|
||||
this._handlers.delete(this._handler);
|
||||
if (this._handlers.length === 0) {
|
||||
this.onUnsubscribeLast();
|
||||
}
|
||||
handler = null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
map(mapper, updater) {
|
||||
return new MapOperator(this, mapper, updater);
|
||||
}
|
||||
|
||||
sort(comparator) {
|
||||
return new SortOperator(this, comparator);
|
||||
}
|
||||
}
|
22
src/observable/map/Operator.js
Normal file
22
src/observable/map/Operator.js
Normal file
@ -0,0 +1,22 @@
|
||||
// import BaseObservableMap from "./BaseObservableMap.js";
|
||||
|
||||
export default class Operator /* extends BaseObservableMap */ {
|
||||
constructor(source) {
|
||||
// super();
|
||||
this._source = source;
|
||||
}
|
||||
|
||||
onSubscribeFirst() {
|
||||
this._sourceSubscription = this._source.subscribe(this);
|
||||
}
|
||||
|
||||
onUnsubscribeLast() {
|
||||
this._sourceSubscription();
|
||||
this._sourceSubscription = null;
|
||||
}
|
||||
|
||||
onRemove(key, value) {}
|
||||
onAdd(key, value) {}
|
||||
onChange(key, value, params) {}
|
||||
onReset() {}
|
||||
}
|
55
src/observable/map/operators/FilterOperator.js
Normal file
55
src/observable/map/operators/FilterOperator.js
Normal file
@ -0,0 +1,55 @@
|
||||
import Operator from "../Operator.js";
|
||||
|
||||
export default class FilterOperator extends Operator {
|
||||
constructor(source, mapper, updater) {
|
||||
super(source);
|
||||
this._mapper = mapper;
|
||||
this._updater = updater;
|
||||
this._mappedValues = new Map();
|
||||
}
|
||||
|
||||
onAdd(key, value) {
|
||||
const mappedValue = this._mapper(value);
|
||||
this._mappedValues.set(key, mappedValue);
|
||||
this.emitAdd(key, mappedValue);
|
||||
}
|
||||
|
||||
onRemove(key, _value) {
|
||||
const mappedValue = this._mappedValues.get(key);
|
||||
if (this._mappedValues.delete(key)) {
|
||||
this.emitRemove(key, mappedValue);
|
||||
}
|
||||
}
|
||||
|
||||
onChange(key, value, params) {
|
||||
const mappedValue = this._mappedValues.get(key);
|
||||
if (mappedValue !== undefined) {
|
||||
const newParams = this._updater(value, params);
|
||||
if (newParams !== undefined) {
|
||||
this.emitChange(key, mappedValue, newParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSubscribeFirst() {
|
||||
for (let [key, value] of this._source) {
|
||||
const mappedValue = this._mapper(value);
|
||||
this._mappedValues.set(key, mappedValue);
|
||||
}
|
||||
super.onSubscribeFirst();
|
||||
}
|
||||
|
||||
onUnsubscribeLast() {
|
||||
super.onUnsubscribeLast();
|
||||
this._mappedValues.clear();
|
||||
}
|
||||
|
||||
onReset() {
|
||||
this._mappedValues.clear();
|
||||
this.emitReset();
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this._mappedValues.entries()[Symbol.iterator];
|
||||
}
|
||||
}
|
55
src/observable/map/operators/MapOperator.js
Normal file
55
src/observable/map/operators/MapOperator.js
Normal file
@ -0,0 +1,55 @@
|
||||
import Operator from "../Operator.js";
|
||||
|
||||
export default class MapOperator extends Operator {
|
||||
constructor(source, mapper, updater) {
|
||||
super(source);
|
||||
this._mapper = mapper;
|
||||
this._updater = updater;
|
||||
this._mappedValues = new Map();
|
||||
}
|
||||
|
||||
onAdd(key, value) {
|
||||
const mappedValue = this._mapper(value);
|
||||
this._mappedValues.set(key, mappedValue);
|
||||
this.emitAdd(key, mappedValue);
|
||||
}
|
||||
|
||||
onRemove(key, _value) {
|
||||
const mappedValue = this._mappedValues.get(key);
|
||||
if (this._mappedValues.delete(key)) {
|
||||
this.emitRemove(key, mappedValue);
|
||||
}
|
||||
}
|
||||
|
||||
onChange(key, value, params) {
|
||||
const mappedValue = this._mappedValues.get(key);
|
||||
if (mappedValue !== undefined) {
|
||||
const newParams = this._updater(value, params);
|
||||
if (newParams !== undefined) {
|
||||
this.emitChange(key, mappedValue, newParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSubscribeFirst() {
|
||||
for (let [key, value] of this._source) {
|
||||
const mappedValue = this._mapper(value);
|
||||
this._mappedValues.set(key, mappedValue);
|
||||
}
|
||||
super.onSubscribeFirst();
|
||||
}
|
||||
|
||||
onUnsubscribeLast() {
|
||||
super.onUnsubscribeLast();
|
||||
this._mappedValues.clear();
|
||||
}
|
||||
|
||||
onReset() {
|
||||
this._mappedValues.clear();
|
||||
this.emitReset();
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this._mappedValues.entries()[Symbol.iterator];
|
||||
}
|
||||
}
|
119
src/observable/map/operators/SortOperator.js
Normal file
119
src/observable/map/operators/SortOperator.js
Normal file
@ -0,0 +1,119 @@
|
||||
import Operator from "../Operator.js";
|
||||
|
||||
/**
|
||||
* @license
|
||||
* Based off baseSortedIndex function in Lodash <https://lodash.com/>
|
||||
* Copyright JS Foundation and other contributors <https://js.foundation/>
|
||||
* Released under MIT license <https://lodash.com/license>
|
||||
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
|
||||
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
|
||||
*/
|
||||
function sortedIndex(array, value, comparator) {
|
||||
let low = 0;
|
||||
let high = array.length;
|
||||
|
||||
while (low < high) {
|
||||
let mid = (low + high) >>> 1;
|
||||
let cmpResult = comparator(value, array[mid]);
|
||||
|
||||
if (cmpResult > 0) {
|
||||
low = mid + 1;
|
||||
} else if (cmpResult < 0) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = high = mid;
|
||||
}
|
||||
}
|
||||
return high;
|
||||
}
|
||||
|
||||
// TODO: this should not inherit from an BaseObservableMap, as it's a list
|
||||
export default class SortOperator extends Operator {
|
||||
constructor(sourceMap, comparator) {
|
||||
super(sourceMap);
|
||||
this._comparator = comparator;
|
||||
this._sortedValues = [];
|
||||
this._keyIndex = new Map();
|
||||
}
|
||||
|
||||
onAdd(key, value) {
|
||||
const idx = sortedIndex(this._sortedValues, value, this._comparator);
|
||||
this._sortedValues.splice(idx, 0, value);
|
||||
this._keyIndex.set(key, idx);
|
||||
this.emitAdd(idx, value);
|
||||
}
|
||||
|
||||
onRemove(key, _value) {
|
||||
const idx = sortedIndex(this._sortedValues, value, this._comparator);
|
||||
this._sortedValues.splice(idx, 0, value);
|
||||
this._keyIndex.set(key, idx);
|
||||
this.emitAdd(idx, value);
|
||||
}
|
||||
|
||||
onChange(key, value, params) {
|
||||
// index could have moved if other items got added in the meantime
|
||||
const oldIdx = this._keyIndex.get(key);
|
||||
this._sortedValues.splice(oldIdx, 1);
|
||||
const idx = sortedIndex(this._sortedValues, value, this._comparator);
|
||||
|
||||
}
|
||||
|
||||
onSubscribeFirst() {
|
||||
this._sortedValues = new Array(this._source.size);
|
||||
let i = 0;
|
||||
for (let [key, value] of this._source) {
|
||||
this._sortedValues[i] = value;
|
||||
this._keyIndex.set(key, i);
|
||||
++i;
|
||||
}
|
||||
this._sortedValues.sort(this._comparator);
|
||||
super.onSubscribeFirst();
|
||||
}
|
||||
|
||||
onUnsubscribeLast() {
|
||||
super.onUnsubscribeLast();
|
||||
this._sortedValues = null;
|
||||
}
|
||||
|
||||
onReset() {
|
||||
this._sortedValues = [];
|
||||
this.emitReset();
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this._source.size;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this._sortedValues;
|
||||
}
|
||||
}
|
||||
|
||||
//#ifdef TESTS
|
||||
export function tests() {
|
||||
return {
|
||||
test_sortIndex(assert) {
|
||||
let idx = sortedIndex([1, 5, 6, 8], 0, (a, b) => a - b);
|
||||
assert.equal(idx, 0);
|
||||
idx = sortedIndex([1, 5, 6, 8], 3, (a, b) => a - b);
|
||||
assert.equal(idx, 1);
|
||||
idx = sortedIndex([1, 5, 6, 8], 8, (a, b) => a - b);
|
||||
assert.equal(idx, 3);
|
||||
},
|
||||
|
||||
test_sortIndex_reverse(assert) {
|
||||
let idx = sortedIndex([8, 6, 5, 1], 6, (a, b) => b - a);
|
||||
assert.equal(idx, 1);
|
||||
},
|
||||
|
||||
test_sortIndex_likeArraySort(assert) {
|
||||
const a = [5, 1, 8, 2];
|
||||
const cmp = (a, b) => a - b;
|
||||
a.sort(cmp);
|
||||
assert.deepEqual(a, [1, 2, 5, 8]);
|
||||
let idx = sortedIndex(a, 2, cmp);
|
||||
assert.equal(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
//#endif
|
@ -1,6 +1,4 @@
|
||||
import EventEmitter from "./event-emitter.js";
|
||||
|
||||
class LiveMap {
|
||||
class ObservableMap {
|
||||
constructor() {
|
||||
this._handlers = new Set();
|
||||
}
|
||||
@ -30,43 +28,46 @@ class LiveMap {
|
||||
}
|
||||
}
|
||||
|
||||
onSubscribeFirst() {
|
||||
|
||||
}
|
||||
|
||||
onUnsubscribeLast() {
|
||||
|
||||
}
|
||||
|
||||
subscribe(handler) {
|
||||
this._handlers.add(handler);
|
||||
if (this._handlers.length === 1) {
|
||||
this.onSubscribeFirst();
|
||||
}
|
||||
return () => {
|
||||
if (handler) {
|
||||
this._handlers.delete(this._handler);
|
||||
if (this._handlers.length === 0) {
|
||||
this.onUnsubscribeLast();
|
||||
}
|
||||
handler = null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Operator extends LiveMap {
|
||||
class Operator extends ObservableMap {
|
||||
constructor(source) {
|
||||
super();
|
||||
this._source = source;
|
||||
}
|
||||
|
||||
subscribe(handler) {
|
||||
this.onSubscribe(this._source);
|
||||
let subscription = super.subscribe(handler);
|
||||
let sourceSubscription = this._source.subscribe(this);
|
||||
return () => {
|
||||
sourceSubscription = sourceSubscription && sourceSubscription();
|
||||
subscription = subscription && subscription();
|
||||
// this.onUnsubscribe(); ?
|
||||
return null;
|
||||
};
|
||||
}
|
||||
onSubscribeFirst() {
|
||||
this._sourceSubscription = this._source.subscribe(this);
|
||||
}
|
||||
|
||||
onSubscribe() {
|
||||
|
||||
}
|
||||
onUnsubscribeLast() {
|
||||
this._sourceSubscription();
|
||||
this._sourceSubscription = null;
|
||||
}
|
||||
|
||||
onRemove(key, value) {}
|
||||
onAdd(key, value) {}
|
||||
@ -74,7 +75,7 @@ class Operator extends LiveMap {
|
||||
onReset() {}
|
||||
}
|
||||
|
||||
export default class LiveMapCollection extends LiveMap {
|
||||
export default class ObservableMapCollection extends ObservableMap {
|
||||
constructor(initialValues) {
|
||||
super();
|
||||
this._values = new Map(initialValues);
|
||||
@ -124,7 +125,7 @@ export default class LiveMapCollection extends LiveMap {
|
||||
}
|
||||
}
|
||||
|
||||
class LiveMapOperator extends Operator {
|
||||
class ObservableMapOperator extends Operator {
|
||||
constructor(source, mapper, updater) {
|
||||
super(source);
|
||||
this._mapper = mapper;
|
||||
@ -132,13 +133,6 @@ class LiveMapOperator extends Operator {
|
||||
this._mappedValues = new Map();
|
||||
}
|
||||
|
||||
onSubscribe(source) {
|
||||
for (let [key, value] of source) {
|
||||
const mappedValue = this._mapper(value);
|
||||
this._mappedValues.set(key, mappedValue);
|
||||
}
|
||||
}
|
||||
|
||||
onAdd(key, value) {
|
||||
const mappedValue = this._mapper(value);
|
||||
this._mappedValues.set(key, mappedValue);
|
||||
@ -162,6 +156,19 @@ class LiveMapOperator extends Operator {
|
||||
}
|
||||
}
|
||||
|
||||
onSubscribeFirst() {
|
||||
for (let [key, value] of this._source) {
|
||||
const mappedValue = this._mapper(value);
|
||||
this._mappedValues.set(key, mappedValue);
|
||||
}
|
||||
super.onSubscribeFirst();
|
||||
}
|
||||
|
||||
onUnsubscribeLast() {
|
||||
super.onUnsubscribeLast();
|
||||
this._mappedValues.clear();
|
||||
}
|
||||
|
||||
onReset() {
|
||||
this._mappedValues.clear();
|
||||
this.emitReset();
|
||||
@ -172,12 +179,12 @@ class LiveMapOperator extends Operator {
|
||||
}
|
||||
}
|
||||
|
||||
class FilterOperator extends LiveMapOperator {
|
||||
class FilterOperator extends ObservableMapOperator {
|
||||
|
||||
}
|
||||
|
||||
class SortSet {
|
||||
constructor(liveMap) {
|
||||
constructor(ObservableMap) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -188,4 +195,4 @@ export function tests() {
|
||||
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
78
src/ui/web/ListView.js
Normal file
78
src/ui/web/ListView.js
Normal file
@ -0,0 +1,78 @@
|
||||
import * as html from "./html.js";
|
||||
|
||||
class UIView {
|
||||
mount(initialValue) {
|
||||
|
||||
}
|
||||
|
||||
unmount() {
|
||||
|
||||
}
|
||||
|
||||
update() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default class ListView {
|
||||
constructor(collection, childCreator) {
|
||||
this._collection = collection;
|
||||
this._root = null;
|
||||
this._subscription = null;
|
||||
this._childCreator = childCreator;
|
||||
this._childInstances = null;
|
||||
}
|
||||
|
||||
getDOMNode() {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
mount() {
|
||||
this._subscription = this._collection.subscribe(this);
|
||||
this._root = html.ul({className: "ListView"});
|
||||
this._childInstances = new Array(this._collection.length);
|
||||
for (let item of this._collection) {
|
||||
const child = this._childCreator(item);
|
||||
this._childInstances.push(child);
|
||||
const childDomNode = child.mount();
|
||||
this._root.appendChild(childDomNode);
|
||||
}
|
||||
return this._root;
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this._subscription = this._subscription();
|
||||
for (let child of this._childInstances) {
|
||||
child.unmount();
|
||||
}
|
||||
this._childInstances = null;
|
||||
}
|
||||
|
||||
onAdd(i, value) {
|
||||
const child = this._childCreator(value);
|
||||
const childDomNode = child.mount();
|
||||
this._childInstances.splice(i, 0, child);
|
||||
const isLast = i === this._collection.length - 1;
|
||||
if (isLast) {
|
||||
this._root.appendChild(childDomNode);
|
||||
} else {
|
||||
const nextDomNode = this._childInstances[i + 1].getDOMNode();
|
||||
this._root.insertBefore(childDomNode, nextDomNode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onRemove(i, _value) {
|
||||
const [child] = this._childInstances.splice(i, 1);
|
||||
child.getDOMNode().remove();
|
||||
child.unmount();
|
||||
}
|
||||
|
||||
onMove(fromIdx, toIdx, value) {
|
||||
|
||||
}
|
||||
|
||||
onChange(i, value) {
|
||||
this._childInstances[i].update(value);
|
||||
}
|
||||
}
|
43
src/ui/web/html.js
Normal file
43
src/ui/web/html.js
Normal file
@ -0,0 +1,43 @@
|
||||
export function setAttribute(el, name, value) {
|
||||
el.setAttribute(name, value);
|
||||
}
|
||||
|
||||
export function el(elementName, attrs, children) {
|
||||
const e = document.createElement(elementName);
|
||||
if (typeof attrs === "object") {
|
||||
for (let [name, value] of Object.entries(attrs)) {
|
||||
setAttribute(e, name, value);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(children)) {
|
||||
// TODO: use fragment here?
|
||||
for (let c of children) {
|
||||
e.appendChild(c);
|
||||
}
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
export function text(str) {
|
||||
return document.createTextNode(str);
|
||||
}
|
||||
|
||||
export function ol(... params) { return el("ol", ... params); }
|
||||
export function ul(... params) { return el("ul", ... params); }
|
||||
export function li(... params) { return el("li", ... params); }
|
||||
export function div(... params) { return el("div", ... params); }
|
||||
export function h1(... params) { return el("h1", ... params); }
|
||||
export function h2(... params) { return el("h2", ... params); }
|
||||
export function h3(... params) { return el("h3", ... params); }
|
||||
export function h4(... params) { return el("h4", ... params); }
|
||||
export function h5(... params) { return el("h5", ... params); }
|
||||
export function h6(... params) { return el("h6", ... params); }
|
||||
export function p(... params) { return el("p", ... params); }
|
||||
export function strong(... params) { return el("strong", ... params); }
|
||||
export function em(... params) { return el("em", ... params); }
|
||||
export function span(... params) { return el("span", ... params); }
|
||||
export function img(... params) { return el("img", ... params); }
|
||||
export function section(... params) { return el("section", ... params); }
|
||||
export function main(... params) { return el("main", ... params); }
|
||||
export function article(... params) { return el("article", ... params); }
|
||||
export function aside(... params) { return el("aside", ... params); }
|
Loading…
Reference in New Issue
Block a user