Merge branch 'master' of https://github.com/RaspberryProgramming/portfolio
This commit is contained in:
commit
523f19a927
File diff suppressed because one or more lines are too long
|
|
@ -41,4 +41,16 @@
|
|||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import "./css/App.css";
|
|||
import Navigation from './Navigation';
|
||||
import Github from './Github';
|
||||
import Articles from './Articles';
|
||||
import ArticleEditor from './ArticleEditor';
|
||||
import About from './About';
|
||||
import Intro from './Intro';
|
||||
import Bai from './Bai';
|
||||
|
|
@ -20,6 +21,7 @@ const App = (props) => {
|
|||
<Route path="/about" component={About} />
|
||||
<Route path="/articles" component={Articles} />
|
||||
<Route path="/bai" component={Bai} />
|
||||
<Route path="/articleEditor" component={ArticleEditor} />
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,36 +1,147 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Theater from './subcomponents/Theater';
|
||||
import './css/Articles.css';
|
||||
|
||||
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 output = [];
|
||||
let type = [];
|
||||
let ind = 0;
|
||||
let tick=false;
|
||||
let delimiters = ['', '`', '*'];
|
||||
let output = [""]; // Used to store separate formatted text
|
||||
let type = []; // Parallel to output list to signify format type
|
||||
let ind = 0; // Denote index of output
|
||||
let tick=false; // used to check if we're currently in formatted text.
|
||||
let delimiters = ['', '`', '*', '~']; // Denotes characters used to format
|
||||
let tmp;
|
||||
|
||||
for (let i = 0; i < text.length; i++) { // Iterate through input
|
||||
console.log(text[i]);
|
||||
if (delimiters.indexOf(text[i]) !== -1) { // Detect Code Delimiter
|
||||
|
||||
if (delimiters.indexOf(text[i]) >= 0) { // Detect Code Delimiter
|
||||
console.log(text[i])
|
||||
if (tick) { // Close the code section
|
||||
console.log(1);
|
||||
output[++ind] = ""
|
||||
tick = false;
|
||||
tmp = "";
|
||||
|
||||
} else { // Start a new code section
|
||||
console.log(2);
|
||||
} else { // Start a new code section
|
||||
type.push(delimiters.indexOf(text[i]));
|
||||
|
||||
if (!output[ind]) {
|
||||
if (output.length < ind) {
|
||||
|
||||
output[ind] = "";
|
||||
|
||||
} else if (output.length < type.length) {
|
||||
output[++ind] = "";
|
||||
}
|
||||
|
||||
tmp = "";
|
||||
tick = true;
|
||||
|
||||
}
|
||||
|
|
@ -40,36 +151,41 @@ const Article = ({article}) => {
|
|||
if (output.length > type.length) { // If this is the beggining of a default text type
|
||||
|
||||
type.push(0);
|
||||
output[ind] = ""
|
||||
//output[ind] = text[i]
|
||||
|
||||
} else if (output.length < type.length) {
|
||||
output[++ind] = ""
|
||||
}
|
||||
|
||||
console.log(3);
|
||||
|
||||
|
||||
output[ind] += text[i]
|
||||
tmp = text[i]
|
||||
}
|
||||
}
|
||||
|
||||
console.log(output);
|
||||
console.log(type);
|
||||
output[ind] += tmp
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
return <div key={i}>{output[i]}</div>;
|
||||
return <div key={i}>{text}</div>;
|
||||
|
||||
} 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) {
|
||||
|
||||
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>;
|
||||
}
|
||||
});
|
||||
|
|
@ -77,7 +193,7 @@ const Article = ({article}) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="article">
|
||||
<div className={"article " + show}>
|
||||
<Theater
|
||||
title={article.title}
|
||||
description={article.desc}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -51,6 +51,8 @@ class Github extends React.Component {
|
|||
if (this.props.repos.length > 0) {
|
||||
// Render each 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 (
|
||||
<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
|
||||
}
|
||||
</div>
|
||||
<div className="times">
|
||||
Last Updated: {updated}
|
||||
<br/>
|
||||
Created: {created}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import './css/Intro.css';
|
||||
import {hideNavigation, showNavigation} from '../actions';
|
||||
import Topic from './subcomponents/Topic';
|
||||
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) {
|
||||
// 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
|
||||
|
||||
// Hide the navigation
|
||||
if (!localStorage.getItem('intro')) {
|
||||
this.props.hideNavigation();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Topics to display in intro
|
||||
|
|
@ -137,7 +129,7 @@ class Intro extends React.Component {
|
|||
<div>
|
||||
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,
|
||||
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>.
|
||||
</div>
|
||||
</Topic>,
|
||||
|
|
@ -178,4 +170,4 @@ const mapStateToProps = (state) => {
|
|||
}
|
||||
|
||||
|
||||
export default connect(mapStateToProps, {hideNavigation, showNavigation})(Intro);
|
||||
export default connect(mapStateToProps, {})(Intro);
|
||||
|
|
|
|||
|
|
@ -7,39 +7,36 @@ import { HouseDoor, FileEarmarkPerson, Github, Envelope, Book } from 'react-boot
|
|||
import ContactModal from './subcomponents/ContactModal';
|
||||
|
||||
const Navigation = (props) => {
|
||||
if (props.enable){
|
||||
return (
|
||||
<div className="Navigation">
|
||||
<Link to="/">
|
||||
Home
|
||||
<HouseDoor />
|
||||
</Link>
|
||||
<Link to="/github">
|
||||
Github
|
||||
<Github />
|
||||
</Link>
|
||||
<Link to="/articles">
|
||||
Articles
|
||||
<Book />
|
||||
</Link>
|
||||
<Link to="/about">
|
||||
About
|
||||
<FileEarmarkPerson />
|
||||
</Link>
|
||||
<button className="end" onClick={()=>props.toggleContactModal()}>
|
||||
Contact Me
|
||||
<Envelope />
|
||||
</button>
|
||||
<ContactModal show={props.modal}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="Navigation">
|
||||
<Link to="/">
|
||||
Home
|
||||
<HouseDoor />
|
||||
</Link>
|
||||
<Link to="/github">
|
||||
Github
|
||||
<Github />
|
||||
</Link>
|
||||
<Link to="/articles">
|
||||
Articles
|
||||
<Book />
|
||||
</Link>
|
||||
<Link to="/about">
|
||||
About
|
||||
<FileEarmarkPerson />
|
||||
</Link>
|
||||
<button className="end" onClick={()=>props.toggleContactModal()}>
|
||||
Contact Me
|
||||
<Envelope />
|
||||
</button>
|
||||
<ContactModal show={props.modal}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
display:flex;
|
||||
flex-direction:row;
|
||||
align-content:center;
|
||||
justify-content:flex-start;
|
||||
justify-content:space-evenly;
|
||||
align-items:center;
|
||||
flex-wrap: wrap;
|
||||
width:85vw;
|
||||
|
|
@ -44,4 +44,10 @@
|
|||
|
||||
.About .links * {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.About .links .link img {
|
||||
width: 25vw;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -3,24 +3,51 @@
|
|||
align-items: center;
|
||||
display: flex;
|
||||
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 {
|
||||
width: 80vw;
|
||||
text-align:left;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.article .code {
|
||||
padding: 10px;
|
||||
background-color: #3A3B3B;
|
||||
color: gray;
|
||||
color: lightgray;
|
||||
border-radius: 5px;
|
||||
margin: 10px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.article .h1 {
|
||||
font-size: 24px;
|
||||
font-weight:bolder;
|
||||
margin-top: 10px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.article .li {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.article a {
|
||||
color: #ACACFF;
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
|
||||
.repo .description {
|
||||
flex-grow: 1;
|
||||
padding:10px;
|
||||
padding:5px;
|
||||
}
|
||||
|
||||
.repo .languages {
|
||||
|
|
@ -82,6 +82,14 @@
|
|||
color: #DAD4DF;
|
||||
}
|
||||
|
||||
.repo .times {
|
||||
padding: 5px;
|
||||
margin: 2px;
|
||||
margin-left: 5px;
|
||||
width: 100%;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 25px;
|
||||
text-align: center;
|
||||
|
|
@ -95,4 +103,11 @@
|
|||
border-radius: 10rem;
|
||||
height: 10rem;
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
|
||||
.repo .content {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
.theater-bg {
|
||||
|
||||
height: 100%;
|
||||
height: inherit;
|
||||
width:100%;
|
||||
background-size: cover;
|
||||
background-image: url('../../img/background.webp');
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import '../css/Listing.css';
|
||||
|
||||
const Listing = (props) => {
|
||||
|
|
@ -11,12 +12,12 @@ const Listing = (props) => {
|
|||
*/
|
||||
return (
|
||||
<div className="listing">
|
||||
<a href={props.link}>
|
||||
<Link to={props.link}>
|
||||
<div className="title">{props.title}</div>
|
||||
<div className="content">
|
||||
{props.children}
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { combineReducers } from "redux";
|
|||
import githubReducer from './githubReducer';
|
||||
import contactModalReducer from "./contactModalReducer";
|
||||
import introReducer from "./introReducer";
|
||||
import navigationReducer from "./navigationReducer";
|
||||
import articlesReducer from "./articlesReducer";
|
||||
import modelReducer from "./modelReducer";
|
||||
|
||||
|
|
@ -11,7 +10,6 @@ export default combineReducers({
|
|||
github: githubReducer,
|
||||
contactModal: contactModalReducer,
|
||||
intro: introReducer,
|
||||
navigation: navigationReducer,
|
||||
articles: articlesReducer,
|
||||
model: modelReducer,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
Loading…
Reference in New Issue