This commit is contained in:
Camerin 2022-01-07 18:15:13 -05:00
commit e7159f2bb2
16 changed files with 338 additions and 88 deletions

File diff suppressed because one or more lines are too long

View File

@ -41,4 +41,16 @@
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
</body> </body>
<!-- Hotjar Tracking Code for https://www.camscode.com -->
<script>
(function(h,o,t,j,a,r){
h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
h._hjSettings={hjid:2739788,hjsv:6};
a=o.getElementsByTagName('head')[0];
r=o.createElement('script');r.async=1;
r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
a.appendChild(r);
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
</script>
</html> </html>

View File

@ -4,6 +4,7 @@ import "./css/App.css";
import Navigation from './Navigation'; import Navigation from './Navigation';
import Github from './Github'; import Github from './Github';
import Articles from './Articles'; import Articles from './Articles';
import ArticleEditor from './ArticleEditor';
import About from './About'; import About from './About';
import Intro from './Intro'; import Intro from './Intro';
import Bai from './Bai'; import Bai from './Bai';
@ -20,6 +21,7 @@ const App = (props) => {
<Route path="/about" component={About} /> <Route path="/about" component={About} />
<Route path="/articles" component={Articles} /> <Route path="/articles" component={Articles} />
<Route path="/bai" component={Bai} /> <Route path="/bai" component={Bai} />
<Route path="/articleEditor" component={ArticleEditor} />
</Switch> </Switch>
</div> </div>
</div> </div>

View File

@ -1,36 +1,147 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import Theater from './subcomponents/Theater'; import Theater from './subcomponents/Theater';
import './css/Articles.css'; import './css/Articles.css';
const Article = ({article}) => { const Article = ({article}) => {
const [show, setShow] = useState("");
const [currArticle, setCurrArticle] = useState("");
useEffect(()=>{
if (currArticle === ""){ // If no articles have been opened
if (show === "") {
// Set the current article and initiate the open animation
setCurrArticle(article);
setShow("open");
// Once the animation runs, open the entire article
setTimeout(() =>{
setShow("show");
}, 1024)
}
} else if (currArticle !== article) { // If we've changed articles
// Change currArticle to the actual current article and initiate close animation
setCurrArticle(article);
setShow("close");
// Step through the close animation, open animation, and fully opening the article
setTimeout(()=>{
setShow("");
setTimeout(()=>{
setShow("open");
setTimeout(() =>{
setShow("show");
}, 1024);
}, 1024);
}, 24);
}
}, [currArticle, article, show]);
let linkProcessor = (text) => {
/**
* Given some text, processes and returns jsx with any link represented as an anchor
*/
let output = [""]; // Stores all text in a list
let loc = 0; // Stores the current location in output that we're working with
let tmp;
let i = 0;
console.log(text);
while (i < text.length) { // Iterate through the entire text string
if (text.slice(i, i+4) === "http"){ // slice from i to 4 chars plus and check for http
let x = i; // store i in x so the location is not modified
for (let y = i; ![" ", "\n"].includes(text[y]) && y < text.length; y++){
i=y;
} // iterate until we find the end of the link denoted by a space
if (output[loc] !== "") { // if the current output location isn't empty, increment loc
loc++;
}
tmp = text.slice(x, i+1);
// Put anchor for link into output list
output[loc] = <a key={i} href={tmp}>{tmp}</a>;
output[++loc] = ""; // Create new location in output with empty string
} else {
// Append current char to output
output[loc] += text[i];
}
i++;
}
// Return the output
return output;
};
let newLineProcessor = (text) => {
/**
* Given some text, processes and returns jsx with line breaks
*/
let tmp;
let output = [""]; // Stores all text in a list
let loc = 0; // Stores the current location in output that we're working with
for (let i = 0; i < text.length; i++) { // Iterate through the entire text string
if (!React.isValidElement(text[i])) {
tmp = [""];
loc = 0;
for (let j = 0; j < text[i].length; j++){
if (text[i].slice(j, j+1) === "\n"){ // slice from i to 4 chars plus and check for http
if (tmp[loc] !== "") { // if the current output location isn't empty, increment loc
loc++;
}
tmp[loc] = <br key={i+j}/>;
tmp[++loc] = "";
} else {
// Append current char to output
tmp[loc] += text[i][j];
}
}
tmp[++loc] = "";
} else {
tmp = text[i]
}
output.push(tmp)
}
// Return the output
return output;
};
let articleFormatter = (text) => { let articleFormatter = (text) => {
let output = []; let output = [""]; // Used to store separate formatted text
let type = []; let type = []; // Parallel to output list to signify format type
let ind = 0; let ind = 0; // Denote index of output
let tick=false; let tick=false; // used to check if we're currently in formatted text.
let delimiters = ['', '`', '*']; let delimiters = ['', '`', '*', '~']; // Denotes characters used to format
let tmp;
for (let i = 0; i < text.length; i++) { // Iterate through input for (let i = 0; i < text.length; i++) { // Iterate through input
console.log(text[i]); if (delimiters.indexOf(text[i]) >= 0) { // Detect Code Delimiter
if (delimiters.indexOf(text[i]) !== -1) { // Detect Code Delimiter console.log(text[i])
if (tick) { // Close the code section if (tick) { // Close the code section
console.log(1);
output[++ind] = "" output[++ind] = ""
tick = false; tick = false;
tmp = "";
} else { // Start a new code section } else { // Start a new code section
console.log(2);
type.push(delimiters.indexOf(text[i])); type.push(delimiters.indexOf(text[i]));
if (!output[ind]) { if (output.length < ind) {
output[ind] = ""; output[ind] = "";
} else if (output.length < type.length) { } else if (output.length < type.length) {
output[++ind] = ""; output[++ind] = "";
} }
tmp = "";
tick = true; tick = true;
} }
@ -40,36 +151,41 @@ const Article = ({article}) => {
if (output.length > type.length) { // If this is the beggining of a default text type if (output.length > type.length) { // If this is the beggining of a default text type
type.push(0); type.push(0);
output[ind] = "" //output[ind] = text[i]
} else if (output.length < type.length) {
output[++ind] = ""
} }
console.log(3); tmp = text[i]
output[ind] += text[i]
} }
}
console.log(output); output[ind] += tmp
console.log(type); }
return [...output.keys()].map((i)=>{ // Format text and return as jsx return [...output.keys()].map((i)=>{ // Format text and return as jsx
let text = linkProcessor(output[i]); // Process links
text = newLineProcessor(text);
if (type[i] === 0){ // Return default text type if (type[i] === 0){ // Return default text type
return <div key={i}>{output[i]}</div>; return <div key={i}>{text}</div>;
} else if(type[i] === 1) { // Return Code text type } else if(type[i] === 1) { // Return Code text type
return <div className="code" key={i}>{output[i]}</div>; return <div className="code" key={i}>{text}</div>;
} else if (type[i] === 2) { } else if (type[i] === 2) {
return <div className="h1" key={i}>{output[i]}</div>; return <div className="h1" key={i}>{text}</div>;
}else { } else if (type[i] === 3) {
return <li className="li" key={i}>{text}</li>;
} else {
return <div key={i}></div>; return <div key={i}></div>;
} }
}); });
@ -77,7 +193,7 @@ const Article = ({article}) => {
}; };
return ( return (
<div className="article"> <div className={"article " + show}>
<Theater <Theater
title={article.title} title={article.title}
description={article.desc} description={article.desc}

View File

@ -0,0 +1,34 @@
import React, { useState } from 'react';
import Article from './Article';
import "./css/ArticleEditor.css";
const ArticleEditor = (props) => {
const [content, setContent] = useState("Hello World");
let article = {
"id":"0",
"title": "Article Editor",
"desc":"This is a place to edit articles",
"contents": content
};
let copyToClipboard = () => {
navigator.clipboard.writeText(content).then(function() {
console.log('Async: Copying to clipboard was successful!');
}, function(err) {
console.error('Async: Could not copy text: ', err);
});
};
return (
<div className="ArticleEditor">
<div id="toolbar" className="toolbar">
<div className="btn" onClick={copyToClipboard()}>Copy to Clipboard</div>
</div>
<Article article={article}/>
<textarea onInput={e=>{setContent(e.target.value)}}></textarea>
</div>
);
};
export default ArticleEditor;

View File

@ -51,6 +51,8 @@ class Github extends React.Component {
if (this.props.repos.length > 0) { if (this.props.repos.length > 0) {
// Render each repo // Render each repo
const render = this.props.repos.map((repo) =>{ const render = this.props.repos.map((repo) =>{
let updated = (new Date (repo.updated_at)).toLocaleString();
let created = (new Date (repo.created_at)).toLocaleString();
return ( return (
<div className="repo" key={repo.id}> <div className="repo" key={repo.id}>
@ -74,6 +76,11 @@ class Github extends React.Component {
this.renderLanguages(repo.name) // Render each language for the repo this.renderLanguages(repo.name) // Render each language for the repo
} }
</div> </div>
<div className="times">
Last Updated: {updated}
<br/>
Created: {created}
</div>
</div> </div>
); );

View File

@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import './css/Intro.css'; import './css/Intro.css';
import {hideNavigation, showNavigation} from '../actions';
import Topic from './subcomponents/Topic'; import Topic from './subcomponents/Topic';
import { ChevronDoubleUp, ChevronDoubleDown } from 'react-bootstrap-icons'; import { ChevronDoubleUp, ChevronDoubleDown } from 'react-bootstrap-icons';
@ -108,8 +107,6 @@ class Intro extends React.Component {
} else if (input === "down" && this.state.nextLoc >= this.topics.length) { } else if (input === "down" && this.state.nextLoc >= this.topics.length) {
// Set something to happen when you reach the end // Set something to happen when you reach the end
this.props.showNavigation();
localStorage.setItem('intro', true);
} }
}; };
@ -123,11 +120,6 @@ class Intro extends React.Component {
document.title = "HomePage"; // Set document title document.title = "HomePage"; // Set document title
// Hide the navigation
if (!localStorage.getItem('intro')) {
this.props.hideNavigation();
}
} }
// Topics to display in intro // Topics to display in intro
@ -137,7 +129,7 @@ class Intro extends React.Component {
<div> <div>
This website helps you access the projects that I've worked on. You can navigate This website helps you access the projects that I've worked on. You can navigate
at the top to different locations in the site. Within you can find information about me, at the top to different locations in the site. Within you can find information about me,
my github repositories, and some youtube videos I've posted. This website is coded with my github repositories, and articles I've posted. This website is coded with
React/Redux and hosted over Vercel. You can email me at <a href="mailto:cam@camscode.com">cam@camscode.com</a>. React/Redux and hosted over Vercel. You can email me at <a href="mailto:cam@camscode.com">cam@camscode.com</a>.
</div> </div>
</Topic>, </Topic>,
@ -178,4 +170,4 @@ const mapStateToProps = (state) => {
} }
export default connect(mapStateToProps, {hideNavigation, showNavigation})(Intro); export default connect(mapStateToProps, {})(Intro);

View File

@ -7,39 +7,36 @@ import { HouseDoor, FileEarmarkPerson, Github, Envelope, Book } from 'react-boot
import ContactModal from './subcomponents/ContactModal'; import ContactModal from './subcomponents/ContactModal';
const Navigation = (props) => { const Navigation = (props) => {
if (props.enable){ return (
return ( <div className="Navigation">
<div className="Navigation"> <Link to="/">
<Link to="/"> Home
Home <HouseDoor />
<HouseDoor /> </Link>
</Link> <Link to="/github">
<Link to="/github"> Github
Github <Github />
<Github /> </Link>
</Link> <Link to="/articles">
<Link to="/articles"> Articles
Articles <Book />
<Book /> </Link>
</Link> <Link to="/about">
<Link to="/about"> About
About <FileEarmarkPerson />
<FileEarmarkPerson /> </Link>
</Link> <button className="end" onClick={()=>props.toggleContactModal()}>
<button className="end" onClick={()=>props.toggleContactModal()}> Contact Me
Contact Me <Envelope />
<Envelope /> </button>
</button> <ContactModal show={props.modal}/>
<ContactModal show={props.modal}/> </div>
</div> );
);
}
return <div></div>;
} }
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
return {modal: state.contactModal.contactModal, enable: state.navigation.enable}; return {modal: state.contactModal.contactModal};
} }
export default connect(mapStateToProps, {toggleContactModal})(Navigation); export default connect(mapStateToProps, {toggleContactModal})(Navigation);

View File

@ -31,7 +31,7 @@
display:flex; display:flex;
flex-direction:row; flex-direction:row;
align-content:center; align-content:center;
justify-content:flex-start; justify-content:space-evenly;
align-items:center; align-items:center;
flex-wrap: wrap; flex-wrap: wrap;
width:85vw; width:85vw;
@ -44,4 +44,10 @@
.About .links * { .About .links * {
margin: 10px; margin: 10px;
}
@media only screen and (max-width: 600px) {
.About .links .link img {
width: 25vw;
}
} }

View File

@ -0,0 +1,42 @@
.ArticleEditor textarea {
width:100%;
height: 35vh;
border-style: solid;
border-color: lightgray;
border-radius: 2px;
border-width: 2px;
padding: 0.4em 0.4em 0.4em 0;
text-align: left;
}
.ArticleEditor .toolbar {
width: 100%;
height: 56px;
display: flex;
}
.ArticleEditor .toolbar .btn {
color: white;
background-color: darkgreen;
text-align: center;
align-items:center;
justify-content: center;
display: flex;
padding-left: 3px;
padding-right: 3px;
}
.ArticleEditor .toolbar .btn:hover{
background-color: #005400
}
.ArticleEditor .article .open {
max-height:100vh;
}
.ArticleEditor .article .Close {
max-height:100vh;
}
.ArticleEditor .article {
max-height:100vh;
}

View File

@ -3,24 +3,51 @@
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height:0;
overflow: hidden;
transition: max-height 1s ease;
}
.article.open {
max-height:100vh;
transition: max-height 1s ease;
}
.article.close {
max-height:100vh;
}
.article.show {
max-height:none;
} }
.article .content { .article .content {
width: 80vw; width: 80vw;
text-align:left; text-align:left;
line-height: 1.5;
} }
.article .code { .article .code {
padding: 10px; padding: 10px;
background-color: #3A3B3B; background-color: #3A3B3B;
color: gray; color: lightgray;
border-radius: 5px; border-radius: 5px;
margin: 10px; margin: 20px;
} }
.article .h1 { .article .h1 {
font-size: 24px; font-size: 24px;
font-weight:bolder; font-weight:bolder;
margin-top: 10px; margin-top: 20px;
margin-bottom: 10px; margin-bottom: 10px;
}
.article .li {
margin-left: 10px;
}
.article a {
color: #ACACFF;
} }

View File

@ -61,7 +61,7 @@
.repo .description { .repo .description {
flex-grow: 1; flex-grow: 1;
padding:10px; padding:5px;
} }
.repo .languages { .repo .languages {
@ -82,6 +82,14 @@
color: #DAD4DF; color: #DAD4DF;
} }
.repo .times {
padding: 5px;
margin: 2px;
margin-left: 5px;
width: 100%;
text-align: start;
}
.content { .content {
margin: 25px; margin: 25px;
text-align: center; text-align: center;
@ -95,4 +103,11 @@
border-radius: 10rem; border-radius: 10rem;
height: 10rem; height: 10rem;
width: 10rem; width: 10rem;
}
@media only screen and (max-width: 600px) {
.repo .content {
flex-direction: column;
}
} }

View File

@ -15,7 +15,7 @@
.theater-bg { .theater-bg {
height: 100%; height: inherit;
width:100%; width:100%;
background-size: cover; background-size: cover;
background-image: url('../../img/background.webp'); background-image: url('../../img/background.webp');

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom';
import '../css/Listing.css'; import '../css/Listing.css';
const Listing = (props) => { const Listing = (props) => {
@ -11,12 +12,12 @@ const Listing = (props) => {
*/ */
return ( return (
<div className="listing"> <div className="listing">
<a href={props.link}> <Link to={props.link}>
<div className="title">{props.title}</div> <div className="title">{props.title}</div>
<div className="content"> <div className="content">
{props.children} {props.children}
</div> </div>
</a> </Link>
</div> </div>
); );
}; };

View File

@ -3,7 +3,6 @@ import { combineReducers } from "redux";
import githubReducer from './githubReducer'; import githubReducer from './githubReducer';
import contactModalReducer from "./contactModalReducer"; import contactModalReducer from "./contactModalReducer";
import introReducer from "./introReducer"; import introReducer from "./introReducer";
import navigationReducer from "./navigationReducer";
import articlesReducer from "./articlesReducer"; import articlesReducer from "./articlesReducer";
import modelReducer from "./modelReducer"; import modelReducer from "./modelReducer";
@ -11,7 +10,6 @@ export default combineReducers({
github: githubReducer, github: githubReducer,
contactModal: contactModalReducer, contactModal: contactModalReducer,
intro: introReducer, intro: introReducer,
navigation: navigationReducer,
articles: articlesReducer, articles: articlesReducer,
model: modelReducer, model: modelReducer,
}); });

View File

@ -1,11 +0,0 @@
let navigationReducer = (state={enable: true}, action) => {
switch(action.type) {
case 'SET_NAVIGATION':
return { ...state, enable: action.payload };
default:
return state;
}
};
export default navigationReducer;