Add support for the room header (RoomView)

This commit is contained in:
Eric Eastwood 2022-02-10 01:42:02 -06:00
parent 2378ed72c7
commit c0a2a65c2f
8 changed files with 201 additions and 69 deletions

8
public/styles/styles.css Normal file
View File

@ -0,0 +1,8 @@
.room-layout {
display: flex;
}
.right-panel {
width: 20%;
background-color: #f2f5f8;
}

View File

@ -1,45 +1,10 @@
const assert = require('assert');
const fetch = require('node-fetch');
const urlJoin = require('./lib/url-join');
const fetchEndpoint = require('./lib/fetch-endpoint');
const { matrixServerUrl } = require('../config.json');
const secrets = require('../secrets.json');
const matrixAccessToken = secrets.matrixAccessToken;
assert(matrixAccessToken);
class HTTPResponseError extends Error {
constructor(response, responseText, ...args) {
super(
`HTTP Error Response: ${response.status} ${response.statusText}: ${responseText}\n URL=${response.url}`,
...args
);
this.response = response;
}
}
const checkStatus = async (response) => {
if (response.ok) {
// response.status >= 200 && response.status < 300
return response;
} else {
const responseText = await response.text();
throw new HTTPResponseError(response, responseText);
}
};
async function fetchEndpoint(endpoint) {
const res = await fetch(endpoint, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${matrixAccessToken}`,
},
});
await checkStatus(res);
const data = await res.json();
return data;
}
assert(matrixServerUrl);
async function fetchEventsForTimestamp(roomId, ts) {
assert(roomId);
@ -50,8 +15,6 @@ async function fetchEventsForTimestamp(roomId, ts) {
`_matrix/client/unstable/org.matrix.msc3030/rooms/${roomId}/timestamp_to_event?ts=${ts}&dir=f`
);
const timestampToEventResData = await fetchEndpoint(timestampToEventEndpoint);
//console.log('timestampToEventResData', timestampToEventResData);
const eventIdForTimestamp = timestampToEventResData.event_id;
assert(eventIdForTimestamp);
@ -67,11 +30,10 @@ async function fetchEventsForTimestamp(roomId, ts) {
`_matrix/client/r0/rooms/${roomId}/messages?from=${contextResData.start}&limit=50&filter={"lazy_load_members":true,"include_redundant_members":true}`
);
const messageResData = await fetchEndpoint(messagesEndpoint);
//console.log('messageResData', messageResData);
//console.log('messageResData.state', messageResData.state);
const stateEventMap = {};
for (const stateEvent of messageResData.state) {
for (const stateEvent of messageResData.state || []) {
if (stateEvent.type === 'm.room.member') {
stateEventMap[stateEvent.state_key] = stateEvent;
}

43
server/fetch-room-data.js Normal file
View File

@ -0,0 +1,43 @@
const assert = require('assert');
const urlJoin = require('./lib/url-join');
const fetchEndpoint = require('./lib/fetch-endpoint');
const { matrixServerUrl } = require('../config.json');
assert(matrixServerUrl);
async function fetchRoomData(roomId) {
const stateNameEndpoint = urlJoin(
matrixServerUrl,
`_matrix/client/r0/rooms/${roomId}/state/m.room.name`
);
const stateAvatarEndpoint = urlJoin(
matrixServerUrl,
`_matrix/client/r0/rooms/${roomId}/state/m.room.avatar`
);
const [stateNameResDataOutcome, stateAvatarResDataOutcome] = await Promise.allSettled([
fetchEndpoint(stateNameEndpoint),
fetchEndpoint(stateAvatarEndpoint),
]);
console.log('stateAvatarResDataOutcome', stateAvatarResDataOutcome);
let name;
if (stateNameResDataOutcome.reason === undefined) {
name = stateNameResDataOutcome.value.name;
}
let avatarUrl;
if (stateAvatarResDataOutcome.reason === undefined) {
avatarUrl = stateAvatarResDataOutcome.value.url;
}
return {
id: roomId,
name,
avatarUrl,
};
}
module.exports = fetchRoomData;

View File

@ -0,0 +1,39 @@
const assert = require('assert');
const fetch = require('node-fetch');
const { matrixAccessToken } = require('../../secrets.json');
assert(matrixAccessToken);
class HTTPResponseError extends Error {
constructor(response, responseText, ...args) {
super(
`HTTP Error Response: ${response.status} ${response.statusText}: ${responseText}\n URL=${response.url}`,
...args
);
this.response = response;
}
}
const checkResponseStatus = async (response) => {
if (response.ok) {
// response.status >= 200 && response.status < 300
return response;
} else {
const responseText = await response.text();
throw new HTTPResponseError(response, responseText);
}
};
async function fetchEndpoint(endpoint) {
const res = await fetch(endpoint, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${matrixAccessToken}`,
},
});
await checkResponseStatus(res);
const data = await res.json();
return data;
}
module.exports = fetchEndpoint;

View File

@ -7,7 +7,8 @@ const { parseHTML } = require('linkedom');
const config = require('../config.json');
async function renderToString(events, stateEventMap) {
async function renderToString(roomData, events, stateEventMap) {
assert(roomData);
assert(events);
assert(stateEventMap);
@ -40,12 +41,13 @@ async function renderToString(events, stateEventMap) {
// So we can see logs from the underlying vm
vmContext.global.console = console;
vmContext.global.INPUT_ROOM_DATA = roomData;
vmContext.global.INPUT_EVENTS = events;
vmContext.global.INPUT_STATE_EVENT_MAP = stateEventMap;
vmContext.global.INPUT_CONFIG = config;
const hydrogenRenderScriptCode = await readFile(
path.resolve(__dirname, './hydrogen-vm-render-script.js'),
path.resolve(__dirname, '../shared/hydrogen-vm-render-script.js'),
'utf8'
);
const hydrogenRenderScript = new vm.Script(hydrogenRenderScriptCode, {

View File

@ -3,6 +3,7 @@ const express = require('express');
const asyncHandler = require('./lib/express-async-handler');
const urlJoin = require('./lib/url-join');
const fetchRoomData = require('./fetch-room-data');
const fetchEventsForTimestamp = require('./fetch-events-for-timestamp');
const renderHydrogenToString = require('./render-hydrogen-to-string');
@ -12,7 +13,7 @@ assert(basePath);
const app = express();
app.get('/style.css', async function (req, res) {
app.get('/hydrogen-styles.css', async function (req, res) {
res.set('Content-Type', 'text/css');
res.sendFile(require.resolve('hydrogen-view-sdk/style.css'));
});
@ -24,6 +25,8 @@ app.get(
})
);
// Based off of the Gitter archive routes,
// https://gitlab.com/gitterHQ/webapp/-/blob/14954e05c905e8c7cb675efebb89116c07cfaab5/server/handlers/app/archive.js#L190-297
app.get(
'/:roomIdOrAlias/date/:yyyy(\\d{4})/:mm(\\d{2})/:dd(\\d{2})/:hourRange(\\d\\d?-\\d\\d?)?',
asyncHandler(async function (req, res) {
@ -77,19 +80,30 @@ app.get(
toTimestamp = Date.UTC(yyyy, mm, dd, toHour);
}
const { events, stateEventMap } = await fetchEventsForTimestamp(roomIdOrAlias, fromTimestamp);
const [roomData, { events, stateEventMap }] = await Promise.all([
fetchRoomData(roomIdOrAlias),
fetchEventsForTimestamp(roomIdOrAlias, fromTimestamp),
]);
const hydrogenHtmlOutput = await renderHydrogenToString(events, stateEventMap);
const hydrogenHtmlOutput = await renderHydrogenToString(roomData, events, stateEventMap);
const styleUrl = urlJoin(basePath, 'style.css');
const hydrogenStylesUrl = urlJoin(basePath, 'hydrogen-styles.css');
const stylesUrl = urlJoin(basePath, 'styles.css');
const pageHtml = `
<!doctype html>
<html lang="en">
<head>
<link href="${styleUrl}" rel="stylesheet">
<link href="${hydrogenStylesUrl}" rel="stylesheet">
<link href="${stylesUrl}" rel="stylesheet">
</head>
<body>
${hydrogenHtmlOutput}
<div class="room-layout">
${hydrogenHtmlOutput}
<aside class="right-panel">
</aside>
</div>
</body>
</html>
`;

17
shared/ArchiveView.js Normal file
View File

@ -0,0 +1,17 @@
const {
TemplateView,
RoomView,
RightPanelView
} = require('hydrogen-view-sdk');
export class ArchiveView extends TemplateView {
render(t, vm) {
return t.div({
className: {
"ArchiveView": true,
},
}, [
]
}
}

View File

@ -11,8 +11,12 @@ const {
encodeEventIdKey,
Timeline,
TimelineView,
RoomView,
RoomViewModel,
} = require('hydrogen-view-sdk');
const roomData = global.INPUT_ROOM_DATA;
assert(roomData);
const events = global.INPUT_EVENTS;
assert(events);
const stateEventMap = global.INPUT_STATE_EVENT_MAP;
@ -25,7 +29,6 @@ let eventIndexCounter = 0;
const fragmentIdComparer = new FragmentIdComparer([]);
function makeEventEntryFromEventJson(eventJson, memberEvent) {
assert(eventJson);
assert(memberEvent);
const eventIndex = eventIndexCounter;
const eventEntry = new EventEntry(
@ -34,8 +37,8 @@ function makeEventEntryFromEventJson(eventJson, memberEvent) {
eventIndex: eventIndex, // TODO: What should this be?
roomId: roomId,
event: eventJson,
displayName: memberEvent.content && memberEvent.content.displayname,
avatarUrl: memberEvent.content && memberEvent.content.avatar_url,
displayName: memberEvent && memberEvent.content && memberEvent.content.displayname,
avatarUrl: memberEvent && memberEvent.content && memberEvent.content.avatar_url,
key: encodeKey(roomId, 0, eventIndex),
eventIdKey: encodeEventIdKey(roomId, eventJson.event_id),
},
@ -64,29 +67,35 @@ async function mountHydrogen() {
//hsApi: this._hsApi
});
const mediaRepository = new MediaRepository({
homeserver: config.matrixServerUrl,
});
const urlCreator = {
urlUntilSegment: () => {
return 'todo';
},
urlForSegments: () => {
return 'todo';
},
};
const navigation = {
segment: () => {
return 'todo';
},
};
const tilesCreator = makeTilesCreator({
platform,
roomVM: {
room: {
mediaRepository: new MediaRepository({
homeserver: config.matrixServerUrl,
}),
mediaRepository,
},
},
timeline,
urlCreator: {
urlUntilSegment: () => {
return 'todo';
},
urlForSegments: () => {
return 'todo';
},
},
navigation: {
segment: () => {
return 'todo';
},
},
urlCreator,
navigation,
});
// Something we can modify with new state updates as we see them
@ -129,7 +138,45 @@ async function mountHydrogen() {
tiles: tiles,
};
const view = new TimelineView(timelineViewModel);
// const view = new TimelineView(timelineViewModel);
// const roomViewModel = {
// kind: 'room',
// timelineViewModel,
// composerViewModel: {
// kind: 'none',
// },
// i18n: RoomViewModel.prototype.i18n,
// id: roomData.id,
// name: roomData.name,
// avatarUrl(size) {
// return getAvatarHttpUrl(roomData.avatarUrl, size, platform, mediaRepository);
// },
// };
const room = {
name: roomData.name,
id: roomData.id,
avatarUrl: roomData.avatarUrl,
avatarColorId: roomData.id,
mediaRepository: mediaRepository,
};
const roomViewModel = new RoomViewModel({
room,
ownUserId: 'xxx',
platform,
urlCreator,
navigation,
});
roomViewModel._timelineVM = timelineViewModel;
roomViewModel._composerVM = {
kind: 'none',
};
const view = new RoomView(roomViewModel);
//console.log('view.mount()', view.mount());
app.appendChild(view.mount());