Add webpack support

This commit is contained in:
Schmelczer András 2019-12-19 22:14:45 +01:00
parent fb3ef7475c
commit eb2075aec5
33 changed files with 988 additions and 765 deletions

4
.gitignore vendored
View file

@ -1 +1,3 @@
# Created by .ignore support plugin (hsz.mobi)
node_modules
dist
package-lock.json

45
.idea/watcherTasks.xml generated Normal file
View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions" suppressed-tasks="SCSS">
<TaskOptions isEnabled="true">
<option name="arguments" value="--write $FilePathRelativeToProjectRoot$" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="js" />
<option name="immediateSync" value="false" />
<option name="name" value="Prettier" />
<option name="output" value="$FilePathRelativeToProjectRoot$" />
<option name="outputFilters">
<array />
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="$ProjectFileDir$/node_modules/.bin/prettier" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="false" />
<option name="workingDir" value="$ProjectFileDir$" />
<envs />
</TaskOptions>
<TaskOptions isEnabled="true">
<option name="arguments" value="--write $FilePathRelativeToProjectRoot$" />
<option name="checkSyntaxErrors" value="true" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="xhtml" />
<option name="immediateSync" value="false" />
<option name="name" value="Prettier" />
<option name="output" value="$FilePathRelativeToProjectRoot$" />
<option name="outputFilters">
<array />
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="$ProjectFileDir$/node_modules/.bin/prettier" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="Project Files" />
<option name="trackOnlyRoot" value="false" />
<option name="workingDir" value="$ProjectFileDir$" />
<envs />
</TaskOptions>
</component>
</project>

45
.idea/workspace.xml generated
View file

@ -2,16 +2,30 @@
<project version="4">
<component name="ChangeListManager">
<list default="true" id="8edc47ab-1265-4111-9771-536b24cc9310" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/vcs.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/og-image.jpg" afterDir="false" />
<change afterPath="$PROJECT_DIR$/static/og.jpg" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/content-en.json" beforeDir="false" afterPath="$PROJECT_DIR$/content-en.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/css/elements.css" beforeDir="false" afterPath="$PROJECT_DIR$/css/elements.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/css/page.css" beforeDir="false" afterPath="$PROJECT_DIR$/css/page.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/static/my-notes.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/content-en.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/content-hu.json" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/css/elements.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/css/main.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/css/page.css" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/index.html" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/js/content.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/js/main.js" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/avoid.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/color.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/forex.gif" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/led.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/led720.mp4" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/me.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/my-notes.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/og-image.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/og.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/photos.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/platform.png" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/process-simulator-input.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/process-simulator.jpg" beforeDir="false" />
<change beforePath="$PROJECT_DIR$/static/simulation.jpg" beforeDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
@ -23,6 +37,7 @@
<option name="RECENT_TEMPLATES">
<list>
<option value="JavaScript File" />
<option value="HTML File" />
</list>
</option>
</component>
@ -35,13 +50,22 @@
</component>
<component name="PropertiesComponent">
<property name="ASKED_ADD_EXTERNAL_FILES" value="true" />
<property name="DefaultHtmlFileTemplate" value="HTML File" />
<property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="ignore_missing_gitignore" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="nodejs_package_manager_path" value="npm" />
<property name="prettierjs.PrettierConfiguration.Package" value="/usr/local/lib/node_modules/prettier" />
<property name="settings.editor.selected.configurable" value="watcher.settings" />
<property name="ts.external.directory.path" value="C:\Projects\portfolio\CompiledCV\node_modules\typescript\lib" />
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="C:\Projects\portfolio\CompiledCV\src\content" />
<recent name="C:\Projects\portfolio\CompiledCV\dist" />
<recent name="C:\Projects\portfolio\CompiledCV\src" />
</key>
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$" />
</key>
@ -72,7 +96,8 @@
<workItem from="1576164066512" duration="3000" />
<workItem from="1576250286627" duration="24922000" />
<workItem from="1576342852221" duration="24000" />
<workItem from="1576352253939" duration="106000" />
<workItem from="1576352253939" duration="353000" />
<workItem from="1576748546157" duration="8755000" />
</task>
<servers />
</component>

24
custom.d.ts vendored Normal file
View file

@ -0,0 +1,24 @@
declare module "*.svg" {
const content: string;
export default content;
}
declare module "*.png" {
const content: string;
export default content;
}
declare module "*.jpg" {
const content: string;
export default content;
}
declare module "*.jpeg" {
const content: string;
export default content;
}
declare module "*.gif" {
const content: string;
export default content;
}

View file

@ -1,42 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta property="og:image:width" content="1500">
<meta property="og:image:height" content="785">
<meta property="og:title" content="Portfolio - Andr&aacute;s Schmelczer">
<meta property="og:description" content="You can view my projects on a timeline.">
<meta property="og:url" content="https://schmelczer.dev">
<meta property="og:image" content="https://schmelczer.dev/og-image.jpg">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="theme-color" content="#31343F" />
<title>Portfolio</title>
<link rel="stylesheet" href="css/main.css" />
<link rel="stylesheet" href="css/elements.css" />
<link rel="stylesheet" href="css/page.css" />
</head>
<body>
<section class="center" id="photo-viewer">
<img id="photo" alt="currently opened photo" src=""/>
<div id="exit"></div>
</section>
<header class="center">
<img id="header-pic" alt="a picture of me" src=""/>
<h1 id="name"></h1>
</header>
<section id="about"></section>
<main id="timeline"></main>
<footer class="card center"><a id="email"></a></footer>
</body>
<script src="js/content.js"></script>
<script src="js/main.js"></script>
</html>

View file

@ -1,157 +0,0 @@
const createPageFactory = ({
nameId,
pictureId,
aboutId,
timelineId,
emailId,
photoId,
photoViewerId,
}) => {
const createPage = async (src) => {
const {config, header, timeline, footer} = await getData(src);
processHeader(header);
processTimeline(timeline, config);
processFooter(footer);
setupGlobals(config);
};
const processHeader = ({name, picture, about}) => {
document.title = name;
getElement(nameId).textContent = name;
getElement(pictureId).src = picture;
getElement(pictureId).onclick = () => showPhoto(picture);
getElement(aboutId).innerHTML = listToHtml(about);
};
const listToHtml = list => list.map(element => {
if (!element.type || element.type === 'p') {
return `<p>${element}</p>`;
} else if (element.type === 'a') {
return `<a href="${element.href}" target="_blank"> ${element.text} </a>`;
} else if (element.type === 'video') {
return `<video controls><source src="${element.src}" /></video>`;
} else return '';
}).join('\n');
const processTimeline = (timeline, {showMore}) => {
getElement(timelineId).innerHTML = timeline.map(
element => timelineElementToHTML(element, createId(), showMore)
).join('\n');
};
const timelineElementToHTML = ({date, title, picture, description, more, link}, id, showMore) => `
<section>
<div class="line">
${date ? `<p class="date-wide-screen">${date}</p>` : ''}
</div>
<div class="card">
<h2>${title}</h2>
${date ? `<p class="date-narrow-screen">${date}</p>` : ''}
${picture ? `<img src="${picture}" onclick="showPhoto('${picture}');" alt="${picture}"/>` : ''}
${description ? `<p class="description">${description}</p>` : ''}
${more ? `
<div class="collapsed" id="${idToActivityId(id)}">
${listToHtml(more)}
</div>
<a id="${idToButtonId(id)}" onclick="toggleLongDescription(${id})">
${showMore}
</a>
`
: (link ? `<a href="http://${link}" target="_blank">${link}</a>` : '')}
</div>
</section>
`;
const processFooter = ({email}) => {
getElement(emailId).href = `mailto:${email}`;
getElement(emailId).textContent = email;
};
const hideFrame = () => {
getElement(photoViewerId).style['z-index'] = -1;
getElement(photoViewerId).style.opacity = '0';
};
const showPhoto = src => {
getElement(photoId).src = src;
getElement(photoViewerId).style['z-index'] = 1000;
getElement(photoViewerId).style.opacity = '1';
};
const setupGlobals = config => {
window.toggleLongDescription = toggleLongDescriptionFactory(config);
window.showPhoto = showPhoto;
window.hideFrame = hideFrame;
getElement(photoViewerId).addEventListener('click', hideFrame);
window.addEventListener('resize', onResize);
document.body.addEventListener('keydown', handleEscape);
};
const toggleLongDescriptionFactory = ({showMore, showLess}) => (id) => {
const button = getElement(idToButtonId(id));
const element = getElement(idToActivityId(id));
if (isClosed(element)) {
open(element);
button.textContent = showLess;
} else {
close(element);
button.textContent = showMore;
}
};
const onResize = () => {
const elements = document.getElementsByClassName('collapsed');
Array.prototype.forEach.call(elements, element => {
if (isOpen(element)) {
element.style.height = 'auto';
setTimeout(() => open(element), 100);
}
});
};
const isClosed = element => ['0px', '0', 0, ''].includes(element.style.height);
const isOpen = element => !isClosed(element);
const close = element => element.style.height = '0';
const open = element => element.style.height = `${element.scrollHeight}px`;
const handleEscape = event => {
if (event.key === 'Escape') {
hideFrame();
}
};
const getElementFactory = () => {
const foundElements = {};
return id => {
if (!(id in foundElements)) {
foundElements[id] = document.getElementById(id);
}
return foundElements[id];
}
};
const getElement = getElementFactory();
const getData = async (src) => await (await fetch(src, {
method: 'GET',
mode: 'cors',
cache: 'no-cache'
})).json();
const createIdFactory = () => {
let id = 0;
return () => id++;
};
const createId = createIdFactory();
const idToButtonId = (id) => `button_${id}`;
const idToActivityId = (id) => `activity_${id}`;
return createPage;
};

View file

@ -1,15 +0,0 @@
(async () => {
const src = 'content-en.json';
const ids = {
pictureId: 'header-pic',
nameId: 'name',
aboutId: 'about',
timelineId: 'timeline',
emailId: 'email',
photoViewerId: 'photo-viewer',
photoId: 'photo'
};
await createPageFactory(ids)(src);
document.body.style.visibility = 'visible';
})();

35
package.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "portfolio",
"version": "1.0.0",
"description": "An easily configurable portfolio.",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"repository": {
"type": "git",
"url": "git+https://github.com/schmelczerandras/timeline.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/schmelczerandras/timeline/issues"
},
"homepage": "https://github.com/schmelczerandras/timeline#readme",
"devDependencies": {
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.4.0",
"file-loader": "^5.0.2",
"html-webpack-plugin": "^3.2.0",
"node-sass": "^4.13.0",
"prettier": "^1.19.1",
"sass-loader": "^8.0.0",
"style-loader": "^1.0.2",
"ts-loader": "^6.2.1",
"typescript": "^3.7.3",
"webpack": "^4.41.4",
"webpack-cli": "^3.3.10"
}
}

View file

@ -1,65 +1,84 @@
{
"config": {
"showMore": "Show details",
"showLess": "Show less"
import me from "../static/me.jpg";
import forex from "../static/forex.gif";
import myNotes from "../static/my-notes.jpg";
import processSimulator from "../static/process-simulator.jpg";
import processSimulatorInput from "../static/process-simulator-input.jpg";
import citySimulation from "../static/simulation.jpg";
import color from "../static/color.jpg";
import platform from "../static/platform.png";
import photos from "../static/photos.jpg";
import led from "../static/led.jpg";
export const content = {
config: {
showMore: "Show details",
showLess: "Show less"
},
"header": {
"name": "András Schmelczer",
"picture": "/static/me.jpg",
"about": [
header: {
name: "András Schmelczer",
picture: me,
about: [
"I have always been fascinated by the engineering feats that surround us. When I realized that someday I might be able to contribute to these achievements, I knew that is what I need to aim for. As I am finishing my fifth semester at the Budapest University of Technology and Economics, I feel I am getting closer to it every day.",
"You can see some of the more interesting projects I have worked on below."
]
},
"timeline": [
timeline: [
{
"date": "2019 Autumn",
"title": "Predicting foreign exchange rates",
"picture": "/static/forex.gif",
"description": "From the animation we can see that my algorithm does a somewhat acceptable job at predicting (blue graph) the EUR/USD rates (green graph).",
"more": [
date: "2019 Autumn",
title: "Predicting foreign exchange rates",
picture: forex,
description:
"From the animation we can see that my algorithm does a somewhat acceptable job at predicting (blue graph) the EUR/USD rates (green graph).",
more: [
"In a nutshell, the algorithm (written with Python - NumPy, SciPy, Flask), extrapolates in the frequency domain. The steps are the following: smoothing the input values, differentiating, applying a short-time Fourier-transformation with overlapped (and Hanning-windowed) windows, extrapolating and then applying the inverse of these transformations to the extrapolated values.",
"Of course, there is still plenty of room for improvement, but even with this simple algorithm a mostly profitable trading strategy is viable. In my free time I may put more work into it."
]
},
{
"date": "2019 November",
"title": "My Notes",
"picture": "/static/my-notes.jpg",
"description": "A minimalist note organizer and editor powered by Markwon.",
"more": [
{ "type": "a", "href": "https://github.com/schmelczerandras/my-notes", "text": "MyNotes on GitHub" },
date: "2019 November",
title: "My Notes",
picture: myNotes,
description: "A minimalist note organizer and editor powered by Markwon.",
more: [
{
type: "a",
href: "https://github.com/schmelczerandras/my-notes",
text: "MyNotes on GitHub"
},
"A basic android app for creating and filtering notes written in markdown.",
"It was my homework for BME's Android and web development course. It was also my first experience with Android development."
]
},
{
"date": "2018 October - November",
"title": "Simulating the cooling system of a nuclear facility",
"picture": "/static/process-simulator.jpg",
"description": "Dynamically calculating the temperatures and flow velocities in a fluid based cooling system based on a simple model.",
"more": [
date: "2018 October - November",
title: "Simulating the cooling system of a nuclear facility",
picture: processSimulator,
description:
"Dynamically calculating the temperatures and flow velocities in a fluid based cooling system based on a simple model.",
more: [
"A simulated system can contain reactors (heaters / coolers), pumps, heat exchangers, drains sources, and of course, pipes.",
"The algorithm takes advantages of graphs and matrices to get to a next time frame.",
"Python is used for the backend along with Flask and NumPy. A REST API facilitates the communication between the layers. For drawing the frontend HTML5 canvas is utilized."
]
},
{
"date": "2018 October - November",
"title": "Graph editing application",
"picture": "/static/process-simulator-input.jpg",
"description": "An intuitive editor to create and edit input files for the nuclear facility simulator.",
"more": [
date: "2018 October - November",
title: "Graph editing application",
picture: processSimulatorInput,
description:
"An intuitive editor to create and edit input files for the nuclear facility simulator.",
more: [
"Nodes can be moved with drag&drop gestures. Editing the parameters of elements can be done on the right panel.",
"The UI is built with JavaFX. The output can be exported as JSON or directly uploaded to the simulation backend."
]
},
{
"date": "2018 July-August",
"title": "City simulation",
"picture": "/static/simulation.jpg",
"description": "Simulating a city where car crashes are more frequent than usual.",
"more": [
date: "2018 July-August",
title: "City simulation",
picture: citySimulation,
description:
"Simulating a city where car crashes are more frequent than usual.",
more: [
"Through a REST API the state of the traffic lights can be changed. The drivers follow the instructions of the traffic lights, so if a mistake is made, there will be collisions. There is also support for displaying tweets on a HUD.",
"This was created for a Cybersecurity challenge. With the help of this program the contestants could instantly see the effect of their work.",
"The most interesting aspect of this project was building it in a server-client architecture. The decisions of the agents is calculated server-side. The real challenge was broadcasting these decisions in a fault-tolerant way using minimal bandwidth.",
@ -67,50 +86,53 @@
]
},
{
"date": "2018 June",
"title": "Photo color grader",
"picture": "/static/color.jpg",
"description": "An innovative (at least I thought so) color grader web application.",
"more": [
date: "2018 June",
title: "Photo color grader",
picture: color,
description:
"An innovative (at least I thought so) color grader web application.",
more: [
"The most noteworthy feature of this application is the color selector UI. This program is only intended as a proof-of-concept, I wanted to experiment with some ideas and this was the outcome. ",
"You can select some colors and then apply transformations to the other colors as a function of their distance to the selected color.",
"By clicking on a colored circle you can change its settings. New circles can be created by clicking in the large circle (and they can also be moved by drag&drop).",
{ "type": "a", "href": "color", "text": "schmelczer.dev/color" }
{ type: "a", href: "color", text: "schmelczer.dev/color" }
]
},
{
"date": "2017 autumn",
date: "2017 autumn",
"title": "Platform game",
"picture": "/static/platform.png",
"description": "A 3D game written in C with the help of SDL 1.2 (I haven't heard of GPU programming at the time).",
"more": [
title: "Platform game",
picture: platform,
description:
"A 3D game written in C with the help of SDL 1.2 (I haven't heard of GPU programming at the time).",
more: [
"The maps are randomly generated and fully destroyable. The player is getting chased by flying enemies. Overall, I find it a really enjoyable game.",
"I did this as a homework for my Basics of Programming course."
]
},
{
"date": "2016 summer",
"title": "Photos",
"picture": "/static/photos.jpg",
"description": "A simple web page where you can view my photos.",
"link": "schmelczer.dev/photos"
date: "2016 summer",
title: "Photos",
picture: photos,
description: "A simple web page where you can view my photos.",
link: "schmelczer.dev/photos"
},
{
"date": "2016 spring",
"title": "Lights synchronised to music",
"picture": "/static/led.jpg",
"description": "A full stack application with a built-in music player which music controls the color of some RGB LED strips.",
"more": [
date: "2016 spring",
title: "Lights synchronised to music",
picture: led,
description:
"A full stack application with a built-in music player which music controls the color of some RGB LED strips.",
more: [
"This was my first non-trivial project which got finished. Obviously, it is rather far from perfect, but I am still proud that I was able to build it on my own.",
"The backend logic is written in Python the FFT is provided by NumPy. A quite simple frontend for accessing the music player and changing the settings also got built using vanilla web development technologies.",
"Below is a video showing the system in work.",
{ "type": "video", "src": "static/led720.mp4" }
{ type: "video", src: "static/led720.mp4" }
]
}
],
"footer": {
"email": "andras.schmelczer@schdesign.hu",
"cv": "/static/andras_schmelczer_cv.pdf"
footer: {
email: "andras.schmelczer@schdesign.hu",
cv: "/static/andras_schmelczer_cv.pdf"
}
}
};

View file

@ -1,4 +1,4 @@
{
/*{
"header": {
"name": "Schmelczer András",
"picture": "/static/me.jpg",
@ -91,3 +91,4 @@
"email": "andras.schmelczer@schdesign.hu"
}
}
*/

35
src/index.html Normal file
View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta property="og:image:width" content="1500">
<meta property="og:image:height" content="785">
<meta property="og:title" content="Portfolio - Andr&aacute;s Schmelczer">
<meta property="og:description" content="You can view my projects on a timeline.">
<meta property="og:url" content="https://schmelczer.dev">
<meta property="og:image" content="https://schmelczer.dev/og-image.jpg">
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="theme-color" content="#31343F" />
<title>Portfolio</title>
</head>
<body>
<section class="center" id="photo-viewer">
<img id="photo" alt="currently opened photo" src=""/>
<div id="exit"></div>
</section>
<header class="center">
<img id="header-pic" alt="a picture of me" src=""/>
<h1 id="name"></h1>
</header>
<section id="about"></section>
<main id="timeline"></main>
<footer class="card center"><a id="email"></a></footer>
</body>
</html>

View file

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 570 KiB

After

Width:  |  Height:  |  Size: 570 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 475 KiB

After

Width:  |  Height:  |  Size: 475 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 227 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 366 KiB

After

Width:  |  Height:  |  Size: 366 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 457 KiB

After

Width:  |  Height:  |  Size: 457 KiB

Before After
Before After

3
src/styles/index.scss Normal file
View file

@ -0,0 +1,3 @@
@import "main";
@import "elements";
@import "page";

19
src/ts/index.ts Normal file
View file

@ -0,0 +1,19 @@
import { createPageFactory } from "./parser";
import { content } from "../content/en";
import "../styles/index.scss";
(async () => {
const ids = {
pictureId: "header-pic",
nameId: "name",
aboutId: "about",
timelineId: "timeline",
emailId: "email",
photoViewerId: "photo-viewer",
photoId: "photo"
};
await createPageFactory(ids)(content);
document.body.style.visibility = "visible";
})();

168
src/ts/parser.ts Normal file
View file

@ -0,0 +1,168 @@
export const createPageFactory = ({
nameId,
pictureId,
aboutId,
timelineId,
emailId,
photoId,
photoViewerId
}) => {
const createPage = content => {
const { config, header, timeline, footer } = content;
processHeader(header);
processTimeline(timeline, config);
processFooter(footer);
setupGlobals(config);
};
const processHeader = ({ name, picture, about }) => {
document.title = name;
getElement(nameId).textContent = name;
getElement(pictureId).src = picture;
getElement(pictureId).onclick = () => showPhoto(picture);
getElement(aboutId).innerHTML = listToHtml(about);
};
const listToHtml = list =>
list
.map(element => {
if (!element.type || element.type === "p") {
return `<p>${element}</p>`;
} else if (element.type === "a") {
return `<a href="${element.href}" target="_blank"> ${element.text} </a>`;
} else if (element.type === "video") {
return `<video controls><source src="${element.src}" /></video>`;
} else return "";
})
.join("\n");
const processTimeline = (timeline, { showMore }) => {
getElement(timelineId).innerHTML = timeline
.map(element => timelineElementToHTML(element, createId(), showMore))
.join("\n");
};
const timelineElementToHTML = (
{ date, title, picture, description, more, link },
id,
showMore
) => `
<section>
<div class="line">
${date ? `<p class="date-wide-screen">${date}</p>` : ""}
</div>
<div class="card">
<h2>${title}</h2>
${date ? `<p class="date-narrow-screen">${date}</p>` : ""}
${
picture
? `<img src="${picture}" onclick="showPhoto('${picture}');" alt="${picture}"/>`
: ""
}
${description ? `<p class="description">${description}</p>` : ""}
${
more
? `
<div class="collapsed" id="${idToActivityId(id)}">
${listToHtml(more)}
</div>
<a id="${idToButtonId(id)}" onclick="toggleLongDescription(${id})">
${showMore}
</a>
`
: link
? `<a href="http://${link}" target="_blank">${link}</a>`
: ""
}
</div>
</section>
`;
const processFooter = ({ email }) => {
getElement(emailId).href = `mailto:${email}`;
getElement(emailId).textContent = email;
};
const hideFrame = () => {
getElement(photoViewerId).style["z-index"] = -1;
getElement(photoViewerId).style.opacity = "0";
};
const showPhoto = src => {
getElement(photoId).src = src;
getElement(photoViewerId).style["z-index"] = 1000;
getElement(photoViewerId).style.opacity = "1";
};
const setupGlobals = config => {
(window as any).toggleLongDescription = toggleLongDescriptionFactory(config);
(window as any).showPhoto = showPhoto;
(window as any).hideFrame = hideFrame;
getElement(photoViewerId).addEventListener("click", hideFrame);
window.addEventListener("resize", onResize);
document.body.addEventListener("keydown", handleEscape);
};
const toggleLongDescriptionFactory = ({ showMore, showLess }) => id => {
const button = getElement(idToButtonId(id));
const element = getElement(idToActivityId(id));
if (isClosed(element)) {
open(element);
button.textContent = showLess;
} else {
close(element);
button.textContent = showMore;
}
};
const onResize = () => {
const elements = document.getElementsByClassName("collapsed");
Array.prototype.forEach.call(elements, element => {
if (isOpen(element)) {
element.style.height = "auto";
setTimeout(() => open(element), 100);
}
});
};
const isClosed = element =>
["0px", "0", 0, ""].includes(element.style.height);
const isOpen = element => !isClosed(element);
const close = element => (element.style.height = "0");
const open = element => (element.style.height = `${element.scrollHeight}px`);
const handleEscape = event => {
if (event.key === "Escape") {
hideFrame();
}
};
const getElementFactory = () => {
const foundElements = {};
return id => {
if (!(id in foundElements)) {
foundElements[id] = document.getElementById(id);
}
return foundElements[id];
};
};
const getElement = getElementFactory();
const createIdFactory = () => {
let id = 0;
return () => id++;
};
const createId = createIdFactory();
const idToButtonId = id => `button_${id}`;
const idToActivityId = id => `activity_${id}`;
return createPage;
};

10
tsconfig.json Normal file
View file

@ -0,0 +1,10 @@
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": false,
"module": "es6",
"target": "es5",
"sourceMap": true,
"allowJs": true
}
}

48
webpack.config.js Normal file
View file

@ -0,0 +1,48 @@
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: "development",
devtool: "inline-source-map",
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
hash: true,
xhtml: true,
template: "./src/index.html"
})
],
entry: {
index: "./src/ts/index.ts"
},
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: ["style-loader", "css-loader", "sass-loader"]
},
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/
},
{
test: /\.(png|svg|jpe?g|gif)$/,
use: {
loader: "file-loader",
query: {
outputPath: "images"
}
}
}
]
},
resolve: {
extensions: [".ts"]
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist")
}
};