Copy everything

This commit is contained in:
schmelczerandras 2020-08-12 10:52:09 +02:00 committed by schmelczerandras
commit 8526739939
68 changed files with 1075 additions and 0 deletions

5
.dockerignore Normal file
View file

@ -0,0 +1,5 @@
node_modules
dist
target
package-lock.json
.*

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
og-image.jpg filter=lfs diff=lfs merge=lfs -text

49
.github/workflows/main.yaml vendored Normal file
View file

@ -0,0 +1,49 @@
name: Deploy everything
on:
push:
branches:
- main
env:
CONTAINER_REGISTRY: schmelczera
DOMAIN: "161.35.71.163"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout current branch with lfs
uses: actions/checkout@main
with:
lfs: true
- name: Setup auth tokens
run: |
docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push job
run: |
docker build . -t $CONTAINER_REGISTRY/photos
docker push $CONTAINER_REGISTRY/photos
push:
runs-on: ubuntu-latest
needs:
- build
steps:
- name: Checkout current branch with lfs
uses: actions/checkout@main
with:
lfs: true
- name: Setup auth tokens
run: |
# SSH key
mkdir ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 400 ~/.ssh/id_ed25519
ssh -o StrictHostKeyChecking=no root@$DOMAIN uptime
- name: Stack deploy
run: |
DOCKER_HOST=ssh://root@$DOMAIN docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASSWORD }}
DOCKER_HOST=ssh://root@$DOMAIN docker stack deploy photos -c docker-compose.yml --with-registry-auth

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
node_modules
dist
target
package-lock.json

9
.idea/dictionaries/andras.xml generated Normal file
View file

@ -0,0 +1,9 @@
<component name="ProjectDictionaryState">
<dictionary name="andras">
<words>
<w>andrás</w>
<w>imgs</w>
<w>schmelczer</w>
</words>
</dictionary>
</component>

6
.idea/misc.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/photos.iml" filepath="$PROJECT_DIR$/.idea/photos.iml" />
</modules>
</component>
</project>

8
.idea/photos.iml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

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

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions">
<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="ts" />
<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="scss" />
<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>

87
.idea/workspace.xml generated Normal file
View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="67a50e5c-f550-4763-aa56-c67942ec6666" name="Default Changelist" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="PackageJsonUpdateNotifier">
<dismissed value="$PROJECT_DIR$/package.json" />
</component>
<component name="ProjectId" id="1WHpfQesortnFO5XuuiNHiPc6f1" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showExcludedFiles" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent">
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" />
<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="$PROJECT_DIR$/node_modules/typescript/lib" />
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src" />
</key>
<key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$" />
</key>
</component>
<component name="ServiceViewManager">
<option name="viewStates">
<list>
<serviceView>
<treeState>
<expand />
<select />
</treeState>
</serviceView>
</list>
</option>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="67a50e5c-f550-4763-aa56-c67942ec6666" name="Default Changelist" comment="" />
<created>1578816476846</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1578816476846</updated>
<workItem from="1578816478170" duration="27472000" />
</task>
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="WindowStateProjectService">
<state x="1571" y="336" width="679" height="462" key="EditFileWatcherDialog" timestamp="1578818832892">
<screen x="0" y="27" width="2560" height="1053" />
</state>
<state x="1571" y="336" width="679" height="462" key="EditFileWatcherDialog/0.27.2560.1053@0.27.2560.1053" timestamp="1578818832892" />
<state x="1699" y="311" width="424" height="491" key="FileChooserDialogImpl" timestamp="1578818955042">
<screen x="0" y="27" width="2560" height="1053" />
</state>
<state x="1699" y="311" width="424" height="491" key="FileChooserDialogImpl/0.27.2560.1053@0.27.2560.1053" timestamp="1578818955042" />
<state x="1420" y="188" width="992" height="737" key="SettingsEditor" timestamp="1578818833773">
<screen x="0" y="27" width="2560" height="1053" />
</state>
<state x="1420" y="188" width="992" height="737" key="SettingsEditor/0.27.2560.1053@0.27.2560.1053" timestamp="1578818833773" />
<state x="1533" y="257" width="774" height="592" key="find.popup" timestamp="1578852407526">
<screen x="0" y="27" width="2560" height="1053" />
</state>
<state x="1533" y="257" width="774" height="592" key="find.popup/0.27.2560.1053@0.27.2560.1053" timestamp="1578852407526" />
<state x="1583" y="259" width="672" height="678" key="search.everywhere.popup" timestamp="1578831923180">
<screen x="0" y="27" width="2560" height="1053" />
</state>
<state x="1583" y="259" width="672" height="678" key="search.everywhere.popup/0.27.2560.1053@0.27.2560.1053" timestamp="1578831923180" />
</component>
</project>

6
.prettierrc Normal file
View file

@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"singleQuote": true,
"endOfLine": "lf"
}

23
Dockerfile Normal file
View file

@ -0,0 +1,23 @@
FROM schmelczera/error-pages as build-error-pages
RUN python build.py 403 404 50x
FROM node:latest as build-webpage
WORKDIR /home/node
COPY src src
COPY package.json custom.d.ts tsconfig.json webpack.config.js ./
RUN npm install
RUN npm run build
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
RUN rm -rf *
COPY --from=build-webpage /home/node/dist .
COPY --from=build-error-pages /home/python/built errors
RUN find . -type f | xargs gzip -k9 &&\
chmod -R 555 .
COPY nginx-config /etc/nginx/

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# Photos
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/hu/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/2.5/hu/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.5/hu/">Creative Commons Attribution-NonCommercial-ShareAlike 2.5 Hungary License</a>.

16
custom.d.ts vendored Normal file
View file

@ -0,0 +1,16 @@
declare module '*.svg' {
const content: string;
export default content;
}
declare module '*.jpg' {
import { ResponsiveImage } from 'src/model/responsive-image';
const content: ResponsiveImage;
export default content;
}
declare module '*.jpeg' {
import { ResponsiveImage } from 'src/model/responsive-image';
const content: ResponsiveImage;
export default content;
}

6
desktop.ini Normal file
View file

@ -0,0 +1,6 @@
[.ShellClassInfo]
IconResource=D:\Projects\icons\camera.ico,0
[ViewState]
Mode=
Vid=
FolderType=Generic

34
docker-compose.yml Normal file
View file

@ -0,0 +1,34 @@
version: '3.8'
services:
photos:
init: true
image: schmelczera/photos
networks:
- network
ports:
- target: 80
protocol: tcp
mode: ingress
deploy:
replicas: 2
resources:
limits:
cpus: '0.5'
memory: 32M
reservations:
cpus: '0.2'
memory: 32M
placement:
max_replicas_per_node: 1
update_config:
parallelism: 1
failure_action: rollback
delay: 10s
monitor: 10s
restart_policy:
condition: on-failure
window: 30s
networks:
network:

55
nginx-config/nginx.conf Normal file
View file

@ -0,0 +1,55 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
sendfile_max_chunk 1m;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
gzip_static on;
gzip_vary on;
gzip_min_length 10240;
gzip_proxied any;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
location ~* \.(jpg|jpeg|png|ico)$ {
expires 30d;
}
error_page 403 /403.html;
error_page 404 /404.html;
error_page 500 501 502 503 504 /50x.html;
location ~ ^/(403|404|50x).html$ {
root /usr/share/nginx/html/errors;
internal;
}
}
}

BIN
og-image.jpg (Stored with Git LFS) Normal file

Binary file not shown.

61
package.json Normal file
View file

@ -0,0 +1,61 @@
{
"name": "portfolio",
"version": "1.0.0",
"description": "An easily configurable portfolio.",
"private": true,
"scripts": {
"start": "webpack-dev-server --mode development",
"build": "webpack && find dist -type f -regex \".*\\(js\\|css\\|txt\\)\" | xargs rm"
},
"repository": {
"type": "git",
"url": "git+https://github.com/schmelczerandras/timeline.git"
},
"keywords": [
"CV",
"portfolio",
"resume",
"resumé"
],
"author": "András Schmelczer",
"license": "UNLICENSED",
"bugs": {
"url": "https://github.com/schmelczerandras/timeline/issues"
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"defaults"
],
"homepage": "https://github.com/schmelczerandras/timeline#readme",
"devDependencies": {
"autoprefixer": "^9.8.6",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.6.0",
"cssnano": "latest",
"file-loader": "^5.1.0",
"html-webpack-inline-source-plugin": "0.0.10",
"html-webpack-plugin": "^3.2.0",
"image-webpack-loader": "^6.0.0",
"mini-css-extract-plugin": "^0.9.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0",
"prettier": "^1.19.1",
"resolve-url-loader": "^3.1.1",
"responsive-loader": "^1.2.0",
"sass": "^1.26.10",
"sass-loader": "^8.0.2",
"sharp": "^0.23.4",
"style-loader": "^1.2.1",
"svg-url-loader": "^3.0.3",
"terser-webpack-plugin": "^2.3.8",
"ts-loader": "^6.2.2",
"typescript": "^3.9.7",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
}
}

View file

@ -0,0 +1,22 @@
import { ResponsiveImage } from '../model/responsive-image';
import { last } from './last';
export const createImage = (
image: ResponsiveImage,
alt: string,
tabIndex: number,
imageScreenRatio = 0.25
): HTMLImageElement => {
const img = new Image(image.width, image.height);
img.tabIndex = tabIndex;
img.srcset = image.srcSet;
img.sizes =
image.images
.map(d => `(max-width: ${d.width / imageScreenRatio}px) ${d.width}px,`)
.join('\n') + `\n${last(image.images).width}px`;
img.src = last(image.images)?.path;
img.alt = alt;
return img;
};

2
src/helper/last.ts Normal file
View file

@ -0,0 +1,2 @@
export const last = <T>(list: ArrayLike<T>): T =>
list.length >= 1 ? list[list.length - 1] : undefined;

24
src/index.html Normal file
View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Photos - András Schmelczer</title>
<meta name="description" content="A very simple page where I showcase some of my photos. Although I am not a professional photographer, I truly love capturing joyous moments.">
<meta name="theme-color" content="#A63446">
<meta name="viewport" content="initial-scale=1.0" />
<link href="https://fonts.googleapis.com/css?family=Roboto:100" rel="stylesheet">
</head>
<body>
<header>
<h1>András Schmelczer</h1>
</header>
<main id="images">
<section id="portraits" class="cluster"></section>
<div id="frame-container">
<img src="" alt="opened photo" id="frame-image"/>
</div>
<section id="landscapes" class="cluster"></section>
</main>
</body>
</html>

239
src/index.scss Normal file
View file

@ -0,0 +1,239 @@
@mixin center-children() {
display: flex;
justify-content: center;
align-items: center;
}
@mixin portrait() {
@media (max-width: 900px) {
@content;
}
}
@mixin landscape() {
@media (min-width: 900px) {
@content;
}
}
$text-color: #ffffff;
$accent-color: #a63446;
$background-color: #eeeeee;
$border-width: 5px;
$normal-margin: 24px;
$small-margin: 6px;
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:focus {
outline: $accent-color solid $border-width * 1.1;
outline-offset: -$border-width * 1.1;
}
img {
border: $border-width solid $text-color;
box-sizing: content-box;
width: 100%;
height: auto;
display: block;
}
html {
height: 100%;
overflow-x: hidden;
background-color: $background-color;
body {
display: flex;
overflow: hidden;
@include portrait {
height: 100%;
flex-direction: column;
}
@include landscape {
&::-webkit-scrollbar-track,
&::-webkit-scrollbar {
background-color: transparent;
width: 12px;
}
&::-webkit-scrollbar-thumb {
background-color: $accent-color;
border-radius: 1000px;
}
}
h1,
header {
font-family: 'Roboto', serif;
font-weight: 100;
@include landscape {
font-size: 3rem;
}
@include portrait {
font-size: 2rem;
}
}
header {
@include center-children;
box-sizing: content-box;
height: 100%;
position: relative;
@include portrait {
width: 100%;
height: 2.15ch;
padding: $normal-margin 0;
}
@include landscape {
width: 2.15ch;
height: 100vh;
padding: 0 $normal-margin;
}
h1 {
color: $text-color;
background-color: $accent-color;
padding: $normal-margin 0;
white-space: nowrap;
user-select: none;
z-index: 2;
position: fixed;
text-align: center;
@include portrait {
width: 100%;
}
@include landscape {
width: 101vh;
transform: rotate(-90deg);
}
}
}
main#images {
flex: 1;
display: flex;
justify-content: space-between;
@include portrait {
flex-direction: column;
}
.cluster {
display: flex;
justify-content: space-between;
img {
cursor: pointer;
}
&#portraits > img {
@include portrait {
height: 12vmin;
width: auto;
}
@include landscape {
width: 8vmin;
}
}
&#landscapes > img {
@include portrait {
height: 8vmin;
width: auto;
}
@include landscape {
width: 12vmin;
}
}
img {
@include portrait {
margin: 0 $small-margin;
}
}
@include portrait {
width: 100%;
overflow: auto;
margin: $small-margin 0;
padding: $small-margin 0;
object-fit: contain;
:first-child {
margin-left: 2 * $small-margin;
}
&:last-child {
margin-right: 2 * $small-margin !important;
}
}
@include landscape {
flex-direction: column;
margin: $normal-margin;
img {
margin: $small-margin 0;
}
:first-child {
margin-top: 0;
}
:last-child {
margin-bottom: 0;
}
}
}
#frame-container {
@include center-children;
margin: 0 $border-width;
flex: 1;
@include landscape {
}
@include portrait {
position: relative;
}
img {
height: auto;
width: auto;
top: 50%;
transform: translateY(-50%) translateX(-50%);
@include portrait {
position: absolute;
box-sizing: border-box;
max-width: 100% !important; // undo js
max-height: 100%;
left: 50% !important; // undo js
}
@include landscape {
position: fixed;
max-height: 90vh;
}
}
}
}
}
}

33
src/index.ts Normal file
View file

@ -0,0 +1,33 @@
import './index.scss';
import { Photos } from './photos';
// @ts-ignore
const images = require.context('./pictures', true, /.jpg$/);
const imagePath = name => images(name, true);
const addSupportForTabNavigation = () =>
(document.onkeydown = e => {
if (e.key === ' ') {
(document.activeElement as HTMLElement)?.click();
e.preventDefault();
}
});
const sizeFrame = () => {
const container = document.querySelector('#frame-container') as HTMLElement;
const image = container.querySelector('img');
image.style.maxWidth = container.clientWidth + 'px';
image.style.left = container.offsetLeft + container.clientWidth / 2 + 'px';
};
new Photos(
images.keys().map(k => imagePath(k)),
document.querySelector('#landscapes'),
document.querySelector('#portraits'),
document.querySelector('#frame-image')
);
addSupportForTabNavigation();
window.addEventListener('resize', sizeFrame);
sizeFrame();

View file

@ -0,0 +1,12 @@
export type ResponsiveImage = {
srcSet: string;
src: string;
placeholder: string;
width: number;
height: number;
images: Array<{
path: string;
width: number;
height: number;
}>;
};

109
src/photos.ts Normal file
View file

@ -0,0 +1,109 @@
import { ResponsiveImage } from './model/responsive-image';
import { createImage } from './helper/create-image';
export class Photos {
private readonly images: Array<HTMLImageElement>;
private _selectedId: number;
private timerId: NodeJS.Timeout = null;
private shouldNotChange = false;
public constructor(
images: Array<ResponsiveImage>,
private readonly landscapesContainer: HTMLElement,
private readonly portraitsContainer: HTMLElement,
private readonly pictureFrame: HTMLImageElement
) {
this.images = this.convertImages(images);
this.placeImages();
this.addEventListeners();
this.startAutoChange();
this.selectedId = 0;
}
private get selectedId(): number {
return this._selectedId;
}
private set selectedId(value: number) {
if (value < 0) {
value += this.images.length - 1;
}
this._selectedId = value % this.images.length;
this.loadToFrame(this.images[this.selectedId]);
this.images[this.selectedId].focus();
this.shouldNotChange = true;
}
private convertImages(images: Array<ResponsiveImage>) {
let portraits = 0; // evens
let landscapes = 1; // odds
const imgs = images.map((image, i) =>
createImage(
image,
'a photo',
Photos.isLandscape(image) ? (landscapes += 2) : (portraits += 2)
)
);
return imgs.sort((a, b) => a.tabIndex - b.tabIndex);
}
private placeImages() {
this.images.forEach(img => {
if (Photos.isLandscape(img)) {
img.classList.add('landscape');
this.landscapesContainer.appendChild(img);
} else {
img.classList.add('portrait');
this.portraitsContainer.appendChild(img);
}
});
}
private addEventListeners() {
window.document.addEventListener('keydown', e => {
switch (e.key) {
case 'ArrowLeft':
this.selectedId--;
break;
case 'ArrowRight':
this.selectedId++;
break;
case 'Tab':
if (this.timerId !== null) {
clearInterval(this.timerId);
this.timerId = null;
}
}
});
window.document.addEventListener(
'scroll',
() => (this.shouldNotChange = true)
);
this.images.forEach((image, i) =>
image.addEventListener('click', () => (this.selectedId = i))
);
}
private startAutoChange() {
this.timerId = setInterval(() => {
if (!this.shouldNotChange) {
this.selectedId++;
}
this.shouldNotChange = false;
}, 7000);
}
private loadToFrame(img: HTMLImageElement) {
this.pictureFrame.src = img.src;
}
private static isLandscape(
image: HTMLImageElement | ResponsiveImage
): boolean {
return image.width > image.height;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

BIN
src/pictures/_DSC1484.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 MiB

BIN
src/pictures/_DSC1834.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
src/pictures/_DSC2313.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
src/pictures/_DSC2362.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

BIN
src/pictures/_DSC2674.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
src/pictures/_DSC2692.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 MiB

BIN
src/pictures/adam.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
src/pictures/dorka-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 MiB

BIN
src/pictures/nap-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 MiB

BIN
src/pictures/web-1-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

BIN
src/pictures/web-1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

BIN
src/pictures/web-10.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

BIN
src/pictures/web-11.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

BIN
src/pictures/web-12.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

BIN
src/pictures/web-13.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

BIN
src/pictures/web-14.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
src/pictures/web-17.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

BIN
src/pictures/web-18.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

BIN
src/pictures/web-19.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

BIN
src/pictures/web-2-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

BIN
src/pictures/web-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

BIN
src/pictures/web-20.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
src/pictures/web-22.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

BIN
src/pictures/web-23.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

BIN
src/pictures/web-24.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

BIN
src/pictures/web-25.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
src/pictures/web-26.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

BIN
src/pictures/web-3-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 KiB

BIN
src/pictures/web-3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

BIN
src/pictures/web-4-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

BIN
src/pictures/web-42.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
src/pictures/web-5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
src/pictures/web-6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 KiB

BIN
src/pictures/web-7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 KiB

BIN
src/pictures/web-8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 KiB

11
tsconfig.json Normal file
View file

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

174
webpack.config.js Normal file
View file

@ -0,0 +1,174 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
const Sharp = require('responsive-loader/sharp');
const Sass = require('sass');
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
watchOptions: {
ignored: /node_modules/,
},
devServer: {
host: '0.0.0.0',
disableHostCheck: true,
},
optimization: {
minimizer: [
new TerserJSPlugin({
sourceMap: !isProduction,
}),
new OptimizeCSSAssetsPlugin({}),
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
xhtml: true,
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
},
inlineSource: '.(js|css)$',
}),
new HtmlWebpackInlineSourcePlugin(),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css',
}),
],
entry: {
index: './src/index.ts',
},
module: {
rules: [
{
test: /\.(jpe?g|png)$/i,
loader: 'responsive-loader',
options: {
adapter: Sharp,
quality: 90,
outputPath: 'static/',
sizes: [300, 800, 1200, 2000],
placeholder: false,
},
},
{
test: /\.(webm|mp4|gif)$/i,
use: [
{
loader: 'file-loader',
query: {
outputPath: 'static/',
},
},
{
loader: 'image-webpack-loader',
options: {
disable: !isProduction,
mozjpeg: {
progressive: true,
quality: 65,
},
optipng: {
enabled: true,
},
pngquant: {
quality: [0.65, 0.9],
speed: 4,
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 65,
},
},
},
],
},
{
test: /\.svg$/,
loader: 'svg-url-loader',
options: {
limit: 10 * 1024,
noquotes: true,
},
},
{
test: /\.(pdf)$/i,
use: {
loader: 'file-loader',
query: {
outputPath: 'static/',
name: '[name].[ext]',
},
},
},
{
test: /\.ico$/i,
use: {
loader: 'file-loader',
query: {
outputPath: '/',
name: '[name].[ext]',
},
},
},
{
test: /\.scss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
{
loader: 'resolve-url-loader',
options: {
keepQuery: true,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
implementation: Sass,
},
},
],
},
{
test: /\.(woff2?|ttf|eot|svg)(?:[?#].+)?$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'static/fonts/',
},
},
include: /fonts/,
},
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.ts', '.js'],
},
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
},
};