Refactor and minor fixes

This commit is contained in:
Andras Schmelczer 2022-09-21 21:57:58 +02:00
parent 2dc9c45642
commit fe75f9af88
No known key found for this signature in database
GPG key ID: 0EA1BC97D0AB076E
31 changed files with 187 additions and 193 deletions

View file

@ -1,6 +1,6 @@
import { PageBackground } from '../page/background/background';
import { Image } from '../page/basics/image/image';
import { PageFooter } from '../page/footer/footer';
import { Image } from '../page/basics/image/image.html';
import { Footer } from '../page/footer/footer.html';
import { PageHeader } from '../page/header/header';
import { PageImageViewer } from '../page/image-viewer/image-viewer';
import { Main } from '../page/main/main';
@ -28,7 +28,11 @@ export const create = (): Array<PageElement> => [
new PageBackground(1, 1),
new PageHeader({
name: `András Schmelczer`,
photo: new Image(meWebP, meJpeg, `a picture of me`, false),
photo: Image({
imageWebP: meWebP,
imageJpeg: meJpeg,
alt: `a picture of me`,
}),
about: [
`
I have always been fascinated by the engineering feats that surround us and pervade every aspect
@ -62,7 +66,7 @@ export const create = (): Array<PageElement> => [
ledsTimelineElement,
],
}),
new PageFooter({
Footer({
title: `Learn more`,
curriculaVitae: [{ name: `Curriculum vitae`, url: cvEnglish }],
email: `andras@schmelczer.dev`,

View file

@ -1,4 +1,4 @@
import { Image } from '../../page/basics/image/image';
import { Image } from '../../page/basics/image/image.html';
import { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import colourJpeg from '../media/color.jpg?format=jpg';
import colourWebP from '../media/color.jpg?format=webp';
@ -6,7 +6,12 @@ import colourWebP from '../media/color.jpg?format=webp';
export const colorsTimelineElement: TimelineElementParameters = {
title: `Photo colour grader`,
date: `2018 June`,
figure: new Image(colourWebP, colourJpeg, `a picture of the app`),
figure: Image({
imageWebP: colourWebP,
imageJpeg: colourJpeg,
alt: `a picture of the app`,
container: true,
}),
description: `An innovative (at least I thought so) colour grader web application.`,
more: [
`

View file

@ -1,4 +1,4 @@
import { Image } from '../../page/basics/image/image';
import { Image } from '../../page/basics/image/image.html';
import { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import myNotesJpeg from '../media/my-notes.png?format=jpg';
import myNotesWebP from '../media/my-notes.png?format=webp';
@ -7,7 +7,12 @@ import { GitHub } from '../shared';
export const myNotesTimelineElement: TimelineElementParameters = {
title: `My Notes`,
date: `2019 November`,
figure: new Image(myNotesWebP, myNotesJpeg, `two screenshots of the application`),
figure: Image({
imageWebP: myNotesWebP,
imageJpeg: myNotesJpeg,
alt: `two screenshots of the application`,
container: true,
}),
description: `A minimalist note organiser and editor powered by Markwon.`,
more: [
`

View file

@ -1,4 +1,4 @@
import { Image } from '../../page/basics/image/image';
import { Image } from '../../page/basics/image/image.html';
import { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import processSimulatorInputJpeg from '../media/process-simulator-input.jpg?format=jpg';
import processSimulatorInputWebP from '../media/process-simulator-input.jpg?format=webp';
@ -6,11 +6,12 @@ import processSimulatorInputWebP from '../media/process-simulator-input.jpg?form
export const nuclearEditorTimelineElement: TimelineElementParameters = {
title: `Graph editing application`,
date: `2018 October - November`,
figure: new Image(
processSimulatorInputWebP,
processSimulatorInputJpeg,
`a picture of the simulator's UI`
),
figure: Image({
imageWebP: processSimulatorInputWebP,
imageJpeg: processSimulatorInputJpeg,
alt: `a picture of the simulator's UI`,
container: true,
}),
description: `
An intuitive editor to create and edit input for the nuclear facility simulator.
`,

View file

@ -1,4 +1,4 @@
import { Image } from '../../page/basics/image/image';
import { Image } from '../../page/basics/image/image.html';
import { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import processSimulatorJpeg from '../media/process-simulator.jpg?format=jpg';
import processSimulatorWebP from '../media/process-simulator.jpg?format=webp';
@ -6,11 +6,12 @@ import processSimulatorWebP from '../media/process-simulator.jpg?format=webp';
export const nuclearTimelineElement: TimelineElementParameters = {
title: `Simulating the cooling system of a nuclear facility`,
date: `2018 October - November`,
figure: new Image(
processSimulatorWebP,
processSimulatorJpeg,
`a screenshot of the simulator`
),
figure: Image({
imageWebP: processSimulatorWebP,
imageJpeg: processSimulatorJpeg,
alt: `a screenshot of the simulator`,
container: true,
}),
description: `
The temperatures and flow velocities are dynamically calculated in a fluid-based
cooling system based on a simple model.

View file

@ -1,4 +1,4 @@
import { Image } from '../../page/basics/image/image';
import { Image } from '../../page/basics/image/image.html';
import { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import photosJpeg from '../media/photos.jpg?format=jpg';
import photosWebP from '../media/photos.jpg?format=webp';
@ -7,7 +7,12 @@ import { Open } from '../shared';
export const photosTimelineElement: TimelineElementParameters = {
title: `Photos`,
date: `2016 summer`,
figure: new Image(photosWebP, photosJpeg, `a picture of the website`),
figure: Image({
imageWebP: photosWebP,
imageJpeg: photosJpeg,
alt: `a picture of the website`,
container: true,
}),
description: `A simple webpage where you can view my photos.`,
more: [
`

View file

@ -1,4 +1,4 @@
import { Image } from '../../page/basics/image/image';
import { Image } from '../../page/basics/image/image.html';
import { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import towersJpeg from '../media/towers.png?format=jpg';
import towersWebP from '../media/towers.png?format=webp';
@ -7,7 +7,12 @@ import { GitHub, Open } from '../shared';
export const towersTimelineElement: TimelineElementParameters = {
title: `Towers tracking app`,
date: `2019 August - September`,
figure: new Image(towersWebP, towersJpeg, `a picture of the website`),
figure: Image({
imageWebP: towersWebP,
imageJpeg: towersJpeg,
alt: `a picture of the website`,
container: true,
}),
description: `An aesthetic representation of your previous and current goals/tasks.`,
more: [
`

View file

@ -4,7 +4,7 @@ import openIcon from '../../static/icons/open.svg';
import packageIcon from '../../static/icons/package.svg';
import pythonIcon from '../../static/icons/python.svg';
import youtubeIcon from '../../static/icons/youtube.svg';
import { ImageAnchorFactory } from '../page/basics/image-anchor/image-anchor';
import { ImageAnchorFactory } from '../page/basics/image-anchor/image-anchor.html';
export const GitHub = ImageAnchorFactory(githubIcon, 'Open on GitHub');
export const NPM = ImageAnchorFactory(packageIcon, 'Open on npm');

View file

@ -1,18 +1,14 @@
import { html } from '../../../types/html';
import { url } from '../../../types/url';
import './image-anchor.scss';
export const generate = ({
href,
svg,
title,
shouldDownload,
}: {
href: url;
svg: url;
title: string;
shouldDownload: boolean;
}): html => `
export const ImageAnchorFactory =
(
svg: string,
title: string,
{ shouldDownload = false }: { shouldDownload?: boolean } = {}
) =>
(href: url) =>
`
<a class="image-anchor"
href="${href}"
rel="noopener"

View file

@ -1,11 +0,0 @@
import { url } from '../../../types/url';
import { generate } from './image-anchor.html';
export const ImageAnchorFactory =
(
svg: string,
title: string,
{ shouldDownload = false }: { shouldDownload?: boolean } = {}
) =>
(href: url) =>
generate({ href, svg, title, shouldDownload });

View file

@ -3,18 +3,18 @@ import { html } from '../../../types/html';
import { ResponsiveImage } from '../../../types/responsive-image';
import './image.scss';
export const generate = ({
sizes,
export const Image = ({
imageWebP,
imageJpeg,
alt,
container,
container = false,
isIgnoredByImageViewer = false,
}: {
sizes: string;
imageWebP: ResponsiveImage;
imageJpeg: ResponsiveImage;
alt: string;
container: boolean;
container?: boolean;
isIgnoredByImageViewer?: boolean;
}): html => `
${
container
@ -23,33 +23,39 @@ export const generate = ({
}%">`
: ''
}
<picture loading="lazy">
<source
srcset="${imageWebP.srcSet}"
sizes="${sizes}"
width="${imageWebP.width}"
height="${imageWebP.height}"
type="image/webp"
alt="${alt}"
/>
<source
srcset="${imageJpeg.srcSet}"
sizes="${sizes}"
width="${imageJpeg.width}"
height="${imageJpeg.height}"
type="image/jpeg"
alt="${alt}"
/>
<img
tabindex="0"
loading="lazy"
srcset="${imageJpeg.srcSet}"
sizes="${sizes}"
width="${imageJpeg.width}"
height="${imageJpeg.height}"
src="${last(imageJpeg.images)?.path}"
alt="${alt}"
/>
</picture>
<picture loading="lazy">
<source
srcset="${imageWebP.srcSet}"
sizes="${getSizes(imageWebP)}"
width="${imageWebP.width}"
height="${imageWebP.height}"
type="image/webp"
alt="${alt}"
/>
<source
srcset="${imageJpeg.srcSet}"
sizes="${getSizes(imageJpeg)}"
width="${imageJpeg.width}"
height="${imageJpeg.height}"
type="image/jpeg"
alt="${alt}"
/>
<img
${isIgnoredByImageViewer ? 'image-viewer-ignore' : ''}
tabindex="0"
loading="lazy"
width="${imageJpeg.width}"
height="${imageJpeg.height}"
src="${last(imageJpeg.images)?.path}"
alt="${alt}"
/>
</picture>
${container ? `</div>` : ''}
`;
const IMAGE_SCREEN_RATIO = 0.8;
const getSizes = (image: ResponsiveImage): string =>
image.images
.slice(0, -1)
.map((d) => `(max-width: ${d.width / IMAGE_SCREEN_RATIO}px) ${d.width}px,`)
.join('\n') + `\n${last(image.images)!.width}px`;

View file

@ -1,37 +0,0 @@
import { createElement } from '../../../helper/create-element';
import { last } from '../../../helper/last';
import { ResponsiveImage } from '../../../types/responsive-image';
import { PageElement } from '../../page-element';
import { generate } from './image.html';
export class Image extends PageElement {
private static readonly imageScreenRatio = 0.8;
public constructor(
imageWebP: ResponsiveImage,
imageJpeg: ResponsiveImage,
alt: string,
container = true
) {
super(
createElement(
generate({
imageWebP,
imageJpeg,
alt,
container,
sizes: Image.getSizes(imageWebP),
})
)
);
}
private static getSizes(image: ResponsiveImage): string {
return (
image.images
.slice(0, -1)
.map((d) => `(max-width: ${d.width / Image.imageScreenRatio}px) ${d.width}px,`)
.join('\n') + `\n${last(image.images)!.width}px`
);
}
}

View file

@ -1,11 +1,27 @@
import loading from '../../../../static/icons/loading.svg';
import play from '../../../../static/icons/play-button.svg';
import { html } from '../../../types/html';
import { ResponsiveImage } from '../../../types/responsive-image';
import { Image } from '../../basics/image/image.html';
import './preview.scss';
export const generate = ({ alt }: { alt: string }): html => `
export const generate = ({
alt,
posterWebP,
posterJpeg,
}: {
alt: string;
posterWebP: ResponsiveImage;
posterJpeg: ResponsiveImage;
}): html => `
<div class="preview">
<img image-viewer-ignore class="poster"/>
${Image({
imageWebP: posterWebP,
imageJpeg: posterJpeg,
alt,
container: true,
isIgnoredByImageViewer: true,
})}
<div class="overlay">
<div class="loading">${loading}</div>
<iframe title="${alt}" allowfullscreen loading="lazy"></iframe>

View file

@ -1,7 +1,6 @@
import { createElement } from '../../../helper/create-element';
import { ResponsiveImage } from '../../../types/responsive-image';
import { PageElement } from '../../page-element';
import { Image } from '../image/image';
import { generate } from './preview.html';
export class Preview extends PageElement {
@ -11,9 +10,8 @@ export class Preview extends PageElement {
private readonly url: string,
alt: string
) {
super(createElement(generate({ alt })));
super(createElement(generate({ posterWebP, posterJpeg, alt })));
this.url += '?portfolioView';
this.attachElementByReplacing('.poster', new Image(posterWebP, posterJpeg, alt));
this.query('.start-button').addEventListener('click', this.loadContent.bind(this));
}

View file

@ -0,0 +1,10 @@
import { ResponsiveImage } from '../../../types/responsive-image';
import { url } from '../../../types/url';
export interface VideoParameters {
mp4: url;
webm: url;
posterWebP: ResponsiveImage;
posterJpeg: ResponsiveImage;
invertButton?: boolean;
}

View file

@ -1,19 +1,26 @@
import loading from '../../../../static/icons/loading.svg';
import play from '../../../../static/icons/play-button.svg';
import { html } from '../../../types/html';
import { VideoParameters } from './video';
import { Image } from '../../basics/image/image.html';
import { VideoParameters } from './video-parameters';
import './video.scss';
export const generate = ({
webm,
mp4,
posterWebP,
posterJpeg,
invertButton,
}: VideoParameters): html => `
<div class="figure-container video-container" style="padding-top:${
(posterJpeg.height / posterJpeg.width) * 100
}%">
<img image-viewer-ignore class="poster"/>
${Image({
imageWebP: posterWebP,
imageJpeg: posterJpeg,
alt: `thumbnail for the video`,
isIgnoredByImageViewer: true,
})}
<div class="loading">${loading}</div>
<video playsinline preload="none">
<source src="${webm}" type="video/webm"/>

View file

@ -1,28 +1,13 @@
import { createElement } from '../../../helper/create-element';
import { ResponsiveImage } from '../../../types/responsive-image';
import { url } from '../../../types/url';
import { PageElement } from '../../page-element';
import { Image } from '../image/image';
import { VideoParameters } from './video-parameters';
import { generate } from './video.html';
export interface VideoParameters {
mp4: url;
webm: url;
posterWebP: ResponsiveImage;
posterJpeg: ResponsiveImage;
invertButton?: boolean;
}
export class Video extends PageElement {
private video: HTMLVideoElement;
public constructor(options: VideoParameters) {
super(createElement(generate(options)));
this.attachElementByReplacing(
'.poster',
new Image(options.posterWebP, options.posterJpeg, `thumbnail for the video`, false)
);
this.video = this.query('video') as HTMLVideoElement;
this.video.addEventListener('click', this.startVideo.bind(this));
this.video.addEventListener('play', () =>

View file

@ -2,17 +2,27 @@ import cvIcon from '../../../static/icons/cv.svg';
import emailIcon from '../../../static/icons/email.svg';
import linkedinIcon from '../../../static/icons/linkedin.svg';
import { html } from '../../types/html';
import { FooterParameters } from './footer';
import { url } from '../../types/url';
import './footer.scss';
export const generate = ({
export const Footer = ({
title,
email,
curriculaVitae,
linkedin,
lastEditText,
lastEdit,
}: FooterParameters): html => `
}: {
title: string;
email: url;
linkedin: url;
curriculaVitae: Array<{
name: string;
url: url;
}>;
lastEditText: string;
lastEdit: Date;
}): html => `
<footer id="footer">
<h2>${title}</h2>
<ul>
@ -25,7 +35,7 @@ export const generate = ({
</li>
`
)
.join('\n')}
.join('')}
<li>
${linkedinIcon}
<a id="linkedin" target="_blank" href="${linkedin}">Find me on LinkedIn</a>

View file

@ -1,10 +1,8 @@
@use '../../style/mixins' as *;
#footer {
footer#footer {
text-align: center;
margin-top: var(--large-margin);
width: 100%;
a {
@include link;

View file

@ -1,22 +0,0 @@
import { createElement } from '../../helper/create-element';
import { url } from '../../types/url';
import { PageElement } from '../page-element';
import { generate } from './footer.html';
export interface FooterParameters {
title: string;
email: url;
linkedin: url;
curriculaVitae: Array<{
name: string;
url: url;
}>;
lastEditText: string;
lastEdit: Date;
}
export class PageFooter extends PageElement {
constructor(footer: FooterParameters) {
super(createElement(generate(footer)));
}
}

View file

@ -4,14 +4,16 @@ import './header.scss';
export const generate = ({
name,
about,
photo,
}: {
name: string;
about: Array<string>;
photo: html;
}): html => `
<section id="about">
<div class="picture"></div>
${photo}
<div class="placeholder"></div>
<h1>${name}</h1>
${about.map((t) => `<p>${t}</p>`).join('\n')}
${about.map((t) => `<p>${t}</p>`).join('')}
</section>
`;

View file

@ -1,8 +1,8 @@
import { createElement } from '../../helper/create-element';
import { Image } from '../basics/image/image';
import { html } from '../../types/html';
import { PageElement } from '../page-element';
import { PageThemeSwitcher } from '../theme-switcher/theme-switcher';
import { generate } from './header.html';
import { PageThemeSwitcher } from './theme-switcher/theme-switcher';
export class PageHeader extends PageElement {
public constructor({
@ -11,12 +11,10 @@ export class PageHeader extends PageElement {
about,
}: {
name: string;
photo: Image;
photo: html;
about: Array<string>;
}) {
super(createElement(generate({ name, about })));
this.attachElementByReplacing('.picture', photo);
super(createElement(generate({ name, about, photo })));
this.attachElement(new PageThemeSwitcher());
}
}

View file

@ -1,4 +1,4 @@
import { html } from '../../types/html';
import { html } from '../../../types/html';
import './theme-switcher.scss';
export const generate = (): html => `

View file

@ -1,4 +1,4 @@
@use '../../style/mixins' as *;
@use '../../../style/mixins' as *;
#theme-switcher {
@include on-large-screen {

View file

@ -1,11 +1,14 @@
import { createElement } from '../../helper/create-element';
import { turnOffAnimations, turnOnAnimations } from '../../style/animations/animations';
import { createElement } from '../../../helper/create-element';
import {
turnOffAnimations,
turnOnAnimations,
} from '../../../style/animations/animations';
import {
isSystemLevelDarkModeEnabled,
turnOnDarkMode,
turnOnLightMode,
} from '../../style/dark-mode/dark-mode';
import { PageElement } from '../page-element';
} from '../../../style/dark-mode/dark-mode';
import { PageElement } from '../../page-element';
import { generate } from './theme-switcher.html';
export class PageThemeSwitcher extends PageElement {

View file

@ -3,8 +3,11 @@ import { PageElement } from '../page-element';
import { generate } from './main.html';
export class Main extends PageElement {
constructor(...children: Array<PageElement>) {
super(createElement(generate()), children);
children.forEach((c) => this.attachElement(c));
constructor(...children: Array<PageElement | string>) {
const actualChildren = children.map((c) =>
c instanceof PageElement ? c : new PageElement(createElement(c))
);
super(createElement(generate()), actualChildren);
actualChildren.forEach((c) => this.attachElement(c));
}
}

View file

@ -1,4 +1,4 @@
export abstract class PageElement {
export class PageElement {
public constructor(
public readonly htmlRoot: HTMLElement,
protected children: Array<PageElement> = []

View file

@ -1,11 +1,10 @@
import { html } from '../../../types/html';
import { Image } from '../../basics/image/image';
import { Preview } from '../../basics/preview/preview';
import { Video } from '../../basics/video/video';
export interface TimelineElementParameters {
date: string;
figure: Image | Video | Preview;
figure: html | Video | Preview;
title: string;
description: string;
more: Array<string>;

View file

@ -26,7 +26,7 @@ export const generate = (
more
? `
<div class="more">
${more.map((t) => `<p>${t}</p>`).join('\n')}
${more.map((t) => `<p>${t}</p>`).join('')}
</div>`
: ''
}

View file

@ -109,6 +109,7 @@
h2 {
text-align: center;
margin-bottom: -10px;
> a {
@include sub-title-font();
@ -131,7 +132,8 @@
}
}
.description {
.description,
.info-button {
text-align: center;
}

View file

@ -18,7 +18,12 @@ export class PageTimelineElement extends PageElement {
addEventListener('resize', this.handleResize.bind(this));
this.query('.info-button').addEventListener('click', this.toggleOpen.bind(this));
this.attachElementByReplacing('.figure', timelineElement.figure);
this.attachElementByReplacing(
'.figure',
timelineElement.figure instanceof PageElement
? timelineElement.figure
: new PageElement(createElement(timelineElement.figure))
);
this.isOpen = false;
}