From 56a0c5ba06df55e109326128ea165268fb7ce659 Mon Sep 17 00:00:00 2001 From: Luyang Liu Date: Tue, 2 Jan 2024 19:14:38 +1100 Subject: [PATCH] Removed non-existent build dependency for github workflow. and build Project Client and Set up Rust toolchain + Build Project Server as two separate jobs that run simultaneously. --- .github/workflows/build.yml | 25 +++- .github/workflows/deploy.yml | 40 +++++- client/.eslintignore | 1 - client/package-lock.json | 17 ++- client/package.json | 3 +- client/src/App.css | 5 + client/src/App.tsx | 130 ++++++++++-------- client/src/components/Card/Card.css | 3 +- client/src/components/Card/Card.tsx | 2 +- client/src/components/CodingCat/CodingCat.tsx | 2 +- .../ExperienceSection/ExperienceSection.tsx | 3 +- .../ExperienceSectionEvent.tsx | 9 +- client/src/components/Gallery/Gallery.tsx | 1 - .../Gallery/GalleryItem/GalleryItem.tsx | 36 ++--- .../components/HeroSection/HeroSection.tsx | 12 +- client/src/components/Image/Image.tsx | 12 +- .../components/Image/Interface/IImageState.ts | 2 +- .../Image/SkeletonImage/SkeletonImage.tsx | 17 +-- .../Navbar/Interface/INavbarProps.tsx | 2 +- .../Navbar/NavBurgerPanel/NavBurgerPanel.tsx | 40 +++--- client/src/components/Navbar/Navbar.tsx | 16 +-- client/src/components/TagCloud/TagCloud.tsx | 20 +-- .../src/components/Utility/ScrollUtility.ts | 2 +- client/src/components/Waves/Waves.tsx | 26 ++-- .../BlogPage/BlogContent/BlogContent.css | 7 +- .../BlogPage/BlogContent/BlogContent.tsx | 2 +- .../MarkdownRenderer/MarkdownRenderer.tsx | 2 +- .../SkeletonBlogContent.tsx | 36 ++--- .../TableOfContents/TableOfContents.css | 4 +- .../TableOfContents/TableOfContents.tsx | 38 +++-- client/src/pages/BlogPage/BlogPage.tsx | 62 +++++---- client/src/pages/ResumePage/ResumePage.tsx | 2 +- client/src/stores/AppContext.tsx | 8 +- server/src/models/local_image_model.rs | 8 +- 34 files changed, 329 insertions(+), 266 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91f4c5fe..7cc72781 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,9 +7,8 @@ on: - deploy jobs: - test-build: + build-client: runs-on: ubuntu-latest - steps: - name: Checkout Repository uses: actions/checkout@v2 @@ -19,9 +18,29 @@ jobs: with: node-version: '21' # Set the Node.js version - - name: Build Project + - name: Build Project Client run: | cd ./client + echo '*' >> .eslintignore npm install typescript@4.x.x --save-dev # Force TypeScript to a compatible version npm install npm run build + + build-server: + runs-on: ubuntu-latest + needs: build-client + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2023-03-01 + override: true + + - name: Build Project Server + run: | + cd ./server + cargo build --verbose diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8b72e0d0..f54fc840 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -7,7 +7,7 @@ on: - deploy jobs: - build-and-deploy: + build-and-deploy-client: runs-on: ubuntu-latest steps: @@ -22,6 +22,7 @@ jobs: - name: Build Project run: | cd ./client + echo '*' >> .eslintignore npm install typescript@4.x.x --save-dev # Force TypeScript to a compatible version npm install npm run build @@ -47,3 +48,40 @@ jobs: port: 22 script: "nginx -s reload" + build-and-deploy-server: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Set up Rust toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2023-03-01 + override: true + + - name: Build Project Server + run: | + cd ./server + cargo build --release --verbose + + - name: Copy files to the server + uses: appleboy/scp-action@master + with: + host: ${{ secrets.REMOTE_HOST }} + username: ${{ secrets.REMOTE_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: 22 + source: "server/target/release/server" + target: "/usr/local/bin/" + strip_components: 3 + + - name: Reload Systemd + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.REMOTE_HOST }} + username: ${{ secrets.REMOTE_USER }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + port: 22 + script: "sudo systemctl daemon-reload && sudo systemctl restart personal_portfolio_client.service" \ No newline at end of file diff --git a/client/.eslintignore b/client/.eslintignore index f59ec20a..e69de29b 100644 --- a/client/.eslintignore +++ b/client/.eslintignore @@ -1 +0,0 @@ -* \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index fc1776e3..e89259cd 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -29,7 +29,8 @@ }, "devDependencies": { "babel-plugin-prismjs": "^2.1.0", - "nodemon": "^2.0.22" + "nodemon": "^2.0.22", + "typescript": "^4.9.5" } }, "node_modules/@adobe/css-tools": { @@ -17164,10 +17165,9 @@ } }, "node_modules/typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "peer": true, + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -30545,10 +30545,9 @@ } }, "typescript": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz", - "integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==", - "peer": true + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" }, "unbox-primitive": { "version": "1.0.2", diff --git a/client/package.json b/client/package.json index 651ee777..2addabad 100644 --- a/client/package.json +++ b/client/package.json @@ -48,6 +48,7 @@ }, "devDependencies": { "babel-plugin-prismjs": "^2.1.0", - "nodemon": "^2.0.22" + "nodemon": "^2.0.22", + "typescript": "^4.9.5" } } diff --git a/client/src/App.css b/client/src/App.css index 546e6a9b..5df11f9f 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -224,6 +224,11 @@ a { .5px .5px 1px rgba(139, 101, 77, 0.2); /* Right shadow */ } +.user-image { + border-radius: 100%; + object-fit: cover +} + /*****************************************************************************/ /* Global Animation Keyframes */ /*****************************************************************************/ diff --git a/client/src/App.tsx b/client/src/App.tsx index 736706ac..4b94f48b 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -15,92 +15,114 @@ import { useNavigate, BrowserRouter, Route, Routes } from 'react-router-dom'; import './App.css'; interface IAppStateInterface { - scrolled: number | null, - scrolling: boolean | null + scrollY: number | null, + scrolling: boolean | null, + deltaScrollCalculation: { + lastRecordedScrollY: number | null, + deltaScrolled: number | null, + timeIntervalCheckMiliseconds: number | null + } } const RedirectToRoot = (props: { link: string }): React.ReactElement<{ link: string }> => { let navigate = useNavigate(); + React.useEffect(() => { navigate(props.link); - }, [navigate]); + }, [navigate, props.link]); return null; } function App() { const [appState, setAppState] = useState({ - scrolled: null, - scrolling: null + scrollY: null, + scrolling: null, + deltaScrollCalculation: { + lastRecordedScrollY: null, + deltaScrolled: null, + timeIntervalCheckMiliseconds: 10 + } }); useEffect(() => { + let scrollTimeout: NodeJS.Timeout = null; + const timeToCheckScrollingHasStoppedMiliseconds = 50; + const handleScroll = () => { - const scrolled = window.scrollY; - setAppState(({ - ...appState, - scrolled: scrolled, + clearTimeout(scrollTimeout); // Clear the timeout to reset the end-of-scroll detection + + setAppState(prevState => ({ + ...prevState, + scrollY: window.scrollY, scrolling: true })); + + // Check if the user has stopped scrolling after a certain time + scrollTimeout = setTimeout(() => { + setAppState(prevState => ({ + ...prevState, + scrolling: false + })); + }, timeToCheckScrollingHasStoppedMiliseconds); }; + // Add the event listener window.addEventListener("scroll", handleScroll); + // Interval for calculating the delta scroll every timeIntervalCheckMiliseconds. + const deltaScrollCalculationInterval: NodeJS.Timeout = setInterval(() => { + setAppState(prevState => ({ + ...prevState, + deltaScrollCalculation: { + ...prevState.deltaScrollCalculation, + lastRecordedScrollY: window.scrollY, + deltaScrolled: window.scrollY - Math.max(0, prevState.deltaScrollCalculation.lastRecordedScrollY) + } + })); + }, appState.deltaScrollCalculation.timeIntervalCheckMiliseconds); + return () => { window.removeEventListener("scroll", handleScroll); + window.clearTimeout(scrollTimeout); + window.clearInterval(deltaScrollCalculationInterval); }; - }, []); - useEffect(() => { - let scrollTimeout: NodeJS.Timeout; + }, [appState.deltaScrollCalculation.timeIntervalCheckMiliseconds]); - const timeToCheckScrollingHasStoppedMiliseconds = 2; - - scrollTimeout = setInterval(() => { - if (appState.scrolling === true) { - setAppState(prevState => ({ - ...prevState, - scrolling: false - })); - } - }, timeToCheckScrollingHasStoppedMiliseconds); - - return () => { - window.clearInterval(scrollTimeout); - }; - }, [appState.scrolled]); + const deltaScrolled = appState.deltaScrollCalculation.deltaScrolled; return (
- - -
- - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - {/* Catch-all route */} - } /> - - {/* Redirections */} - } /> - } /> - } /> - -
-
+ + +
+ + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + {/* Catch-all route */} + } /> + + {/* Redirections */} + } /> + } /> + } /> + +
+
-
+ ); } diff --git a/client/src/components/Card/Card.css b/client/src/components/Card/Card.css index bb55173d..7d52d548 100644 --- a/client/src/components/Card/Card.css +++ b/client/src/components/Card/Card.css @@ -151,8 +151,7 @@ .card-image--author-image { width: 20px; height: 20px; - margin-right: 4px; - border-radius: 100%; + margin-right: 4px } .card-image--author-info { diff --git a/client/src/components/Card/Card.tsx b/client/src/components/Card/Card.tsx index 3c094d8f..a3d5a092 100644 --- a/client/src/components/Card/Card.tsx +++ b/client/src/components/Card/Card.tsx @@ -57,7 +57,7 @@ class Card extends Component { return (
- {Author} + Author {author}
diff --git a/client/src/components/CodingCat/CodingCat.tsx b/client/src/components/CodingCat/CodingCat.tsx index efecbfcd..0628027f 100644 --- a/client/src/components/CodingCat/CodingCat.tsx +++ b/client/src/components/CodingCat/CodingCat.tsx @@ -5,7 +5,7 @@ import { gsap } from 'gsap'; import ICodingCatProps from './Interface/ICodingCatProps'; import ICodingCatState from './Interface/ICodingCatState'; -import ChristmasHat from "./ChristmasHat/ChristmasHat"; +// import ChristmasHat from "./ChristmasHat/ChristmasHat"; class CodingCat extends Component { constructor(props: ICodingCatProps) { diff --git a/client/src/components/ExperienceSection/ExperienceSection.tsx b/client/src/components/ExperienceSection/ExperienceSection.tsx index b638ae4b..1fd32bf0 100644 --- a/client/src/components/ExperienceSection/ExperienceSection.tsx +++ b/client/src/components/ExperienceSection/ExperienceSection.tsx @@ -336,7 +336,8 @@ class ExperienceSection extends Component this.state.timeLineLength; + // const isPastTimelineLength = this.isLocked() && this.props.scrolled - this.getLockPosition() > this.state.timeLineLength; + return isBeforeLockPosition; } diff --git a/client/src/components/ExperienceSection/ExperienceSectionEvent/ExperienceSectionEvent.tsx b/client/src/components/ExperienceSection/ExperienceSectionEvent/ExperienceSectionEvent.tsx index db81e289..a883fa68 100644 --- a/client/src/components/ExperienceSection/ExperienceSectionEvent/ExperienceSectionEvent.tsx +++ b/client/src/components/ExperienceSection/ExperienceSectionEvent/ExperienceSectionEvent.tsx @@ -3,7 +3,13 @@ import { ExperienceSectionItem } from "../Interface/IExperienceSectionState"; import "./ExperienceSectionEvent.css"; import { PiMapPinLineThin } from "react-icons/pi"; -const ExperienceSectionEvent: React.FC<{ item: ExperienceSectionItem, index: number }> = ({ item, index }) => { +interface ExperienceSectionEventProps { + item: ExperienceSectionItem, + index: number, + alt?: string +} + +const ExperienceSectionEvent: React.FC = ({ item, index, alt }) => { const displayIsImage = item.display === "IMAGE"; const defaultDisplay = item.display === "NORMAL" || !item.display; @@ -47,6 +53,7 @@ const ExperienceSectionEvent: React.FC<{ item: ExperienceSectionItem, index: num
{alt}
diff --git a/client/src/components/Gallery/Gallery.tsx b/client/src/components/Gallery/Gallery.tsx index 159626e9..bfaffc56 100644 --- a/client/src/components/Gallery/Gallery.tsx +++ b/client/src/components/Gallery/Gallery.tsx @@ -2,7 +2,6 @@ import React, { Component, createRef } from "react"; import { IGalleryProps } from "./Interface/IGalleryProps"; import IGalleryState from "./Interface/IGalleryState"; import GalleryItem from "./GalleryItem/GalleryItem"; -import TagCloud from "../TagCloud/TagCloud"; import "./Gallery.css"; diff --git a/client/src/components/Gallery/GalleryItem/GalleryItem.tsx b/client/src/components/Gallery/GalleryItem/GalleryItem.tsx index e5a7c0e5..1af1edd5 100644 --- a/client/src/components/Gallery/GalleryItem/GalleryItem.tsx +++ b/client/src/components/Gallery/GalleryItem/GalleryItem.tsx @@ -28,24 +28,24 @@ class GalleryItem extends Component { } } - get GalleryItemTypeSegment(): React.ReactElement | undefined { - switch (this.props.type) { - case "blog": - return ( -
- - Blog -
- ) - break; - case "tool": - return ( -
- - Tool -
- ) + get GalleryItemTypeSegment(): React.ReactElement { + const type = this.props.type; + + if (type === "blog") { + return ( +
+ + Blog +
+ ) } + + return ( +
+ + Tool +
+ ) } render() { @@ -61,7 +61,7 @@ class GalleryItem extends Component { onMouseMove={cardGradientEffect} className="gallery-item gallery-item--no-boundary card"> {this.GalleryItemTypeSegment} - +

{this.props.name}

{ this.props.minuteRead && this.props.dateCreated && diff --git a/client/src/components/HeroSection/HeroSection.tsx b/client/src/components/HeroSection/HeroSection.tsx index 81c16035..b63a8bd7 100644 --- a/client/src/components/HeroSection/HeroSection.tsx +++ b/client/src/components/HeroSection/HeroSection.tsx @@ -1,5 +1,4 @@ import { Component } from 'react'; -import { NavLink } from "react-router-dom"; import { AiOutlineArrowRight } from "react-icons/ai"; import IHeroState from "./Interface/IHeroState"; import IHeroProps from "./Interface/IHeroProps"; @@ -18,9 +17,7 @@ class HeroSection extends Component { super(props); this.state = { backgrounds: [], - introduction: "I am a motivated software engineering grad with a diverse array of skills and experiences, \ -ranging from web and mobile app development to machine learning research. \ -I pride myself on my efficient time and effort management abilities and my aptitude for continuous learning.", + introduction: "I am a motivated software engineering grad with a diverse array of skills and experiences, ranging from web and mobile app development to machine learning research. I pride myself on my efficient time and effort management abilities and my aptitude for continuous learning.", mainContent: { heading: "Hi There 👋. I am Luyang!", itemsToShow: [], @@ -114,7 +111,12 @@ I pride myself on my efficient time and effort management abilities and my aptit
{ heroSectionState.linkToMyOtherSocialMedia.map((item: any, index: number) => ( - + {item.name} )) diff --git a/client/src/components/Image/Image.tsx b/client/src/components/Image/Image.tsx index 5f8ff1a6..48c17670 100644 --- a/client/src/components/Image/Image.tsx +++ b/client/src/components/Image/Image.tsx @@ -14,6 +14,8 @@ class Image extends Component { defaultAuthorImageId: "65194be0f9b642fb30be59af" }; + defaultImageAlt = "You are blind"; + componentDidMount(): void { this.updateImage(); } @@ -26,12 +28,8 @@ class Image extends Component { constructor(props: IImageProps) { super(props); - this.imageRepository = ImageRepository.getInstance(); - - this.state = { - fetchedImageUrl: null - } + this.state = {} } async updateImage() { @@ -53,12 +51,14 @@ class Image extends Component { render() { const { fetchedImageUrl } = this.state; + let { className, alt } = this.props; + alt ??= this.defaultImageAlt; return ( <> { fetchedImageUrl ? - {this.props.alt} + {alt} : } diff --git a/client/src/components/Image/Interface/IImageState.ts b/client/src/components/Image/Interface/IImageState.ts index c40e6648..541ab538 100644 --- a/client/src/components/Image/Interface/IImageState.ts +++ b/client/src/components/Image/Interface/IImageState.ts @@ -1,5 +1,5 @@ interface IImageState { - fetchedImageUrl: string + fetchedImageUrl?: string } export default IImageState; diff --git a/client/src/components/Image/SkeletonImage/SkeletonImage.tsx b/client/src/components/Image/SkeletonImage/SkeletonImage.tsx index c7983e3d..32188ef5 100644 --- a/client/src/components/Image/SkeletonImage/SkeletonImage.tsx +++ b/client/src/components/Image/SkeletonImage/SkeletonImage.tsx @@ -1,18 +1,13 @@ -import React, { Component } from 'react'; +import React from 'react'; import "./SkeletonImage.css"; import ISkeletonImageProps from "./Interface/ISkeletonImageProps"; -class SkeletonImage extends Component { - constructor(props: ISkeletonImageProps) { - super(props); - } +const SkeletonImage: React.FC = (props) => { + const className = `image-skeleton ${props.class}`; - render() { - const className = `image-skeleton ${this.props.class}`; - return ( -
- ); - } + return ( +
+ ); } export default SkeletonImage; diff --git a/client/src/components/Navbar/Interface/INavbarProps.tsx b/client/src/components/Navbar/Interface/INavbarProps.tsx index bb139732..3a0b37dd 100644 --- a/client/src/components/Navbar/Interface/INavbarProps.tsx +++ b/client/src/components/Navbar/Interface/INavbarProps.tsx @@ -3,7 +3,7 @@ interface INavbarProps { current?: string; scrollStatus: { scrolled: number | null; - scrolling: boolean | null; + deltaScrolled: number | null; } } diff --git a/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.tsx b/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.tsx index 3cdd9e8a..3f9d2784 100644 --- a/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.tsx +++ b/client/src/components/Navbar/NavBurgerPanel/NavBurgerPanel.tsx @@ -1,30 +1,24 @@ -import { Component } from 'react'; +import React from 'react'; import { NavLink } from "react-router-dom"; import INavBurgerPanelProps from "./Interface/INavBurgerPanelProps"; import "./NavBurgerPanel.css"; -class NavBurgerPanel extends Component { - constructor(props: INavBurgerPanelProps) { - super(props); - } - - render() { - return ( -
- { - this.props.links.map(link => ( - ["burger-item", isActive ? "burger-item active-link" : null].filter(Boolean).join(" ")} - key={link.name} - > - {link.name} - - )) - } -
- ) - } +const NavBurgerPanel: React.FC = ({ burgerPanel, links }) => { + return ( +
+ { + links.map(link => ( + ["burger-item", isActive ? "burger-item active-link" : null].filter(Boolean).join(" ")} + key={link.name} + > + {link.name} + + )) + } +
+ ) }; diff --git a/client/src/components/Navbar/Navbar.tsx b/client/src/components/Navbar/Navbar.tsx index 01d24acf..b113a480 100644 --- a/client/src/components/Navbar/Navbar.tsx +++ b/client/src/components/Navbar/Navbar.tsx @@ -125,15 +125,14 @@ class NavBar extends Component { listenDeltaScrolled = () => { const { scrollStatus } = this.props; - const { lastScrollY, hideNavBarScrollSensitivity } = this.state; + const { hideNavBarScrollSensitivity } = this.state; + const deltaScrolled = scrollStatus.deltaScrolled ?? 0; - if (scrollStatus.scrolled! - Math.max(0, lastScrollY) >= hideNavBarScrollSensitivity && !this.state.isNavbarHidden) { + if (deltaScrolled >= hideNavBarScrollSensitivity && !this.state.isNavbarHidden) { this.hideNavBar(); - } else if (Math.max(0, lastScrollY - scrollStatus.scrolled!) >= hideNavBarScrollSensitivity && this.state.isNavbarHidden) { + } else if (deltaScrolled <= -hideNavBarScrollSensitivity && this.state.isNavbarHidden) { this.showNavBar(); } - - this.setState({ lastScrollY: scrollStatus.scrolled! }); }; listenContinuousScrolled = () => { @@ -186,13 +185,15 @@ class NavBar extends Component { } componentDidUpdate(prevProps: INavbarProps) { - this.updateScrollingBehavior(prevProps); + if (prevProps !== this.props) + this.updateScrollingBehavior(prevProps); } private updateScrollingBehavior(prevProps: INavbarProps) { const { scrollStatus } = this.props; - if (scrollStatus.scrolling !== prevProps.scrollStatus.scrolling) { + if (scrollStatus.deltaScrolled !== 0) { + console.log(scrollStatus.deltaScrolled) this.listenDeltaScrolled(); } @@ -283,7 +284,6 @@ class NavBar extends Component { render() { const { name, links } = this.state; - const currentlyHoveredNavbarLinkName = this.state.currentlyHoveredNavbarLinkName; return ( <> diff --git a/client/src/components/TagCloud/TagCloud.tsx b/client/src/components/TagCloud/TagCloud.tsx index bee5d042..88be366f 100644 --- a/client/src/components/TagCloud/TagCloud.tsx +++ b/client/src/components/TagCloud/TagCloud.tsx @@ -1,19 +1,13 @@ -import React, { Component } from 'react'; +import React from 'react'; import ITagCloudProps from "./Interface/ITagCloudProps"; import "./TagCloud.css"; -class TagCloud extends Component { - constructor(props: ITagCloudProps) { - super(props); - } - - render() { - return ( -
- { this.props.tags && this.props.tags.map(item => #{ item }) } -
- ); - } +const TagCloud: React.FC = ({ tags }) => { + return ( +
+ { tags && tags.map(item => #{ item }) } +
+ ); } export default TagCloud; diff --git a/client/src/components/Utility/ScrollUtility.ts b/client/src/components/Utility/ScrollUtility.ts index 46f5e9cc..f0d4ee7d 100644 --- a/client/src/components/Utility/ScrollUtility.ts +++ b/client/src/components/Utility/ScrollUtility.ts @@ -82,7 +82,7 @@ const adjustElementPositionAbsoluteY = (element: HTMLElement, y: number = 0): vo // Center the element in the viewport taking the scroll position into account /* element.style.top = `calc(50% + ${scrollTop}px)`; */ - if (y != 0) + if (y !== 0) element.style.top = `calc(${y}px)`; element.style.transform = 'translate(-50%, -50%)'; } diff --git a/client/src/components/Waves/Waves.tsx b/client/src/components/Waves/Waves.tsx index 5670f9d0..fd85b185 100644 --- a/client/src/components/Waves/Waves.tsx +++ b/client/src/components/Waves/Waves.tsx @@ -1,21 +1,15 @@ -import { Component } from "react"; +import React from "react"; import "./Waves.css"; -class Waves extends Component<{}, {}> { - constructor(props: {}) { - super(props) - } - - render() { - return ( -
-
-
-
-
-
- ) - } +const Waves: React.FC<{}> = () => { + return ( +
+
+
+
+
+
+ ) } export default Waves; diff --git a/client/src/pages/BlogPage/BlogContent/BlogContent.css b/client/src/pages/BlogPage/BlogContent/BlogContent.css index c6f22a99..5a108919 100644 --- a/client/src/pages/BlogPage/BlogContent/BlogContent.css +++ b/client/src/pages/BlogPage/BlogContent/BlogContent.css @@ -3,8 +3,8 @@ position: sticky; left: 0; - top: calc(var(--navbar-height) + 15px); - margin-top: 7vh; + margin-top: 20vh; + top: calc(var(--navbar-height) + 2px); backdrop-filter: blur(2px); @@ -79,8 +79,7 @@ .blog-content--author-image { width: 40px; - height: 40px; - border-radius: 100%; + height: 40px } .card-image--author-info>* { diff --git a/client/src/pages/BlogPage/BlogContent/BlogContent.tsx b/client/src/pages/BlogPage/BlogContent/BlogContent.tsx index 89ac1151..2f95b108 100644 --- a/client/src/pages/BlogPage/BlogContent/BlogContent.tsx +++ b/client/src/pages/BlogPage/BlogContent/BlogContent.tsx @@ -118,7 +118,7 @@ class BlogContent extends Component {

{this.state.content.heading}

- +
{author} {displayDateCreated} diff --git a/client/src/pages/BlogPage/BlogContent/MarkdownRenderer/MarkdownRenderer.tsx b/client/src/pages/BlogPage/BlogContent/MarkdownRenderer/MarkdownRenderer.tsx index 3f0f03f4..383bf3d9 100644 --- a/client/src/pages/BlogPage/BlogContent/MarkdownRenderer/MarkdownRenderer.tsx +++ b/client/src/pages/BlogPage/BlogContent/MarkdownRenderer/MarkdownRenderer.tsx @@ -3,13 +3,13 @@ import { marked } from 'marked'; import DOMPurify from 'dompurify'; import Prism from "prismjs"; -// import "prismjs/themes/prism-okaidia.css"; import 'prismjs/components/prism-javascript'; import 'prismjs/components/prism-bash'; import 'prismjs/components/prism-python'; import 'prismjs/components/prism-nginx'; import 'prismjs/components/prism-rust'; import 'prismjs/components/prism-toml'; +import 'prismjs/components/prism-lisp'; import "./MarkdownRenderer.css"; diff --git a/client/src/pages/BlogPage/BlogContent/SkeletonBlogContent/SkeletonBlogContent.tsx b/client/src/pages/BlogPage/BlogContent/SkeletonBlogContent/SkeletonBlogContent.tsx index 98df7578..de1918e7 100644 --- a/client/src/pages/BlogPage/BlogContent/SkeletonBlogContent/SkeletonBlogContent.tsx +++ b/client/src/pages/BlogPage/BlogContent/SkeletonBlogContent/SkeletonBlogContent.tsx @@ -1,26 +1,20 @@ -import React, { Component } from "react"; +import React from "react"; import "./SkeletonBlogContent.css"; -class SkeletonBlogContent extends Component { - constructor(props: {}) { - super(props); - } - - render(): React.ReactNode { - return ( -
-
-
-
-
-
-
-

-
-
-
- ) - } +const SkeletonBlogContent: React.FC = () => { + return ( +
+
+
+
+
+
+
+

+
+
+
+ ); } export default SkeletonBlogContent; diff --git a/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.css b/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.css index 3fe785ce..1cdd267d 100644 --- a/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.css +++ b/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.css @@ -6,9 +6,11 @@ width: 100%; height: 50vw; - padding: 10px; overflow: auto; + margin: 0 10px 10px 10px; + + overflow-x: visible; overflow-y: auto } diff --git a/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.tsx b/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.tsx index fefd9194..ad845830 100644 --- a/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.tsx +++ b/client/src/pages/BlogPage/BlogContent/TableOfContents/TableOfContents.tsx @@ -1,14 +1,12 @@ -import React, { Component } from "react"; +import React from "react"; import ItableOfContentsProps from "./Interface/ItableOfContentsProps" -import ITableOfContentsState from "./Interface/ITableOfContentsState" +// import ITableOfContentsState from "./Interface/ITableOfContentsState" import "./TableOfContents.css"; -class TableOfContents extends Component { - constructor(props: ItableOfContentsProps) { - super(props); - } +const TableOfContents: React.FC = (props) => { + // const [tableOfContentsState, settableOfContentsState] = useState({}); - getIdFromHeading(str: string): number { + const getIdFromHeading = (str: string): number => { let hash = 0; for (let i = 0; i < str.length; i++) { @@ -20,7 +18,7 @@ class TableOfContents extends Component, id: string) => { + const handleClick = (event: React.MouseEvent, id: string) => { const targetElement = document.getElementById(id); @@ -31,38 +29,36 @@ class TableOfContents extends Component { const getTextColor = (level: number): string => { const lightness = level * 20; return `hsl(0, 0%, ${lightness}%)`; }; - const subheadings = this.props.headings?.filter(({ level }) => level !== 0); + const subheadings = props.headings?.filter(({ level }) => level !== 0); return subheadings?.map(({ title, level }, idx: number) => { const indentation = `${Math.max((level - 1), 0) * 20}px`; const marginBottom = `${(22 - 4.5 * level) / 2}px`; const color = getTextColor(level); - const id = this.getIdFromHeading(title); + const id = getIdFromHeading(title); return ( -
this.handleClick(e, id.toString())}> +
handleClick(e, id.toString())}> {title}
); }); } - render() { - const className = ["table-of-contents", this.props.className].join(" "); + const className = ["table-of-contents", props.className].join(" "); - return ( -
-

Table of Contents:

- {this.renderTableOfContents()} -
- ) - } + return ( +
+

Table of Contents:

+ {renderTableOfContents()} +
+ ) } export default TableOfContents; diff --git a/client/src/pages/BlogPage/BlogPage.tsx b/client/src/pages/BlogPage/BlogPage.tsx index f89b7ca1..cd8e5db1 100644 --- a/client/src/pages/BlogPage/BlogPage.tsx +++ b/client/src/pages/BlogPage/BlogPage.tsx @@ -31,7 +31,7 @@ class BlogPage extends Component { } componentDidUpdate(prevProps: Readonly, prevState: Readonly, snapshot?: any): void { - if (prevState.content != this.state.content) { + if (prevState.content !== this.state.content) { this.updateAllUniqueTags(); this.updateTopPickedPosts(); } @@ -87,10 +87,14 @@ class BlogPage extends Component { return array1.every(item => array2.includes(item)); } + // TODO: Backend send user image id and card gets it + const authorImage = "http://llcode.tech/api/image/65817ae96c73ceb16ba51731"; + return this.sortPostsByDate(this.state.content).filter(({ tags }) => isSubset(selectedTags, tags) || !selectedTags).map((content, _) => ( { renderSelectedTags = (): React.ReactNode | null => { const baseUrlLink = "/digital_chronicles/blogs"; - - return [...this.state.allTags].map((tagName) => { - const tagAlreadySelected = this.currentSelectedTags.includes(tagName); - - if (tagAlreadySelected) { + return [...this.state.allTags] + .filter(tagName => { + const tagAlreadySelected = this.currentSelectedTags.includes(tagName); + return tagAlreadySelected; + }) + .map((tagName) => { let selectedTagsString: string[] = []; selectedTagsString = this.currentSelectedTags.filter(tag => tag !== tagName); - const tagClassName = ['blog__tag', 'noselect', tagAlreadySelected ? 'blog__tag--selected' : ''].join(" "); - + const tagClassName = ['blog__tag', 'noselect', 'blog__tag--selected'].join(" "); + const to = `${baseUrlLink}?tag=${encodeURIComponent(selectedTagsString.join(","))}`; return ( - #{tagName} + #{tagName} ); - } - }) + }) }; renderUnSelectedTags = (): React.ReactNode | null => { const baseUrlLink = "/digital_chronicles/blogs"; - - return [...this.state.allTags].map((tagName) => { - const tagAlreadySelected = this.currentSelectedTags.includes(tagName); - - if (!tagAlreadySelected) { + return [...this.state.allTags] + .filter(tagName => { + const tagAlreadySelected = this.currentSelectedTags.includes(tagName); + return !tagAlreadySelected; + }) + .map((tagName) => { let selectedTagsString: string[] = []; selectedTagsString = this.currentSelectedTags.concat(tagName); - const tagClassName = ['blog__tag', 'noselect', tagAlreadySelected ? 'blog__tag--selected' : ''].join(" "); - + const tagClassName = ['blog__tag', 'noselect'].join(" "); + const to = `${baseUrlLink}?tag=${encodeURIComponent(selectedTagsString.join(","))}`; return ( - #{tagName} + #{tagName} ); - } - }) + }); }; @@ -176,14 +180,14 @@ class BlogPage extends Component { return (
- {this.currentSelectedTags.length > 0 && (
{this.renderSelectedTags()}
)} -
{this.renderUnSelectedTags()}
-
- 2023 -
-
- {this.renderPostsSortedByDateDescending()} -
+ {this.currentSelectedTags.length > 0 && (
{this.renderSelectedTags()}
)} +
{this.renderUnSelectedTags()}
+
+ 2023 +
+
+ {this.renderPostsSortedByDateDescending()} +
{this.renderTopPickedBlogPost()} diff --git a/client/src/pages/ResumePage/ResumePage.tsx b/client/src/pages/ResumePage/ResumePage.tsx index c5f37117..bc07c609 100644 --- a/client/src/pages/ResumePage/ResumePage.tsx +++ b/client/src/pages/ResumePage/ResumePage.tsx @@ -19,7 +19,7 @@ class ResumePage extends Component { render(): React.ReactElement { return (
-
) diff --git a/client/src/stores/AppContext.tsx b/client/src/stores/AppContext.tsx index b2bd99df..da0f7960 100644 --- a/client/src/stores/AppContext.tsx +++ b/client/src/stores/AppContext.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState, useRef, useCallback } from "react"; import UserRepository from "../repositories/UserRepository"; import IAppContextProvider from "./Interface/IAppContextProvider"; import IAppContextProps from "./Interface/IAppContextProps"; @@ -21,7 +21,7 @@ export const AppContextProvider: React.FC = ({ children }) => return 1 }; - const processQueue = () => { + const processQueue = useCallback(() => { if (queueRef.current.length === 0) return; const element = queueRef.current.shift(); @@ -30,7 +30,7 @@ export const AppContextProvider: React.FC = ({ children }) => } setTimeout(processQueue, 200); - }; + }, []); // Empty dependency array to ensure it doesn't change const fadeInElement = (element: Element) => { (element as HTMLElement).style.opacity = '1'; @@ -39,7 +39,7 @@ export const AppContextProvider: React.FC = ({ children }) => useEffect(() => { processQueue(); - }, [queueRef.current]); + }, [processQueue]); useEffect(() => { diff --git a/server/src/models/local_image_model.rs b/server/src/models/local_image_model.rs index 1404d023..f1fd6271 100644 --- a/server/src/models/local_image_model.rs +++ b/server/src/models/local_image_model.rs @@ -7,9 +7,9 @@ pub struct LocalImage { #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] pub id: Option, pub image_type: String, - pub year: i32, - pub month: i32, - pub date_created: String, + pub year: Option, + pub month: Option, + pub date_created: Option, pub file_name: String, pub source: Option -} +} \ No newline at end of file