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 { PageBackground } from '../page/background/background';
import { Image } from '../page/basics/image/image'; import { Image } from '../page/basics/image/image.html';
import { PageFooter } from '../page/footer/footer'; import { Footer } from '../page/footer/footer.html';
import { PageHeader } from '../page/header/header'; import { PageHeader } from '../page/header/header';
import { PageImageViewer } from '../page/image-viewer/image-viewer'; import { PageImageViewer } from '../page/image-viewer/image-viewer';
import { Main } from '../page/main/main'; import { Main } from '../page/main/main';
@ -28,7 +28,11 @@ export const create = (): Array<PageElement> => [
new PageBackground(1, 1), new PageBackground(1, 1),
new PageHeader({ new PageHeader({
name: `András Schmelczer`, 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: [ about: [
` `
I have always been fascinated by the engineering feats that surround us and pervade every aspect 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, ledsTimelineElement,
], ],
}), }),
new PageFooter({ Footer({
title: `Learn more`, title: `Learn more`,
curriculaVitae: [{ name: `Curriculum vitae`, url: cvEnglish }], curriculaVitae: [{ name: `Curriculum vitae`, url: cvEnglish }],
email: `andras@schmelczer.dev`, 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 { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import colourJpeg from '../media/color.jpg?format=jpg'; import colourJpeg from '../media/color.jpg?format=jpg';
import colourWebP from '../media/color.jpg?format=webp'; import colourWebP from '../media/color.jpg?format=webp';
@ -6,7 +6,12 @@ import colourWebP from '../media/color.jpg?format=webp';
export const colorsTimelineElement: TimelineElementParameters = { export const colorsTimelineElement: TimelineElementParameters = {
title: `Photo colour grader`, title: `Photo colour grader`,
date: `2018 June`, 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.`, description: `An innovative (at least I thought so) colour grader web application.`,
more: [ 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 { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import myNotesJpeg from '../media/my-notes.png?format=jpg'; import myNotesJpeg from '../media/my-notes.png?format=jpg';
import myNotesWebP from '../media/my-notes.png?format=webp'; import myNotesWebP from '../media/my-notes.png?format=webp';
@ -7,7 +7,12 @@ import { GitHub } from '../shared';
export const myNotesTimelineElement: TimelineElementParameters = { export const myNotesTimelineElement: TimelineElementParameters = {
title: `My Notes`, title: `My Notes`,
date: `2019 November`, 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.`, description: `A minimalist note organiser and editor powered by Markwon.`,
more: [ 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 { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import processSimulatorInputJpeg from '../media/process-simulator-input.jpg?format=jpg'; import processSimulatorInputJpeg from '../media/process-simulator-input.jpg?format=jpg';
import processSimulatorInputWebP from '../media/process-simulator-input.jpg?format=webp'; 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 = { export const nuclearEditorTimelineElement: TimelineElementParameters = {
title: `Graph editing application`, title: `Graph editing application`,
date: `2018 October - November`, date: `2018 October - November`,
figure: new Image( figure: Image({
processSimulatorInputWebP, imageWebP: processSimulatorInputWebP,
processSimulatorInputJpeg, imageJpeg: processSimulatorInputJpeg,
`a picture of the simulator's UI` alt: `a picture of the simulator's UI`,
), container: true,
}),
description: ` description: `
An intuitive editor to create and edit input for the nuclear facility simulator. 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 { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import processSimulatorJpeg from '../media/process-simulator.jpg?format=jpg'; import processSimulatorJpeg from '../media/process-simulator.jpg?format=jpg';
import processSimulatorWebP from '../media/process-simulator.jpg?format=webp'; 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 = { export const nuclearTimelineElement: TimelineElementParameters = {
title: `Simulating the cooling system of a nuclear facility`, title: `Simulating the cooling system of a nuclear facility`,
date: `2018 October - November`, date: `2018 October - November`,
figure: new Image( figure: Image({
processSimulatorWebP, imageWebP: processSimulatorWebP,
processSimulatorJpeg, imageJpeg: processSimulatorJpeg,
`a screenshot of the simulator` alt: `a screenshot of the simulator`,
), container: true,
}),
description: ` description: `
The temperatures and flow velocities are dynamically calculated in a fluid-based The temperatures and flow velocities are dynamically calculated in a fluid-based
cooling system based on a simple model. 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 { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import photosJpeg from '../media/photos.jpg?format=jpg'; import photosJpeg from '../media/photos.jpg?format=jpg';
import photosWebP from '../media/photos.jpg?format=webp'; import photosWebP from '../media/photos.jpg?format=webp';
@ -7,7 +7,12 @@ import { Open } from '../shared';
export const photosTimelineElement: TimelineElementParameters = { export const photosTimelineElement: TimelineElementParameters = {
title: `Photos`, title: `Photos`,
date: `2016 summer`, 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.`, description: `A simple webpage where you can view my photos.`,
more: [ 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 { TimelineElementParameters } from '../../page/timeline/timeline-element/timeline-element-parameters';
import towersJpeg from '../media/towers.png?format=jpg'; import towersJpeg from '../media/towers.png?format=jpg';
import towersWebP from '../media/towers.png?format=webp'; import towersWebP from '../media/towers.png?format=webp';
@ -7,7 +7,12 @@ import { GitHub, Open } from '../shared';
export const towersTimelineElement: TimelineElementParameters = { export const towersTimelineElement: TimelineElementParameters = {
title: `Towers tracking app`, title: `Towers tracking app`,
date: `2019 August - September`, 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.`, description: `An aesthetic representation of your previous and current goals/tasks.`,
more: [ more: [
` `

View file

@ -4,7 +4,7 @@ import openIcon from '../../static/icons/open.svg';
import packageIcon from '../../static/icons/package.svg'; import packageIcon from '../../static/icons/package.svg';
import pythonIcon from '../../static/icons/python.svg'; import pythonIcon from '../../static/icons/python.svg';
import youtubeIcon from '../../static/icons/youtube.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 GitHub = ImageAnchorFactory(githubIcon, 'Open on GitHub');
export const NPM = ImageAnchorFactory(packageIcon, 'Open on npm'); 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 { url } from '../../../types/url';
import './image-anchor.scss'; import './image-anchor.scss';
export const generate = ({ export const ImageAnchorFactory =
href, (
svg, svg: string,
title, title: string,
shouldDownload, { shouldDownload = false }: { shouldDownload?: boolean } = {}
}: { ) =>
href: url; (href: url) =>
svg: url; `
title: string;
shouldDownload: boolean;
}): html => `
<a class="image-anchor" <a class="image-anchor"
href="${href}" href="${href}"
rel="noopener" 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 { ResponsiveImage } from '../../../types/responsive-image';
import './image.scss'; import './image.scss';
export const generate = ({ export const Image = ({
sizes,
imageWebP, imageWebP,
imageJpeg, imageJpeg,
alt, alt,
container, container = false,
isIgnoredByImageViewer = false,
}: { }: {
sizes: string;
imageWebP: ResponsiveImage; imageWebP: ResponsiveImage;
imageJpeg: ResponsiveImage; imageJpeg: ResponsiveImage;
alt: string; alt: string;
container: boolean; container?: boolean;
isIgnoredByImageViewer?: boolean;
}): html => ` }): html => `
${ ${
container container
@ -23,33 +23,39 @@ export const generate = ({
}%">` }%">`
: '' : ''
} }
<picture loading="lazy"> <picture loading="lazy">
<source <source
srcset="${imageWebP.srcSet}" srcset="${imageWebP.srcSet}"
sizes="${sizes}" sizes="${getSizes(imageWebP)}"
width="${imageWebP.width}" width="${imageWebP.width}"
height="${imageWebP.height}" height="${imageWebP.height}"
type="image/webp" type="image/webp"
alt="${alt}" alt="${alt}"
/> />
<source <source
srcset="${imageJpeg.srcSet}" srcset="${imageJpeg.srcSet}"
sizes="${sizes}" sizes="${getSizes(imageJpeg)}"
width="${imageJpeg.width}" width="${imageJpeg.width}"
height="${imageJpeg.height}" height="${imageJpeg.height}"
type="image/jpeg" type="image/jpeg"
alt="${alt}" alt="${alt}"
/> />
<img <img
tabindex="0" ${isIgnoredByImageViewer ? 'image-viewer-ignore' : ''}
loading="lazy" tabindex="0"
srcset="${imageJpeg.srcSet}" loading="lazy"
sizes="${sizes}" width="${imageJpeg.width}"
width="${imageJpeg.width}" height="${imageJpeg.height}"
height="${imageJpeg.height}" src="${last(imageJpeg.images)?.path}"
src="${last(imageJpeg.images)?.path}" alt="${alt}"
alt="${alt}" />
/> </picture>
</picture>
${container ? `</div>` : ''} ${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 loading from '../../../../static/icons/loading.svg';
import play from '../../../../static/icons/play-button.svg'; import play from '../../../../static/icons/play-button.svg';
import { html } from '../../../types/html'; import { html } from '../../../types/html';
import { ResponsiveImage } from '../../../types/responsive-image';
import { Image } from '../../basics/image/image.html';
import './preview.scss'; 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"> <div class="preview">
<img image-viewer-ignore class="poster"/> ${Image({
imageWebP: posterWebP,
imageJpeg: posterJpeg,
alt,
container: true,
isIgnoredByImageViewer: true,
})}
<div class="overlay"> <div class="overlay">
<div class="loading">${loading}</div> <div class="loading">${loading}</div>
<iframe title="${alt}" allowfullscreen loading="lazy"></iframe> <iframe title="${alt}" allowfullscreen loading="lazy"></iframe>

View file

@ -1,7 +1,6 @@
import { createElement } from '../../../helper/create-element'; import { createElement } from '../../../helper/create-element';
import { ResponsiveImage } from '../../../types/responsive-image'; import { ResponsiveImage } from '../../../types/responsive-image';
import { PageElement } from '../../page-element'; import { PageElement } from '../../page-element';
import { Image } from '../image/image';
import { generate } from './preview.html'; import { generate } from './preview.html';
export class Preview extends PageElement { export class Preview extends PageElement {
@ -11,9 +10,8 @@ export class Preview extends PageElement {
private readonly url: string, private readonly url: string,
alt: string alt: string
) { ) {
super(createElement(generate({ alt }))); super(createElement(generate({ posterWebP, posterJpeg, alt })));
this.url += '?portfolioView'; this.url += '?portfolioView';
this.attachElementByReplacing('.poster', new Image(posterWebP, posterJpeg, alt));
this.query('.start-button').addEventListener('click', this.loadContent.bind(this)); 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 loading from '../../../../static/icons/loading.svg';
import play from '../../../../static/icons/play-button.svg'; import play from '../../../../static/icons/play-button.svg';
import { html } from '../../../types/html'; import { html } from '../../../types/html';
import { VideoParameters } from './video'; import { Image } from '../../basics/image/image.html';
import { VideoParameters } from './video-parameters';
import './video.scss'; import './video.scss';
export const generate = ({ export const generate = ({
webm, webm,
mp4, mp4,
posterWebP,
posterJpeg, posterJpeg,
invertButton, invertButton,
}: VideoParameters): html => ` }: VideoParameters): html => `
<div class="figure-container video-container" style="padding-top:${ <div class="figure-container video-container" style="padding-top:${
(posterJpeg.height / posterJpeg.width) * 100 (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> <div class="loading">${loading}</div>
<video playsinline preload="none"> <video playsinline preload="none">
<source src="${webm}" type="video/webm"/> <source src="${webm}" type="video/webm"/>

View file

@ -1,28 +1,13 @@
import { createElement } from '../../../helper/create-element'; import { createElement } from '../../../helper/create-element';
import { ResponsiveImage } from '../../../types/responsive-image';
import { url } from '../../../types/url';
import { PageElement } from '../../page-element'; import { PageElement } from '../../page-element';
import { Image } from '../image/image'; import { VideoParameters } from './video-parameters';
import { generate } from './video.html'; import { generate } from './video.html';
export interface VideoParameters {
mp4: url;
webm: url;
posterWebP: ResponsiveImage;
posterJpeg: ResponsiveImage;
invertButton?: boolean;
}
export class Video extends PageElement { export class Video extends PageElement {
private video: HTMLVideoElement; private video: HTMLVideoElement;
public constructor(options: VideoParameters) { public constructor(options: VideoParameters) {
super(createElement(generate(options))); 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 = this.query('video') as HTMLVideoElement;
this.video.addEventListener('click', this.startVideo.bind(this)); this.video.addEventListener('click', this.startVideo.bind(this));
this.video.addEventListener('play', () => 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 emailIcon from '../../../static/icons/email.svg';
import linkedinIcon from '../../../static/icons/linkedin.svg'; import linkedinIcon from '../../../static/icons/linkedin.svg';
import { html } from '../../types/html'; import { html } from '../../types/html';
import { FooterParameters } from './footer'; import { url } from '../../types/url';
import './footer.scss'; import './footer.scss';
export const generate = ({ export const Footer = ({
title, title,
email, email,
curriculaVitae, curriculaVitae,
linkedin, linkedin,
lastEditText, lastEditText,
lastEdit, lastEdit,
}: FooterParameters): html => ` }: {
title: string;
email: url;
linkedin: url;
curriculaVitae: Array<{
name: string;
url: url;
}>;
lastEditText: string;
lastEdit: Date;
}): html => `
<footer id="footer"> <footer id="footer">
<h2>${title}</h2> <h2>${title}</h2>
<ul> <ul>
@ -25,7 +35,7 @@ export const generate = ({
</li> </li>
` `
) )
.join('\n')} .join('')}
<li> <li>
${linkedinIcon} ${linkedinIcon}
<a id="linkedin" target="_blank" href="${linkedin}">Find me on LinkedIn</a> <a id="linkedin" target="_blank" href="${linkedin}">Find me on LinkedIn</a>

View file

@ -1,10 +1,8 @@
@use '../../style/mixins' as *; @use '../../style/mixins' as *;
#footer { footer#footer {
text-align: center; text-align: center;
margin-top: var(--large-margin); margin-top: var(--large-margin);
width: 100%;
a { a {
@include link; @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 = ({ export const generate = ({
name, name,
about, about,
photo,
}: { }: {
name: string; name: string;
about: Array<string>; about: Array<string>;
photo: html;
}): html => ` }): html => `
<section id="about"> <section id="about">
<div class="picture"></div> ${photo}
<div class="placeholder"></div> <div class="placeholder"></div>
<h1>${name}</h1> <h1>${name}</h1>
${about.map((t) => `<p>${t}</p>`).join('\n')} ${about.map((t) => `<p>${t}</p>`).join('')}
</section> </section>
`; `;

View file

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

View file

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

View file

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

View file

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

View file

@ -3,8 +3,11 @@ import { PageElement } from '../page-element';
import { generate } from './main.html'; import { generate } from './main.html';
export class Main extends PageElement { export class Main extends PageElement {
constructor(...children: Array<PageElement>) { constructor(...children: Array<PageElement | string>) {
super(createElement(generate()), children); const actualChildren = children.map((c) =>
children.forEach((c) => this.attachElement(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 constructor(
public readonly htmlRoot: HTMLElement, public readonly htmlRoot: HTMLElement,
protected children: Array<PageElement> = [] protected children: Array<PageElement> = []

View file

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

View file

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

View file

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

View file

@ -18,7 +18,12 @@ export class PageTimelineElement extends PageElement {
addEventListener('resize', this.handleResize.bind(this)); addEventListener('resize', this.handleResize.bind(this));
this.query('.info-button').addEventListener('click', this.toggleOpen.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; this.isOpen = false;
} }