commit 00b82ada54c080f05d4e85227bf7c3c0f38f24c6
parent c069ba29e3e6af66fdde75018eeeab9a1322592c
Author: MTRNord <mtrnord1@gmail.com>
Date: Thu, 13 Jan 2022 23:36:33 +0100
Serverside render as much as possible. Should fix meta tags and make stuff faster. Might break private posts. (Those dont exist yet so it is impossible to test)
Diffstat:
5 files changed, 181 insertions(+), 221 deletions(-)
diff --git a/helpers/matrix_client.ts b/helpers/matrix_client.ts
@@ -365,7 +365,7 @@ export default class MatrixClient {
return info;
}
- async getTimeline(roomId: string, limit: number, callback: (arg0: MatrixEvent[]) => void | Promise<void>) {
+ async getTimeline(roomId: string, limit: number) {
if (!this.accessToken) {
console.error("No access token");
return [];
@@ -373,6 +373,7 @@ export default class MatrixClient {
limit = limit || 100;
let seenEvents = 0;
let from;
+ let msgs: MatrixEvent[] = [];
while (seenEvents < limit) {
let fromQuery = ``;
if (from) {
@@ -385,17 +386,16 @@ export default class MatrixClient {
}
);
from = data.end;
- let msgs: MatrixEvent[] = [];
for (const ev of data.chunk) {
msgs.push(ev);
}
- await Promise.resolve(callback(msgs));
seenEvents += msgs.length;
if (data.chunk.length < limit) {
break;
}
seenEvents += 1; // just in case, to stop infinite loops
}
+ return msgs;
}
/**
diff --git a/pages/Home.tsx b/pages/Home.tsx
@@ -1,163 +0,0 @@
-import React, { PureComponent } from 'react';
-import { MatrixEventBase, MatrixImageEvents } from '../helpers/event_types';
-import Head from 'next/head';
-import Header from '../components/Header';
-import { ClientContext } from '../components/ClientContext';
-import { constMatrixArtServer } from '../helpers/matrix_client';
-import FrontPageImage from '../components/FrontPageImage';
-import Footer from '../components/Footer';
-
-type Props = {
-};
-
-type State = {
- directory_data: { _id: string; user_id: string; user_room: string; }[];
- error?: any;
- directoryIsLoaded: boolean;
- isLoadingImages: boolean;
- hasFullyLoaded: boolean;
- // TODO make sure we parse both extev variants properly
- image_events: MatrixImageEvents[] | [];
-};
-
-
-export default class Home extends PureComponent<Props, State>{
- declare context: React.ContextType<typeof ClientContext>;
-
- constructor(props: Props) {
- super(props);
-
- this.state = {
- isLoadingImages: false,
- directoryIsLoaded: false,
- hasFullyLoaded: false,
- image_events: []
- } as State;
- }
-
- async componentDidUpdate() {
- await this.loadEvents();
- }
-
- async componentDidMount() {
- // auto-register as a guest if not logged in
- if (!this.context.client?.accessToken) {
- this.registerAsGuest();
- } else {
- console.log("Already logged in");
- try {
- const resp = await fetch('/api/directory');
- const data = await resp.json();
- this.setState({ directory_data: data.data, directoryIsLoaded: true });
- } catch (error) {
- this.setState({
- hasFullyLoaded: true,
- error
- });
- }
- }
- }
-
- async registerAsGuest() {
- try {
- let serverUrl = constMatrixArtServer + "/_matrix/client";
- await this.context.client?.registerAsGuest(serverUrl);
- await this.context.guest_client?.registerAsGuest(serverUrl);
- if (typeof window !== "undefined") {
- window.location.reload();
- }
- } catch (error) {
- console.error("Failed to register as guest:", error);
- this.setState({
- error: "Failed to register as guest: " + JSON.stringify(error),
- });
- }
- }
-
- async loadEvents() {
- const { directoryIsLoaded, directory_data, isLoadingImages, hasFullyLoaded } = this.state;
- if (!directoryIsLoaded || isLoadingImages || hasFullyLoaded) {
- return;
- }
- const client = this.context.client.isGuest ? this.context.client : this.context.guest_client;
- this.setState({
- isLoadingImages: true,
- });
- try {
- for (let user of directory_data) {
- // We dont need many events
- const roomId = await client?.followUser(user.user_room);
- await client?.getTimeline(roomId, 100, (events) => {
- // Filter events by type
- const image_events = events.filter((event) => event.type == "m.image_gallery" || event.type == "m.image") as MatrixImageEvents[];
- console.log("Adding", image_events.length, "items");
- this.setState({
- image_events: image_events,
- });
- });
- }
- } catch (error) {
- this.setState({
- error: JSON.stringify(error),
- });
- } finally {
- this.setState({
- hasFullyLoaded: true,
- isLoadingImages: false
- });
- }
- }
-
- render() {
- const { error, hasFullyLoaded, image_events } = this.state;
-
- if (error) {
- return (
- <div>Error: {error.message}</div>
- );
- } else if (!hasFullyLoaded) {
- return (
- <div className='h-full bg-[#f8f8f8] dark:bg-[#06070D]'>
- <Head>
- <title key="title">Matrix Art | Home</title>
- <meta property="og:title" content="Matrix Art | Home" key="og-title" />
- <meta property="og:type" content="website" key="og-type" />
- </Head>
- <Header></Header>
- <main className='w-full lg:pt-20 pt-52 z-0'>
- <div className="m-0 w-full">
- <div className="loader fixed top-[50%] left-[50%] transform translate-x-[-50%] translate-y-[-50%]">Loading...</div>
- </div>
- </main>
- </div>
- );
- } else {
- return (
- <div className='h-full flex flex-col justify-between bg-[#f8f8f8] dark:bg-[#06070D]'>
- <Head>
- <title key="title">Matrix Art | Home</title>
- <meta property="og:title" content="Matrix Art | Home" key="og-title" />
- <meta property="og:type" content="website" key="og-type" />
- </Head>
- <Header></Header>
- <main className='mb-auto lg:pt-20 pt-52 z-0'>
- <div className='z-[100] sticky lg:top-20 top-52 bg-[#fefefe]/[.95] dark:bg-[#14181E]/[.95]'>
- <div className='h-16 px-10 w-full relative grid grid-cols-[1fr_auto_1fr] items-center' id='section-grid'>
- <h1 className='text-xl text-gray-900 dark:text-gray-200 font-bold'>Home</h1>
- </div>
- </div>
- <div className='m-10'>
- <ul className='flex flex-wrap gap-1'>
- {image_events.map(event => <FrontPageImage event={event} key={(event as MatrixEventBase).event_id} />)}
- <li className='grow-[10]'></li>
- </ul>
- </div>
- </main>
- <Footer></Footer>
- </div>
- );
- };
- }
-
-}
-Home.contextType = ClientContext;
-\ No newline at end of file
diff --git a/pages/_app.tsx b/pages/_app.tsx
@@ -1,8 +1,32 @@
import '../styles/globals.css';
import type { AppProps } from 'next/app';
+import { useState } from 'react';
+import { client, ClientContext, guest_client } from '../components/ClientContext';
+import React from 'react';
+import Head from 'next/head';
function MyApp({ Component, pageProps }: AppProps) {
- return <Component {...pageProps} />;
+ const [value, setValue] = useState({
+ client: client,
+ guest_client: guest_client,
+ });
+ return (
+ <>
+ <Head>
+ <title key="title">Matrix Art</title>
+ <meta property="og:title" content="Matrix Art" key="og-title" />
+ <meta property="og:type" content="website" key="og-type" />
+ <meta key="viewport" name="viewport" content="initial-scale=1.0, width=device-width" />
+ </Head>
+ <React.StrictMode>
+ <ClientContext.Provider
+ value={value}
+ >
+ <Component {...pageProps}/>
+ </ClientContext.Provider>
+ </React.StrictMode>
+ </>
+ );
}
export default MyApp;
diff --git a/pages/index.tsx b/pages/index.tsx
@@ -1,45 +1,104 @@
-import Head from 'next/head';
import React, { PureComponent } from 'react';
-import { guest_client, client, ClientContext } from '../components/ClientContext';
-import MatrixClient from '../helpers/matrix_client';
-import Home from './Home';
-
-type Props = {
+import { MatrixEventBase, MatrixImageEvents } from '../helpers/event_types';
+import Head from 'next/head';
+import Header from '../components/Header';
+import { client, ClientContext } from '../components/ClientContext';
+import FrontPageImage from '../components/FrontPageImage';
+import Footer from '../components/Footer';
+import { NextRouter, withRouter } from 'next/router';
+import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
+import { get_data } from './api/directory';
+type Props = InferGetServerSidePropsType<typeof getServerSideProps> & {
+ router: NextRouter;
};
type State = {
- value: { client: MatrixClient; guest_client: MatrixClient; };
+ error?: any;
+ // TODO make sure we parse both extev variants properly
+ image_events: MatrixImageEvents[] | [];
};
-class App extends PureComponent<Props, State> {
- constructor(props: Props | Readonly<Props>) {
+
+
+class Home extends PureComponent<Props, State>{
+ declare context: React.ContextType<typeof ClientContext>;
+
+ constructor(props: Props) {
super(props);
+
this.state = {
- value: {
- client: client,
- guest_client: guest_client,
- },
- };
+ image_events: props.image_events,
+ } as State;
}
render() {
- return (
- <>
- <Head>
- <title key="title">Matrix Art</title>
- <meta property="og:title" content="Matrix Art" key="og-title" />
- <meta property="og:type" content="website" key="og-type" />
- <meta key="viewport" name="viewport" content="initial-scale=1.0, width=device-width" />
- </Head>
- <React.StrictMode>
- <ClientContext.Provider
- value={this.state.value}
- >
- <Home />
- </ClientContext.Provider>
- </React.StrictMode>
- </>
- );
+ const { error, image_events } = this.state;
+
+ if (error) {
+ return (
+ <div>Error: {error.message}</div>
+ );
+ } else {
+ return (
+ <div className='h-full flex flex-col justify-between bg-[#f8f8f8] dark:bg-[#06070D]'>
+ <Head>
+ <title key="title">Matrix Art | Home</title>
+ <meta property="og:title" content="Matrix Art | Home" key="og-title" />
+ <meta property="og:type" content="website" key="og-type" />
+ </Head>
+ <Header></Header>
+ <main className='mb-auto lg:pt-20 pt-52 z-0'>
+ <div className='z-[100] sticky lg:top-20 top-52 bg-[#fefefe]/[.95] dark:bg-[#14181E]/[.95]'>
+ <div className='h-16 px-10 w-full relative grid grid-cols-[1fr_auto_1fr] items-center' id='section-grid'>
+ <h1 className='text-xl text-gray-900 dark:text-gray-200 font-bold'>Home</h1>
+ </div>
+ </div>
+ <div className='m-10'>
+ <ul className='flex flex-wrap gap-1'>
+ {image_events.map(event => <FrontPageImage event={event} key={(event as MatrixEventBase).event_id} />)}
+ <li className='grow-[10]'></li>
+ </ul>
+ </div>
+ </main>
+ <Footer></Footer>
+ </div>
+ );
+ }
}
+
}
+Home.contextType = ClientContext;
+
+export const getServerSideProps: GetServerSideProps = async (context) => {
+ const { res } = context;
+
+ res.setHeader(
+ 'Cache-Control',
+ 'public, s-maxage=10, stale-while-revalidate=59'
+ );
+ let image_events: MatrixImageEvents[] = [];
+ try {
+ const data = await get_data();
+
+ // TODO fix this. It is super inefficent.
+ for (let user of data) {
+ // TODO check what happens if this is a non public image.
+ // We dont need many events
+ const roomId = await client?.followUser(user.user_room);
+ const events = await client?.getTimeline(roomId, 100);
+ // Filter events by type
+ image_events = [...image_events, ...(events.filter((event) => event.type == "m.image_gallery" || event.type == "m.image") as MatrixImageEvents[])];
+ console.log("Adding", image_events.length, "items");
+ }
+
+
+ return {
+ props: {
+ image_events: image_events
+ }
+ };
+ } catch {
+ return { notFound: true, props: {} };
+ }
+};
-export default App;
+export default withRouter(Home);
+\ No newline at end of file
diff --git a/pages/post/[id].tsx b/pages/post/[id].tsx
@@ -8,7 +8,7 @@ import { GetServerSideProps, InferGetServerSidePropsType } from "next";
import Head from "next/head";
import { NextRouter, withRouter } from "next/router";
import { PureComponent, ReactNode } from "react";
-import { ClientContext } from "../../components/ClientContext";
+import { client, ClientContext } from "../../components/ClientContext";
import Header from "../../components/Header";
import { ImageEvent, ImageGalleryEvent, MatrixEventBase, MatrixImageEvents } from "../../helpers/event_types";
import { constMatrixArtServer } from "../../helpers/matrix_client";
@@ -36,7 +36,9 @@ class Post extends PureComponent<Props, State> {
super(props);
this.state = {
- hasFullyLoaded: false,
+ hasFullyLoaded: props.hasFullyLoaded,
+ image_event: props.image_event,
+ displayname: props.displayname,
isLoadingImages: false
};
}
@@ -71,25 +73,25 @@ class Post extends PureComponent<Props, State> {
for (let user of this.props.directory_data) {
// We dont need many events
const roomId = await this.context.client?.followUser(user.user_room);
- await this.context.client?.getTimeline(roomId, 100, async (events) => {
- // Filter events by type
- const image_event = events.find((event) => (event.type === "m.image_gallery" || event.type === "m.image") && event.event_id === event_id);
- try {
- // TODO this should get handled if null
- const profile = await this.context.client.getProfile(image_event!.sender);
- this.setState({
- image_event: image_event as MatrixImageEvents,
- displayname: profile.displayname,
- });
+ const events = await this.context.client?.getTimeline(roomId, 100); // Filter events by type
+ const image_event = events.find((event) => (event.type === "m.image_gallery" || event.type === "m.image") && event.event_id === event_id);
+ if (image_event == null) {
+ continue;
+ }
+ try {
+ const profile = await this.context.client.getProfile(image_event.sender);
+ this.setState({
+ image_event: image_event as MatrixImageEvents,
+ displayname: profile.displayname,
+ });
- } catch (error) {
- console.debug(`Failed to fetch profile for user ${image_event?.sender}:`, error);
- this.setState({
- image_event: image_event as MatrixImageEvents,
- displayname: image_event?.sender,
- });
- }
- });
+ } catch (error) {
+ console.debug(`Failed to fetch profile for user ${image_event.sender}:`, error);
+ this.setState({
+ image_event: image_event as MatrixImageEvents,
+ displayname: image_event.sender,
+ });
+ }
}
} catch (error) {
this.setState({
@@ -314,10 +316,48 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
try {
const data = await get_data();
+ // TODO fix this. It is super inefficent.
+ for (let user of data) {
+ // TODO check what happens if this is a non public image.
+ // We dont need many events
+ const roomId = await client?.followUser(user.user_room);
+ const events = await client?.getTimeline(roomId, 100);
+ // Filter events by type
+ const image_event = events.find((event) => (event.type === "m.image_gallery" || event.type === "m.image") && event.event_id === event_id);
+ if (image_event == null) {
+ continue;
+ }
+ try {
+ const profile = await client.getProfile(image_event.sender);
+ return {
+ props: {
+ directory_data: data,
+ image_event: image_event as MatrixImageEvents,
+ event_id: event_id,
+ hasFullyLoaded: true,
+ displayname: profile.displayname
+ }
+ };
+ } catch (error) {
+ console.debug(`Failed to fetch profile for user ${image_event.sender}:`, error);
+ return {
+ props: {
+ directory_data: data,
+ image_event: image_event as MatrixImageEvents,
+ event_id: event_id,
+ hasFullyLoaded: true,
+ displayname: image_event.sender
+ }
+ };
+ }
+ }
+
return {
props: {
- directory_data: data, event_id: event_id
+ directory_data: data,
+ event_id: event_id,
+ hasFullyLoaded: false,
}
};
} catch {