Clean up
|
|
@ -7,12 +7,15 @@ on:
|
||||||
branches: ['main']
|
branches: ['main']
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: 'pages'
|
group: 'photos-${{ github.ref }}'
|
||||||
cancel-in-progress: false
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
validate:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
@ -21,18 +24,46 @@ jobs:
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '12'
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: npm ci
|
||||||
npm install
|
|
||||||
|
- name: Check
|
||||||
|
run: npm run check
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: npm run build
|
||||||
npm run build
|
|
||||||
|
|
||||||
- name: Copy files to nginx pages mount
|
- name: Install Playwright browser
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
run: npx playwright install --with-deps chromium
|
||||||
|
|
||||||
|
- name: End-to-end smoke test
|
||||||
|
run: npm run test:e2e
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
runs-on: docker
|
||||||
|
needs: validate
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Copy build to host pages mount
|
||||||
run: |
|
run: |
|
||||||
apt update && apt install -y rsync
|
apt update && apt install -y rsync
|
||||||
rsync -a --delete --mkpath --chmod=ugo=rwx dist/ /pages/photos/
|
mkdir -p /pages/photos
|
||||||
|
rsync -a --delete dist/ /pages/photos/
|
||||||
|
|
|
||||||
1
.gitattributes
vendored
|
|
@ -1 +0,0 @@
|
||||||
og-image.jpg filter=lfs diff=lfs merge=lfs -text
|
|
||||||
5
.gitignore
vendored
|
|
@ -1,5 +1,8 @@
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
target
|
target
|
||||||
package-lock.json
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
public/static/photos
|
||||||
|
src/generated
|
||||||
|
playwright-report
|
||||||
|
test-results
|
||||||
|
|
|
||||||
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
22.13.0
|
||||||
26
custom.d.ts
vendored
|
|
@ -1,26 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.txt' {
|
|
||||||
const content: string;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '*.html' {
|
|
||||||
const content: string;
|
|
||||||
export default content;
|
|
||||||
}
|
|
||||||
38
eslint.config.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
ignores: [
|
||||||
|
'dist/**',
|
||||||
|
'public/static/photos/**',
|
||||||
|
'src/generated/**',
|
||||||
|
'playwright-report/**',
|
||||||
|
'test-results/**',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
js.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tseslint.parser,
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-undef': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.js', '**/*.mjs'],
|
||||||
|
languageOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
BIN
og-image.jpg
|
Before Width: | Height: | Size: 131 B After Width: | Height: | Size: 243 KiB |
5495
package-lock.json
generated
Normal file
BIN
public/og-image.jpg
Normal file
|
After Width: | Height: | Size: 243 KiB |
4
public/robots.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://schmelczer.dev/photos/sitemap.xml
|
||||||
15
public/site.webmanifest
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"name": "Photos - András Schmelczer",
|
||||||
|
"short_name": "Photos",
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#eeeeee",
|
||||||
|
"theme_color": "#a63446",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.svg",
|
||||||
|
"sizes": "64x64",
|
||||||
|
"type": "image/svg+xml"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
6
public/sitemap.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<url>
|
||||||
|
<loc>https://schmelczer.dev/photos/</loc>
|
||||||
|
</url>
|
||||||
|
</urlset>
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export const last = <T>(list: ArrayLike<T>): T =>
|
|
||||||
list.length >= 1 ? list[list.length - 1] : undefined;
|
|
||||||
44
src/index.ts
|
|
@ -1,35 +1,19 @@
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
import './no-change/404.html';
|
import { photos } from './generated/photos';
|
||||||
import './no-change/robots.txt';
|
import { PhotoGallery, requiredElement } from './photos';
|
||||||
import { Photos } from './photos';
|
|
||||||
|
|
||||||
// @ts-ignore
|
document.documentElement.classList.add('js');
|
||||||
const images = require.context('./pictures', true, /.jpg$/);
|
|
||||||
const imagePath = name => images(name, true);
|
|
||||||
|
|
||||||
const addSupportForTabNavigation = () =>
|
const gallery = requiredElement<HTMLElement>('#gallery', HTMLElement);
|
||||||
(document.onkeydown = e => {
|
const frame = requiredElement<HTMLElement>('#frame-content', HTMLElement);
|
||||||
if (e.key === ' ') {
|
const toggle = requiredElement<HTMLButtonElement>(
|
||||||
(document.activeElement as HTMLElement)?.click();
|
'#slideshow-toggle',
|
||||||
e.preventDefault();
|
HTMLButtonElement
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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();
|
new PhotoGallery({
|
||||||
window.addEventListener('resize', sizeFrame);
|
photos,
|
||||||
sizeFrame();
|
gallery,
|
||||||
|
frame,
|
||||||
|
toggle,
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
export type ResponsiveImage = {
|
|
||||||
srcSet: string;
|
|
||||||
src: string;
|
|
||||||
placeholder: string;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
images: Array<{
|
|
||||||
path: string;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
||||||
|
|
||||||
<title>Not found</title>
|
|
||||||
<meta name="theme-color" content="#b7455e" />
|
|
||||||
<meta name="viewport" content="initial-scale=1.0" />
|
|
||||||
<style>
|
|
||||||
html,
|
|
||||||
body {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background-color: #b7455e;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-family: "Roboto", "Helvetica Neue", sans-serif;
|
|
||||||
font-weight: 100;
|
|
||||||
font-size: 3rem;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>The requested resource cannot be found.</h1>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
User-agent: *
|
|
||||||
Allow: /
|
|
||||||
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
|
Before Width: | Height: | Size: 3 MiB After Width: | Height: | Size: 3 MiB |
|
Before Width: | Height: | Size: 2.6 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.5 MiB |
|
Before Width: | Height: | Size: 9.6 MiB After Width: | Height: | Size: 7.4 MiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 2.2 MiB After Width: | Height: | Size: 1.4 MiB |
|
Before Width: | Height: | Size: 6.7 MiB After Width: | Height: | Size: 4.5 MiB |
|
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 6.5 MiB After Width: | Height: | Size: 2 MiB |
|
Before Width: | Height: | Size: 7.6 MiB After Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 482 KiB |
|
Before Width: | Height: | Size: 281 KiB After Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 215 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 307 KiB After Width: | Height: | Size: 368 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 244 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 308 KiB |
|
Before Width: | Height: | Size: 321 KiB After Width: | Height: | Size: 346 KiB |
|
Before Width: | Height: | Size: 413 KiB After Width: | Height: | Size: 462 KiB |
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 429 KiB |
|
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 381 KiB After Width: | Height: | Size: 436 KiB |
|
Before Width: | Height: | Size: 356 KiB After Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 255 KiB |
|
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 369 KiB After Width: | Height: | Size: 437 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 820 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 136 KiB |
|
Before Width: | Height: | Size: 465 KiB After Width: | Height: | Size: 572 KiB |
|
Before Width: | Height: | Size: 670 KiB After Width: | Height: | Size: 691 KiB |
|
Before Width: | Height: | Size: 810 KiB After Width: | Height: | Size: 920 KiB |
|
|
@ -1,11 +1,26 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist/",
|
"target": "ES2022",
|
||||||
"noImplicitAny": false,
|
"useDefineForClassFields": true,
|
||||||
"module": "es6",
|
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||||
"target": "es5",
|
"allowJs": false,
|
||||||
"sourceMap": true,
|
"skipLibCheck": true,
|
||||||
"allowJs": true,
|
"esModuleInterop": true,
|
||||||
"downlevelIteration": true
|
"allowSyntheticDefaultImports": true,
|
||||||
}
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"types": ["vite/client", "vitest/globals"]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.json",
|
||||||
|
"vite.config.ts",
|
||||||
|
"eslint.config.js"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||