articles canvas mode
This commit is contained in:
@@ -1,44 +0,0 @@
|
|||||||
import {createContext, PropsWithChildren, useContext, useEffect, useState} from "react";
|
|
||||||
import {Reader} from "./Reader";
|
|
||||||
import {Article, ARTICLES} from "./articles";
|
|
||||||
import {useRouter} from "../router/RouterContext";
|
|
||||||
import {Path} from "../router/router";
|
|
||||||
|
|
||||||
const articleContext =
|
|
||||||
createContext<ReturnType<typeof useState<Article | undefined>>>([undefined, function(){}]);
|
|
||||||
|
|
||||||
export function useArticle() {
|
|
||||||
return useContext(articleContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ARTICLE_QUERY = "a";
|
|
||||||
export default function ArticleProvider({ children }: PropsWithChildren) {
|
|
||||||
const [article, setArticle] = useState<Article | undefined>(undefined);
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const params = new URLSearchParams(location.search);
|
|
||||||
if(params.has(ARTICLE_QUERY)) {
|
|
||||||
const id = Number.parseInt(params.get(ARTICLE_QUERY)!);
|
|
||||||
setArticle(ARTICLES[id]);
|
|
||||||
}
|
|
||||||
}, [setArticle]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if(!article) {
|
|
||||||
// query param
|
|
||||||
const params = new URLSearchParams(location.search);
|
|
||||||
if(params.has(ARTICLE_QUERY)) {
|
|
||||||
router.replacePage(location.pathname as Path);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const id = ARTICLES.indexOf(article);
|
|
||||||
router.replacePage(`${location.pathname}?${ARTICLE_QUERY}=${id}` as Path);
|
|
||||||
}
|
|
||||||
}, [article]);
|
|
||||||
|
|
||||||
return <articleContext.Provider value={[article, setArticle]}>
|
|
||||||
<Reader/>
|
|
||||||
{children}
|
|
||||||
</articleContext.Provider>;
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,67 @@
|
|||||||
import {useArticle} from "./ArticleContext";
|
|
||||||
import "./list.sass";
|
import "./list.sass";
|
||||||
import {ReactComponent as BoxSvg} from "../img/blog-box.svg";
|
import {ReactComponent as BoxSvg} from "../img/blog-box.svg";
|
||||||
import {Article, ARTICLES} from "./articles";
|
import {Article, ARTICLES} from "./articles";
|
||||||
import {motion} from "motion/react";
|
import {motion} from "motion/react";
|
||||||
import {AnimateFade} from "../animations";
|
import {AnimateFade} from "../animations";
|
||||||
|
import {Canvas} from "@react-three/fiber";
|
||||||
|
import {Environment, Html, OrbitControls} from "@react-three/drei";
|
||||||
|
import MilkyWay from "../img/2k_stars_milky_way.jpg";
|
||||||
|
import {Sun} from "../index/Canvas";
|
||||||
|
import {Bloom, EffectComposer, Vignette} from "@react-three/postprocessing";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {Reader} from "./Reader";
|
||||||
|
import {useRouter} from "../router/RouterContext";
|
||||||
|
import {Path} from "../router/router";
|
||||||
|
|
||||||
|
const ARTICLE_QUERY = "a";
|
||||||
const main = AnimateFade();
|
const main = AnimateFade();
|
||||||
export default function ArticlesList() {
|
export default function ArticlesList() {
|
||||||
return <motion.main animate={main.animate} initial={main.initial} className="art-ctr">
|
const [article, setArticle] = useState<Article | null>(null);
|
||||||
<section className="spacer"/>
|
const router = useRouter();
|
||||||
<section className="articles">
|
|
||||||
{
|
useEffect(() => {
|
||||||
ARTICLES.map(a =>
|
const params = new URLSearchParams(location.search);
|
||||||
<BlogBox key={a.name} article={a} />
|
if(params.has(ARTICLE_QUERY)) {
|
||||||
)
|
const id = Number.parseInt(params.get(ARTICLE_QUERY)!);
|
||||||
|
setArticle(ARTICLES[id] ?? null);
|
||||||
|
}
|
||||||
|
}, [setArticle]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if(!article) {
|
||||||
|
// query param
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
if(params.has(ARTICLE_QUERY)) {
|
||||||
|
router.replacePage(location.pathname as Path);
|
||||||
}
|
}
|
||||||
</section>
|
} else {
|
||||||
<section className="spacer"/>
|
const id = ARTICLES.indexOf(article);
|
||||||
|
router.replacePage(`${location.pathname}?${ARTICLE_QUERY}=${id}` as Path);
|
||||||
|
}
|
||||||
|
}, [article]);
|
||||||
|
|
||||||
|
|
||||||
|
return <motion.main animate={main.animate} initial={main.initial} className="art-ctr">
|
||||||
|
<Canvas camera={{position: [0,0,20]}}>
|
||||||
|
<Environment background files={MilkyWay}/>
|
||||||
|
<EffectComposer>
|
||||||
|
<Bloom luminanceThreshold={0} luminanceSmoothing={0.9} />
|
||||||
|
<Vignette eskil={false} offset={0.1} darkness={1.1} />
|
||||||
|
</EffectComposer>
|
||||||
|
<Sun/>
|
||||||
|
<OrbitControls enablePan={false} enableZoom />
|
||||||
|
<Html className="articles" zIndexRange={[100,0]} prepend center transform>
|
||||||
|
{
|
||||||
|
ARTICLES.map(a => <BlogBox setReading={setArticle} article={a}/>)
|
||||||
|
}
|
||||||
|
</Html>
|
||||||
|
</Canvas>
|
||||||
|
<Reader post={article} setPost={setArticle}/>
|
||||||
</motion.main>
|
</motion.main>
|
||||||
}
|
}
|
||||||
|
|
||||||
function BlogBox(props: { article: Article }) {
|
function BlogBox(props: { article: Article, setReading: (_: Article | null) => void }) {
|
||||||
const {article, article: {name, description, thumbnail, published} } = props;
|
const {article, setReading, article: {name, description, thumbnail, published} } = props;
|
||||||
const [, setReading] = useArticle();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className="link" onClick={() => setReading(article)}>
|
<button className="link" onClick={() => setReading(article)}>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import {useArticle} from "./ArticleContext";
|
|
||||||
import {animate, motion} from "framer-motion";
|
import {animate, motion} from "framer-motion";
|
||||||
import {useEffect} from "react";
|
import {useEffect} from "react";
|
||||||
import {AnimateFade, AnimateTemplate} from "../animations";
|
import {AnimateFade, AnimateTemplate} from "../animations";
|
||||||
@@ -6,11 +5,11 @@ import "./reader.sass";
|
|||||||
import {ExternalLink} from "../router/Link";
|
import {ExternalLink} from "../router/Link";
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||||
import {faClose} from "@fortawesome/free-solid-svg-icons/faClose";
|
import {faClose} from "@fortawesome/free-solid-svg-icons/faClose";
|
||||||
|
import {Article} from "./articles";
|
||||||
|
|
||||||
const READER : AnimateTemplate = AnimateFade();
|
const READER : AnimateTemplate = AnimateFade();
|
||||||
|
|
||||||
export function Reader() {
|
export function Reader({post, setPost}: {post: Article | null, setPost: (_: Article | null) => void}) {
|
||||||
const [post, setPost] = useArticle();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function pressEscape(e: KeyboardEvent) {
|
function pressEscape(e: KeyboardEvent) {
|
||||||
@@ -23,7 +22,7 @@ export function Reader() {
|
|||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
animate(".reader", READER.initial)
|
animate(".reader", READER.initial)
|
||||||
.then(() => setPost(undefined));
|
.then(() => setPost(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
return post ? (
|
return post ? (
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
@use "../nav/nav"
|
@use "../nav/nav"
|
||||||
@use "../index"
|
@use "../index"
|
||||||
$entry-width: 400px
|
$entry-width: 400px
|
||||||
|
|
||||||
|
canvas
|
||||||
|
width: 100vw
|
||||||
|
height: calc(100vh - 100px)
|
||||||
|
|
||||||
.blog-box
|
.blog-box
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
@@ -39,6 +44,7 @@ $entry-width: 400px
|
|||||||
color: grey
|
color: grey
|
||||||
|
|
||||||
.articles
|
.articles
|
||||||
|
z-index: -999
|
||||||
display: flex
|
display: flex
|
||||||
flex-grow: 3
|
flex-grow: 3
|
||||||
justify-content: center
|
justify-content: center
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
align-items: center
|
align-items: center
|
||||||
border-radius: 15px
|
border-radius: 15px
|
||||||
margin: auto
|
margin: auto
|
||||||
padding: 30px
|
|
||||||
overflow-y: scroll
|
overflow-y: scroll
|
||||||
overflow-x: hidden
|
overflow-x: hidden
|
||||||
background: white
|
background: white
|
||||||
@@ -64,4 +63,4 @@
|
|||||||
font-weight: bold
|
font-weight: bold
|
||||||
color: red !important
|
color: red !important
|
||||||
background: white
|
background: white
|
||||||
border-radius: 15px
|
border-radius: 999px
|
||||||
@@ -5,7 +5,6 @@ import {RouterProvider} from "./router/RouterContext";
|
|||||||
import "@fontsource/inter"; // Defaults to weight 400
|
import "@fontsource/inter"; // Defaults to weight 400
|
||||||
import "@fontsource/neuton";
|
import "@fontsource/neuton";
|
||||||
import {Routes} from "./router/Route";
|
import {Routes} from "./router/Route";
|
||||||
import ArticleProvider from "./articles/ArticleContext";
|
|
||||||
|
|
||||||
const Nav = React.lazy(() => import("./nav/Nav"));
|
const Nav = React.lazy(() => import("./nav/Nav"));
|
||||||
|
|
||||||
@@ -16,10 +15,8 @@ const root = ReactDOM.createRoot(
|
|||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<RouterProvider>
|
<RouterProvider>
|
||||||
<ArticleProvider>
|
<Nav/>
|
||||||
<Nav/>
|
<Routes/>
|
||||||
<Routes/>
|
|
||||||
</ArticleProvider>
|
|
||||||
</RouterProvider>
|
</RouterProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default function Canvas() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Sun() {
|
export function Sun() {
|
||||||
return (
|
return (
|
||||||
<mesh position={[-100, 0, -100]}>
|
<mesh position={[-100, 0, -100]}>
|
||||||
<sphereGeometry args={[5, 32, 32]} />
|
<sphereGeometry args={[5, 32, 32]} />
|
||||||
|
|||||||
Reference in New Issue
Block a user