matrix-art

An image gallery for Matrix
git clone git://archive.git.mtrnord.blog/MTRNord/matrix-art.git
Log | Files | Refs | README | LICENSE

commit 91ad818ad2be4c0f55d2d31613ba8a9eafb17e30
parent b53580e256030d0d6ea50cc1e202ce6ee0f7257e
Author: MTRNord <mtrnord1@gmail.com>
Date:   Tue,  4 Jan 2022 02:43:38 +0100

Improve profile page header

Diffstat:
Mhelpers/event_types.ts | 14++++++++++++--
Mpages/Home.tsx | 4++--
Mpages/api/directory.tsx | 11++++-------
Mpages/login.tsx | 2--
Mpages/post/[id].tsx | 7++++---
Mpages/profile/[userid].tsx | 142++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mstyles/globals.css | 10++++++++++
7 files changed, 147 insertions(+), 43 deletions(-)

diff --git a/helpers/event_types.ts b/helpers/event_types.ts @@ -52,5 +52,15 @@ export type ImageGalleryEvent = MatrixEventBase & { content: ImageGalleryContent; }; +export type BannerEventContent = { + "m.text": string; + "m.file": FileEvent; + "m.image": ImageFields; +}; + +export type BannerEvent = MatrixEventBase & { + content: BannerEventContent; +}; + export type MatrixImageEvents = ImageEvent | ImageGalleryEvent; -export type MatrixEvent = MatrixImageEvents; -\ No newline at end of file +export type MatrixEvent = MatrixImageEvents | BannerEvent; +\ No newline at end of file diff --git a/pages/Home.tsx b/pages/Home.tsx @@ -1,7 +1,6 @@ import React, { Component } from 'react'; import { RingLoader } from 'react-spinners'; -import { ImageEvent, ImageGalleryEvent, MatrixEventBase, MatrixImageEvents } from '../helpers/event_types'; -import Link from 'next/link'; +import { MatrixEventBase, MatrixImageEvents } from '../helpers/event_types'; import Head from 'next/head'; import Header from '../components/Header'; import { ClientContext } from '../components/ClientContext'; @@ -68,6 +67,7 @@ export default class Home extends Component<Props, State>{ 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(); } diff --git a/pages/api/directory.tsx b/pages/api/directory.tsx @@ -20,9 +20,9 @@ export const get_data = async () => { console.log("Running in tests!"); return [ { - "user_id": "@MTRNord:art.midnightthoughts.space", - "user_room": "#@MTRNord:art.midnightthoughts.space", - "_id": "@MTRNord:art.midnightthoughts.space" + "user_id": "@mtrnord:art.midnightthoughts.space", + "user_room": "#@mtrnord:art.midnightthoughts.space", + "_id": "@mtrnord:art.midnightthoughts.space" } ]; } @@ -34,10 +34,6 @@ export const get_data = async () => { // TODO this is fully insecured. Make sure to use OpenID or something to verify the user of this. export default async function handler(req: NextApiRequest, res: NextApiResponse) { - db.info().then(function (info) { - console.log(info); - }); - // Run cors await cors(req, res); @@ -71,6 +67,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const data: { user_id: string; } = req.body; + console.log(data.user_id); try { const db_data = await db.get(data.user_id); await db.remove(db_data); diff --git a/pages/login.tsx b/pages/login.tsx @@ -1,11 +1,9 @@ import Head from "next/head"; import { NextRouter, withRouter } from "next/router"; import { Component, ReactNode } from "react"; -import { RingLoader } from "react-spinners"; import { ClientContext } from "../components/ClientContext"; import Header from "../components/Header"; import { constMatrixArtServer } from "../helpers/matrix_client"; -import { centerSpinner } from "./Home"; type Props = { router: NextRouter; diff --git a/pages/post/[id].tsx b/pages/post/[id].tsx @@ -53,6 +53,7 @@ class Post extends Component<Props, State> { try { let serverUrl = constMatrixArtServer + "/_matrix/client"; await this.context.client?.registerAsGuest(serverUrl); + await this.context.guest_client?.registerAsGuest(serverUrl); } catch (err) { console.error("Failed to register as guest:", err); } @@ -83,13 +84,13 @@ class Post extends Component<Props, State> { try { const profile = await this.context.client.getProfile(image_event[0].sender); this.setState({ - image_event: image_event[0], + image_event: image_event[0] as MatrixImageEvents, displayname: profile.displayname, }); } catch (ex) { console.debug(`Failed to fetch profile for user ${image_event[0].sender}:`, ex); this.setState({ - image_event: image_event[0], + image_event: image_event[0] as MatrixImageEvents, displayname: image_event[0].sender, }); } @@ -163,7 +164,7 @@ class Post extends Component<Props, State> { <div className="grow bg-[#f8f8f8] dark:bg-[#06070D] min-h-[400px] flex flex-col items-center"> <div className="flex flex-col items-start lg:min-w-[60rem] lg:w-[60rem]"> <h1 className="my-4 text-6xl text-gray-900 dark:text-gray-200 font-bold">{post_title}</h1> - <Link href={"/profile/" + encodeURIComponent(image_event.sender)}><h3 className="cursor-pointer mt-0 mb-4 text-l text-gray-900 dark:text-gray-200 font-normal">{this.state.displayname}</h3></Link> + <h3 className="cursor-pointer mt-0 mb-4 text-l text-gray-900 dark:text-gray-200 font-normal"><Link href={"/profile/" + encodeURIComponent(image_event.sender)}>{this.state.displayname}</Link></h3> {isImageGalleryEvent(image_event) ? this.renderImageGalleryTags(image_event) : isImageEvent(image_event) ? this.renderSingleImageTags(image_event) : <div key={(image_event as MatrixEventBase).event_id + "tags"}></div>} </div> </div> diff --git a/pages/profile/[userid].tsx b/pages/profile/[userid].tsx @@ -4,6 +4,8 @@ import { NextRouter, withRouter } from "next/router"; import { Component } from "react"; import { ClientContext } from "../../components/ClientContext"; import Header from "../../components/Header"; +import { BannerEvent, MatrixEvent } from "../../helpers/event_types"; +import { constMatrixArtServer } from "../../helpers/matrix_client"; type Props = InferGetServerSidePropsType<typeof getServerSideProps> & { router: NextRouter; @@ -11,6 +13,11 @@ type Props = InferGetServerSidePropsType<typeof getServerSideProps> & { type State = { displayname: string; + avatar_url: string; + events: MatrixEvent[] | []; + error?: any; + isLoadingImages: boolean; + hasFullyLoaded: boolean; }; class Profile extends Component<Props, State> { @@ -20,42 +27,104 @@ class Profile extends Component<Props, State> { super(props); this.state = { - displayname: this.props.mxid + displayname: this.props.mxid, + events: [] } as State; } async componentDidMount() { - if (!this.props.mxid) { + // auto-register as a guest if not logged in + if (!this.context.client?.accessToken) { + this.registerAsGuest(); + } else { + console.log("Already logged in"); + if (!this.props.mxid) { + return; + } + try { + const profile = await this.context.client.getProfile(this.props.mxid); + this.setState({ + displayname: profile.displayname, + avatar_url: profile.avatar_url, + }); + } catch (ex) { + console.debug(`Failed to fetch profile for user ${this.props.mxid}:`, ex); + } + await this.loadEvents(); + } + } + + 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 (err) { + console.error("Failed to register as guest:", err); + this.setState({ + error: "Failed to register as guest: " + JSON.stringify(err), + }); + } + } + + async loadEvents() { + const { isLoadingImages, hasFullyLoaded } = this.state; + if (isLoadingImages || hasFullyLoaded) { return; } + const client = this.context.client.isGuest ? this.context.client : this.context.guest_client; + this.setState({ + isLoadingImages: true, + }); try { - const profile = await this.context.client.getProfile(this.props.mxid); + const roomId = await client?.followUser("#" + this.props.mxid); + await client?.getTimeline(roomId, 100, (events) => { + const filtered_events = events.filter(event => event.type !== "m.room.member" && event.type !== "m.room.topic" && event.type !== "m.room.name" && event.type !== "m.room.power_levels"); + console.log("Adding ", filtered_events.length, " items"); + this.setState({ + events: filtered_events, + }); + }); + } catch (err) { this.setState({ - displayname: profile.displayname, + error: JSON.stringify(err), + }); + } finally { + this.setState({ + hasFullyLoaded: true, + isLoadingImages: false }); - } catch (ex) { - console.debug(`Failed to fetch profile for user ${this.props.mxid}:`, ex); } } + renderNotFound() { + return ( + <div className="h-full bg-[#fefefe]/[.95] dark:bg-[#14181E]/[.95]"> + <Head> + <title key="title">Matrix Art | User not Found</title> + <meta property="og:title" content="Matrix Art | User not Found" key="og-title" /> + <meta name="twitter:card" content="summary_large_image" key="og-twitter" /> + <meta name="twitter:title" content="Matrix Art | User not Found" key="og-twitter-title" /> + <meta property="og:type" content="website" key="og-type" /> + </Head> + <Header></Header> + <main className='h-full lg:pt-[108px] pt-[216px] z-0 flex items-center justify-center'> + <h1 className="text-6xl text-gray-900 dark:text-gray-200 font-bold">The User you wanted does not exist!</h1> + </main> + </div> + ); + } + render() { if (!this.props.mxid || !this.props.mxid?.startsWith("@")) { - return ( - <div className="h-full bg-[#fefefe]/[.95] dark:bg-[#14181E]/[.95]"> - <Head> - <title key="title">Matrix Art | User not Found</title> - <meta property="og:title" content="Matrix Art | User not Found" key="og-title" /> - <meta name="twitter:card" content="summary_large_image" key="og-twitter" /> - <meta name="twitter:title" content="Matrix Art | User not Found" key="og-twitter-title" /> - <meta property="og:type" content="website" key="og-type" /> - </Head> - <Header></Header> - <main className='h-full lg:pt-[108px] pt-[216px] z-0 flex items-center justify-center'> - <h1 className="text-6xl text-gray-900 dark:text-gray-200 font-bold">The User you wanted does not exist!</h1> - </main> - </div> - ); + return this.renderNotFound(); } + + const banner_event = this.state.events.filter(event => event.type === "matrixart.profile_banner")[0]; + const avatar_url = this.state.avatar_url; return ( <div className="h-full bg-[#f8f8f8] dark:bg-[#06070D]"> <Head> @@ -67,9 +136,29 @@ class Profile extends Component<Props, State> { </Head> <Header></Header> <main className='lg:pt-[108px] pt-[216px] z-0'> - <div className='z-[100] sticky lg:top-[108px] top-[216px] bg-[#fefefe]/[.95] dark:bg-[#14181E]/[.95]'> - <div className='h-[72px] 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'>{"User – " + this.state.displayname}</h1> + {banner_event ? <div style={{ + backgroundImage: `url(${this.context.client.downloadLink((banner_event as BannerEvent).content["m.file"].url)})` + }} + className="fixed top-[54px] w-full h-[520px] bg-cover bg-[position:50%]" + ></div> : null} + <div className="relative"> + <div className="relative mb-0 min-h-[700px]"> + <div id="transparent_gradient" className="absolute left-0 right-0 top-[626px] bottom-0"></div> + <div className="px-[60px] relative pt-[250px]"> + <div className="relative mt-0"> + <div className="relative inline-flex z-[1] mb-[40px]"> + <span> + <div className="block relative"> + {/* TODO fallback*/} + <img className="block object-cover rounded-[4px]" src={avatar_url} alt={this.state.displayname} title={this.state.displayname} /> + </div> + </span> + <div className="ml-[20px] flex flex-col justify-center"> + <h1 className="text-bold text-5xl text-gray-900 dark:text-gray-200 mt-[-17px] flex items-[flex-end]">{this.state.displayname}</h1> + </div> + </div> + </div> + </div> </div> </div> </main> @@ -90,7 +179,6 @@ export const getServerSideProps: GetServerSideProps = async (context) => { ); if (mxid && mxid.startsWith("@")) { try { - return { props: { mxid: mxid @@ -104,4 +192,4 @@ export const getServerSideProps: GetServerSideProps = async (context) => { }; -export default withRouter(Profile); -\ No newline at end of file +export default withRouter(Profile);; +\ No newline at end of file diff --git a/styles/globals.css b/styles/globals.css @@ -5,4 +5,14 @@ html, body, #__next { margin: 0; @apply h-full bg-[#f8f8f8] dark:bg-[#06070D]; +} + +#transparent_gradient:after { + background-image: linear-gradient(180deg,rgba(6,7,13,0) 0,#06070D 70%,#06070D); + content: ""; + display: block; + position: absolute; + width: 100%; + height: 500px; + transform: translateY(-100%); } \ No newline at end of file