commit 3e81f3bd0fce4a70fd9f6958b664c75f906d7361
parent b52cb312b52ac8d631f2a628fb3105d378bc6019
Author: MTRNord <mtrnord1@gmail.com>
Date: Tue, 3 Jan 2023 21:16:35 +0100
load images async, preconnect to matrix server and fix deploying the page
Diffstat:
10 files changed, 337 insertions(+), 77 deletions(-)
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
@@ -26,6 +26,9 @@ jobs:
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4.4.1
+ env:
+ BUILD_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
branch: gh-pages # The branch the action should deploy to.
folder: dist # The folder the action should deploy.
diff --git a/index.html b/index.html
@@ -11,6 +11,7 @@
global = globalThis // fix for "global not defined" error
globalThis.OLM_OPTIONS = undefined; // Fix missing var for olm
</script>
+ <link rel="preconnect" href="<%= env.VITE_MATRIX_SERVER_URL %>">
</head>
<body>
diff --git a/package-lock.json b/package-lock.json
@@ -17,7 +17,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-plock": "github:MTRNord/react-plock#MTRNord/patch-react",
- "react-router-dom": "^6.6.1"
+ "react-router-dom": "^6.6.1",
+ "react-spinners": "^0.13.7"
},
"devDependencies": {
"@types/events": "^3.0.0",
@@ -33,7 +34,8 @@
"postcss": "^8.4.20",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.4",
- "vite": "^4.0.4"
+ "vite": "^4.0.4",
+ "vite-plugin-ejs": "^1.6.4"
}
},
"node_modules/@babel/code-frame": {
@@ -1447,6 +1449,12 @@
"node": ">=8"
}
},
+ "node_modules/async": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
+ "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
+ "dev": true
+ },
"node_modules/autoprefixer": {
"version": "10.4.13",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
@@ -2025,6 +2033,21 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
+ "node_modules/ejs": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
+ "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
+ "dev": true,
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/electron-to-chromium": {
"version": "1.4.284",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
@@ -2468,6 +2491,36 @@
"node": "^10.12.0 || >=12.0.0"
}
},
+ "node_modules/filelist": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
+ "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
+ "dev": true,
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
+ "node_modules/filelist/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/filelist/node_modules/minimatch": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz",
+ "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -2950,6 +3003,24 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
+ "node_modules/jake": {
+ "version": "10.8.5",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
+ "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
+ "dev": true,
+ "dependencies": {
+ "async": "^3.2.3",
+ "chalk": "^4.0.2",
+ "filelist": "^1.0.1",
+ "minimatch": "^3.0.4"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/js-sdsl": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
@@ -3856,6 +3927,15 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-spinners": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.7.tgz",
+ "integrity": "sha512-mRamn56bfxWbGcacif5RT3UbeJaXi2AttjtPwSmomuv2IcxjpbfETCzdTvaQpNDk0E33ENJsStsQeKAZFuJcpA==",
+ "peerDependencies": {
+ "react": "^16.0.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -4742,6 +4822,15 @@
}
}
},
+ "node_modules/vite-plugin-ejs": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/vite-plugin-ejs/-/vite-plugin-ejs-1.6.4.tgz",
+ "integrity": "sha512-23p1RS4PiA0veXY5/gHZ60pl3pPvd8NEqdBsDgxNK8nM1rjFFDcVb0paNmuipzCgNP/Y0f/Id22M7Il4kvZ2jA==",
+ "dev": true,
+ "dependencies": {
+ "ejs": "^3.1.8"
+ }
+ },
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
@@ -5732,6 +5821,12 @@
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
},
+ "async": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
+ "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
+ "dev": true
+ },
"autoprefixer": {
"version": "10.4.13",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
@@ -6112,6 +6207,15 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
+ "ejs": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
+ "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
+ "dev": true,
+ "requires": {
+ "jake": "^10.8.5"
+ }
+ },
"electron-to-chromium": {
"version": "1.4.284",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
@@ -6455,6 +6559,35 @@
"flat-cache": "^3.0.4"
}
},
+ "filelist": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
+ "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
+ "dev": true,
+ "requires": {
+ "minimatch": "^5.0.1"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "minimatch": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.2.tgz",
+ "integrity": "sha512-bNH9mmM9qsJ2X4r2Nat1B//1dJVcn3+iBLa3IgqJ7EbGaDNepL9QSHOxN4ng33s52VMMhhIfgCYDk3C4ZmlDAg==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ }
+ }
+ },
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
@@ -6790,6 +6923,18 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
+ "jake": {
+ "version": "10.8.5",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
+ "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
+ "dev": true,
+ "requires": {
+ "async": "^3.2.3",
+ "chalk": "^4.0.2",
+ "filelist": "^1.0.1",
+ "minimatch": "^3.0.4"
+ }
+ },
"js-sdsl": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
@@ -7409,6 +7554,12 @@
"react-router": "6.6.1"
}
},
+ "react-spinners": {
+ "version": "0.13.7",
+ "resolved": "https://registry.npmjs.org/react-spinners/-/react-spinners-0.13.7.tgz",
+ "integrity": "sha512-mRamn56bfxWbGcacif5RT3UbeJaXi2AttjtPwSmomuv2IcxjpbfETCzdTvaQpNDk0E33ENJsStsQeKAZFuJcpA==",
+ "requires": {}
+ },
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -8012,6 +8163,15 @@
"rollup": "^3.7.0"
}
},
+ "vite-plugin-ejs": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/vite-plugin-ejs/-/vite-plugin-ejs-1.6.4.tgz",
+ "integrity": "sha512-23p1RS4PiA0veXY5/gHZ60pl3pPvd8NEqdBsDgxNK8nM1rjFFDcVb0paNmuipzCgNP/Y0f/Id22M7Il4kvZ2jA==",
+ "dev": true,
+ "requires": {
+ "ejs": "^3.1.8"
+ }
+ },
"wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
diff --git a/package.json b/package.json
@@ -19,7 +19,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-plock": "github:MTRNord/react-plock#MTRNord/patch-react",
- "react-router-dom": "^6.6.1"
+ "react-router-dom": "^6.6.1",
+ "react-spinners": "^0.13.7"
},
"devDependencies": {
"@types/events": "^3.0.0",
@@ -35,6 +36,7 @@
"postcss": "^8.4.20",
"tailwindcss": "^3.2.4",
"typescript": "^4.9.4",
- "vite": "^4.0.4"
+ "vite": "^4.0.4",
+ "vite-plugin-ejs": "^1.6.4"
}
}
diff --git a/src/components/header.tsx b/src/components/header.tsx
@@ -1,37 +1,29 @@
import { Link } from "react-router-dom";
-import { PureComponent } from "react";
+import { useContext } from "react";
import { Client } from "../context";
-import { MatrixClient } from "../matrix/client";
-export class Header extends PureComponent {
- static contextType = Client;
- declare context: React.ContextType<typeof Client>;
-
- render() {
- const client: MatrixClient | undefined = this.context;
- return (
- <div className="m-12 flex flex-col lg:flex-row items-center justify-between">
- <Link to="/"><img alt="Matrix Art" src="Logo_colored.svg" className="ease-in-out hover:scale-105 trandform transition-transform duration-300" /></Link>
- <div className="flex items-center mt-8 lg:mt-0 flex-col sm:flex-row">
- <div className="flex items-center justify-between w-80 max-w-80 mx-6 ease-in-out hover:scale-105 trandform transition-transform duration-300">
- <div className="absolute ml-4">
- <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#AAB3CF">
- <path d="M0 0h24v24H0V0z" fill="none" />
- <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
- </svg>
- </div>
- <input className="search-bg shadow rounded-2xl border-0 py-3 px-4 pl-10 text-data" placeholder="Search"></input>
+export function Header() {
+ const client = useContext(Client);
+ return (
+ <div className="m-12 flex flex-col lg:flex-row items-center justify-between">
+ <Link to="/"><img alt="Matrix Art" src="Logo_colored.svg" className="ease-in-out hover:scale-105 trandform transition-transform duration-300" /></Link>
+ <div className="flex items-center mt-8 lg:mt-0 flex-col sm:flex-row">
+ <div className="flex items-center justify-between w-80 max-w-80 mx-6 ease-in-out hover:scale-105 trandform transition-transform duration-300">
+ <div className="absolute ml-4">
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#AAB3CF">
+ <path d="M0 0h24v24H0V0z" fill="none" />
+ <path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
+ </svg>
</div>
- {
+ <input className="search-bg shadow rounded-2xl border-0 py-3 px-4 pl-10 text-data" placeholder="Search"></input>
+ </div>
+ {
- !client?.isLoggedIn() ? <Link to="/join" className="text-white font-bold text-1xl logo-bg rounded-2xl py-3 px-12 shadow mt-4 sm:mt-0 transform transition-transform ease-in-out duration-300 hover:scale-105">Join</Link> :
- <Link to="/" className="text-white font-bold text-1xl logo-bg rounded-2xl py-3 px-12 shadow mt-4 sm:mt-0 transform transition-transform ease-in-out duration-300 hover:scale-105">Post</Link>
- }
+ !client?.isLoggedIn() ? <Link to="/join" className="text-white font-bold text-1xl logo-bg rounded-2xl py-3 px-12 shadow mt-4 sm:mt-0 transform transition-transform ease-in-out duration-300 hover:scale-105">Join</Link> :
+ <Link to="/" className="text-white font-bold text-1xl logo-bg rounded-2xl py-3 px-12 shadow mt-4 sm:mt-0 transform transition-transform ease-in-out duration-300 hover:scale-105">Post</Link>
+ }
- </div>
</div>
- );
- }
-}
-
-Header.contextType = Client;
-\ No newline at end of file
+ </div>
+ );
+}
+\ No newline at end of file
diff --git a/src/components/post.tsx b/src/components/post.tsx
@@ -1,22 +1,20 @@
-import { PureComponent } from "react";
+import { Suspense } from "react";
import { PostData } from "../data/post";
import { UserData } from "../data/user";
+import { SuspenseImage } from "../utils/asyncImages";
+import { BounceLoader } from "react-spinners";
type Props = {
user: UserData;
post: PostData;
};
-export class Post extends PureComponent<Props> {
- constructor(props: Props) {
- super(props);
- }
- render() {
- const { user, post }: Props = this.props;
- return (
+export function Post({ user, post }: Props) {
+ return (
+ <Suspense fallback={<div className="flex flex-col w-full"><BounceLoader color="#FEA500" /></div>}>
<div className="flex flex-col">
<a id="gallery_click" href={post.content.file.url} className="w-full" target="_blank" rel="noreferrer">
- <img decoding="async" loading="lazy" className="rounded-3xl shadow object-cover transform transition-transform ease-in-out duration-300 hover:scale-105" src={post.content.file.url} />
+ <SuspenseImage className="rounded-3xl shadow object-cover transform transition-transform ease-in-out duration-300 hover:scale-105" src={post.content.file.url} />
</a>
<div className="flex items-center justify-between py-4">
<a className="flex items-center" href="#">
@@ -45,6 +43,6 @@ export class Post extends PureComponent<Props> {
</div>
</div>
</div>
- );
- }
+ </Suspense>
+ );
}
\ No newline at end of file
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
@@ -1,4 +1,3 @@
-import { PureComponent } from "react";
import { Header } from "../components/header";
import { Post } from "../components/post";
import { Plock } from "react-plock";
@@ -13,39 +12,37 @@ const BREAKPOINTS = [
{ size: 1401, columns: 4 },
];
-export class Home extends PureComponent {
- render() {
- return (
- <div className="flex flex-col">
- <header>
- <Header />
- </header>
- <main className="m-12 mt-6">
- <h1 className="text-3xl font-bold mb-4 text-white">Explore</h1>
- <div className="flex justify-center" id="gallery">
- <Plock gap={"24px"} breakpoints={BREAKPOINTS}>
- {/*
+export function Home() {
+ return (
+ <div className="flex flex-col">
+ <header>
+ <Header />
+ </header>
+ <main className="m-12 mt-6">
+ <h1 className="text-3xl font-bold mb-4 text-white">Explore</h1>
+ <div className="flex justify-center" id="gallery">
+ <Plock gap={"24px"} breakpoints={BREAKPOINTS}>
+ {/*
// @ts-ignore */}
- <Post user={new UserData("", "Person A", "https://unsplash.com/photos/_cvwXhGqG-o/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8OHx8ZmFjZXxlbnwwfDJ8fHwxNjQ4Nzc5NzYx")} post={new PostData("", { file: { url: "https://unsplash.com/photos/UWmrTAQ75iA/download?ixid=MnwxMjA3fDB8MXxhbGx8Mnx8fHx8fDJ8fDE2NDg4MzI0Njg" } })} />
- {/*
+ <Post user={new UserData("", "Person A", "https://unsplash.com/photos/_cvwXhGqG-o/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8OHx8ZmFjZXxlbnwwfDJ8fHwxNjQ4Nzc5NzYx")} post={new PostData("", { file: { url: "https://unsplash.com/photos/UWmrTAQ75iA/download?ixid=MnwxMjA3fDB8MXxhbGx8Mnx8fHx8fDJ8fDE2NDg4MzI0Njg" } })} />
+ {/*
// @ts-ignore */}
- <Post user={new UserData("", "Person B", "https://unsplash.com/photos/uJ8LNVCBjFQ/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTN8fGZhY2V8ZW58MHwyfHx8MTY0ODc3OTc2MQ")} post={new PostData("", { file: { url: "https://unsplash.com/photos/7ptbiEPxKMQ/download?ixid=MnwxMjA3fDB8MXxhbGx8OXx8fHx8fDJ8fDE2NDg4MzI0Njg" } })} />
- {/*
+ <Post user={new UserData("", "Person B", "https://unsplash.com/photos/uJ8LNVCBjFQ/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTN8fGZhY2V8ZW58MHwyfHx8MTY0ODc3OTc2MQ")} post={new PostData("", { file: { url: "https://unsplash.com/photos/7ptbiEPxKMQ/download?ixid=MnwxMjA3fDB8MXxhbGx8OXx8fHx8fDJ8fDE2NDg4MzI0Njg" } })} />
+ {/*
// @ts-ignore */}
- <Post user={new UserData("", "Person C", "https://unsplash.com/photos/u3pi6HhSYew/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTd8fGZhY2V8ZW58MHwyfHx8MTY0ODc3OTc2MQ")} post={new PostData("", { file: { url: "https://unsplash.com/photos/ZiPlVmmKEYA/download?ixid=MnwxMjA3fDB8MXxhbGx8Mzl8fHx8fHwyfHwxNjQ4ODM2MDIz" } })} />
- {/*
+ <Post user={new UserData("", "Person C", "https://unsplash.com/photos/u3pi6HhSYew/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTd8fGZhY2V8ZW58MHwyfHx8MTY0ODc3OTc2MQ")} post={new PostData("", { file: { url: "https://unsplash.com/photos/ZiPlVmmKEYA/download?ixid=MnwxMjA3fDB8MXxhbGx8Mzl8fHx8fHwyfHwxNjQ4ODM2MDIz" } })} />
+ {/*
// @ts-ignore */}
- <Post user={new UserData("", "Person D", "https://unsplash.com/photos/TW_z_iUD_bQ/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8ODR8fHBvcnRyYWl0fGVufDB8Mnx8fDE2NDg4NDQ3MjQ")} post={new PostData("", { file: { url: "https://unsplash.com/photos/FTQJBGDxKXg/download?ixid=MnwxMjA3fDB8MXxhbGx8M3x8fHx8fDJ8fDE2NDg4NDMyNDY" } })} />
- {/*
+ <Post user={new UserData("", "Person D", "https://unsplash.com/photos/TW_z_iUD_bQ/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8ODR8fHBvcnRyYWl0fGVufDB8Mnx8fDE2NDg4NDQ3MjQ")} post={new PostData("", { file: { url: "https://unsplash.com/photos/FTQJBGDxKXg/download?ixid=MnwxMjA3fDB8MXxhbGx8M3x8fHx8fDJ8fDE2NDg4NDMyNDY" } })} />
+ {/*
// @ts-ignore */}
- <Post user={new UserData("", "Person E", "https://unsplash.com/photos/kYtN89E5ce4/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTA0fHxwb3J0cmFpdHxlbnwwfDJ8fHwxNjQ4ODQ0NzQy")} post={new PostData("", { file: { url: "https://unsplash.com/photos/zfhxiaZO3JE/download?ixid=MnwxMjA3fDB8MXxhbGx8Mjl8fHx8fHwyfHwxNjQ4ODQzMjQ1" } })} />
- {/*
+ <Post user={new UserData("", "Person E", "https://unsplash.com/photos/kYtN89E5ce4/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTA0fHxwb3J0cmFpdHxlbnwwfDJ8fHwxNjQ4ODQ0NzQy")} post={new PostData("", { file: { url: "https://unsplash.com/photos/zfhxiaZO3JE/download?ixid=MnwxMjA3fDB8MXxhbGx8Mjl8fHx8fHwyfHwxNjQ4ODQzMjQ1" } })} />
+ {/*
// @ts-ignore */}
- <Post user={new UserData("", "Person E", "https://unsplash.com/photos/kBonaevi3yA/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTE5fHxwb3J0cmFpdHxlbnwwfDJ8fHwxNjQ4ODQ0NzQy")} post={new PostData("", { file: { url: "https://unsplash.com/photos/jm-JKqrAXsA/download?ixid=MnwxMjA3fDB8MXxhbGx8MTAyfHx8fHx8Mnx8MTY0ODg0MzI1NQ" } })} />
- </Plock>
- </div>
- </main>
- </div>
- );
- }
+ <Post user={new UserData("", "Person E", "https://unsplash.com/photos/kBonaevi3yA/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTE5fHxwb3J0cmFpdHxlbnwwfDJ8fHwxNjQ4ODQ0NzQy")} post={new PostData("", { file: { url: "https://unsplash.com/photos/jm-JKqrAXsA/download?ixid=MnwxMjA3fDB8MXxhbGx8MTAyfHx8fHx8Mnx8MTY0ODg0MzI1NQ" } })} />
+ </Plock>
+ </div>
+ </main>
+ </div>
+ );
}
\ No newline at end of file
diff --git a/src/utils/asyncImages.tsx b/src/utils/asyncImages.tsx
@@ -0,0 +1,45 @@
+// First we need a type of cache to avoid creating resources for images
+
+import React from "react";
+import { ImgHTMLAttributes } from "react";
+import { createResource, Resource } from "./resources";
+
+// we have already fetched in the past
+export const cache = new Map<string, any>();
+
+// then we create our loadImage function, this function receives the source
+// of the image and returns a resource
+export function loadImage(source: string): Resource<string> {
+ // here we start getting the resource from the cache
+ let resource = cache.get(source);
+ // and if it's there we return it immediately
+ if (resource) return resource;
+ // but if it's not we create a new resource
+ resource = createResource<string>(
+ () =>
+ // in our async function we create a promise
+ new Promise((resolve, reject) => {
+ // then create a new image element
+ const img = new window.Image();
+ // set the src to our source
+ img.src = source;
+ // and start listening for the load event to resolve the promise
+ img.addEventListener("load", () => resolve(source));
+ // and also the error event to reject the promise
+ img.addEventListener("error", () =>
+ reject(new Error(`Failed to load image ${source}`))
+ );
+ })
+ );
+ // before finishing we save the new resource in the cache
+ cache.set(source, resource);
+ // and return return it
+ return resource;
+}
+
+export function SuspenseImage(
+ props: ImgHTMLAttributes<HTMLImageElement>
+): JSX.Element {
+ loadImage(props.src!).read();
+ return <img {...props} />;
+}
+\ No newline at end of file
diff --git a/src/utils/resources.ts b/src/utils/resources.ts
@@ -0,0 +1,52 @@
+// A Resource is an object with a read method returning the payload
+export interface Resource<Payload> {
+ read: () => Payload;
+}
+
+export type status = "pending" | "success" | "error";
+
+// this function let us get a new function using the asyncFn we pass
+// this function also receives a payload and return us a resource with
+// that payload assigned as type
+export function createResource<Payload>(
+ asyncFn: () => Promise<Payload>
+): Resource<Payload> {
+ // we start defining our resource is on a pending status
+ let status: status = "pending";
+ // and we create a variable to store the result
+ let result: any;
+ // then we immediately start running the `asyncFn` function
+ // and we store the resulting promise
+ const promise = asyncFn().then(
+ (r: Payload) => {
+ // once it's fulfilled we change the status to success
+ // and we save the returned value as result
+ status = "success";
+ result = r;
+ },
+ (e: Error) => {
+ // once it's rejected we change the status to error
+ // and we save the returned error as result
+ status = "error";
+ result = e;
+ }
+ );
+ // lately we return an error object with the read method
+ return {
+ read(): Payload {
+ // here we will check the status value
+ switch (status) {
+ case "pending":
+ // if it's still pending we throw the promise
+ // throwing a promise is how Suspense know our component is not ready
+ throw promise;
+ case "error":
+ // if it's error we throw the error
+ throw result;
+ case "success":
+ // if it's success we return the result
+ return result;
+ }
+ },
+ };
+}
+\ No newline at end of file
diff --git a/vite.config.ts b/vite.config.ts
@@ -1,9 +1,17 @@
import { defineConfig, splitVendorChunkPlugin } from 'vite';
import react from "@vitejs/plugin-react-swc";
+import { ViteEjsPlugin } from "vite-plugin-ejs";
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react(), splitVendorChunkPlugin()],
+ plugins: [
+ react(),
+ splitVendorChunkPlugin(),
+ ViteEjsPlugin((viteConfig) => ({
+ // viteConfig is the current Vite resolved config
+ env: viteConfig.env,
+ })),
+ ],
resolve: {
browserField: false,
},