Move to TypeScript
This commit is contained in:
@ -1,12 +1,13 @@
|
||||
<script>
|
||||
<script type="ts">
|
||||
import Photo from './Photo.svelte';
|
||||
|
||||
/** @type {Album} */
|
||||
export let album;
|
||||
/** The album. */
|
||||
export let album: Album;
|
||||
|
||||
/** @type {Item[]?} */
|
||||
export let items = null;
|
||||
/** The items to show. Set to `null` (or leave unset) to use all items of the album. */
|
||||
export let items: Item[] | null = null;
|
||||
|
||||
/** Base URL */
|
||||
export let base = '';
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
<script>
|
||||
<script type="ts">
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
export let title = 'Galerie';
|
||||
export let description = '';
|
||||
/** @type {string | undefined} */
|
||||
export let back = undefined;
|
||||
export let back: string | undefined = undefined;
|
||||
</script>
|
||||
|
||||
<header>
|
||||
|
||||
@ -1,30 +1,28 @@
|
||||
<script>
|
||||
<script type="ts">
|
||||
/** Class name for additional CSS styling */
|
||||
export let clazz = '';
|
||||
export { clazz as class };
|
||||
|
||||
/** Icon color. */
|
||||
export let color = 'currentColor';
|
||||
|
||||
/** @type {string} */
|
||||
export let mdi;
|
||||
/** Material Design Icons icon name */
|
||||
export let mdi: string;
|
||||
|
||||
/** @type {number | string} */
|
||||
export let size = 1;
|
||||
/** Icon size. Numbers are treated as `<value>em`, strings as `<value>`. */
|
||||
export let size: number | string = 1;
|
||||
|
||||
let mdiOld = '';
|
||||
let path = '';
|
||||
|
||||
$: {
|
||||
loadIcon(mdi);
|
||||
}
|
||||
$: loadIcon(mdi);
|
||||
|
||||
$: width = size ? (typeof size === 'number' || !Number.isNaN(Number(size)) ? `${size}em` : size) : '1em';
|
||||
|
||||
/**
|
||||
* @param {string} mdi
|
||||
*/
|
||||
async function loadIcon(mdi) {
|
||||
/** Load the icon from the `$lib/icons` folder. */
|
||||
async function loadIcon(mdi: string) {
|
||||
if (mdi === mdiOld) return;
|
||||
path = (await import(`$lib/icons/${mdi}.js`)).default;
|
||||
//path = icon.path;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,43 +1,28 @@
|
||||
<script>
|
||||
<script type="ts">
|
||||
import { strf } from "$lib/data/language";
|
||||
import { getFileName } from "$lib/util/links";
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
/**
|
||||
* The source of the image.
|
||||
* @type {string}
|
||||
*/
|
||||
export let src;
|
||||
/** The source of the image. */
|
||||
export let src: string;
|
||||
|
||||
/** @type {Album} */
|
||||
export let album;
|
||||
/** The album. */
|
||||
export let album: Album;
|
||||
|
||||
/**
|
||||
* The item.
|
||||
* @type {Item}
|
||||
*/
|
||||
export let item;
|
||||
/** The item. */
|
||||
export let item: Item;
|
||||
|
||||
/**
|
||||
* Whether the image is the first one in the viewport.
|
||||
* @type {boolean}
|
||||
*/
|
||||
/** Whether the image is the first one in the viewport. */
|
||||
export let lazy = false;
|
||||
|
||||
/**
|
||||
* The click handler for the image.
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
async function clickHandler(event) {
|
||||
/** The click handler for the image. */
|
||||
async function clickHandler(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
console.log('click', event);
|
||||
}
|
||||
|
||||
/**
|
||||
* The right click handler for the image.
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
async function rightClickHandler(event) {
|
||||
/** The right click handler for the image. */
|
||||
async function rightClickHandler(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
console.log('right click', event);
|
||||
}
|
||||
@ -83,22 +68,24 @@
|
||||
height: fit-content;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
min-width: 30vw;
|
||||
min-width: 28.125em
|
||||
}
|
||||
|
||||
figure {
|
||||
width: 30vw;
|
||||
height: 20vw;
|
||||
width: 28.125em;
|
||||
height: 18.75em;
|
||||
background-color: rgba(0,0,0,.25);
|
||||
background-image: var(--image);
|
||||
background-image: linear-gradient(rgba(0,0,0,.125), rgba(0,0,0,.25)), var(--image);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 30vw;
|
||||
height: 20vw;
|
||||
position: absolute;
|
||||
width: 28.125em;
|
||||
height: 18.75em;
|
||||
object-fit: contain;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
import { writable, derived } from "svelte/store";
|
||||
import { writable, derived, type Writable } from "svelte/store";
|
||||
|
||||
/** @type {import('svelte/store').Writable<TranslationKey>} */
|
||||
export const language = writable('de');
|
||||
export const language: Writable<TranslationKey> = writable('de');
|
||||
|
||||
/**
|
||||
* @type {Record<TranslationKey, Record<String, String>>}
|
||||
*/
|
||||
const translations = {
|
||||
const translations: Record<TranslationKey, Record<string, string>> = {
|
||||
de: {
|
||||
'gallery': 'Galerie',
|
||||
'album': 'Album',
|
||||
@ -42,11 +38,7 @@ const translations = {
|
||||
};
|
||||
|
||||
export const str = derived(language, $language => {
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {...any} args
|
||||
*/
|
||||
function translate(key, ...args) {
|
||||
function translate(key: string, ...args: any[]) {
|
||||
const str = translations[$language][key];
|
||||
if (str === undefined) return key;
|
||||
return str.replace(/\{(\d+)\}/g, (_, i) => args[i]);
|
||||
@ -55,11 +47,7 @@ export const str = derived(language, $language => {
|
||||
});
|
||||
|
||||
export const strf = derived(language, $language => {
|
||||
/**
|
||||
* @param {Translation | string} translations
|
||||
* @param {...any} args
|
||||
*/
|
||||
function translate(translations, ...args) {
|
||||
function translate(translations: Translation | string, ...args: any[]) {
|
||||
if (translations === undefined) return undefined;
|
||||
if (typeof translations === 'string') return translations;
|
||||
const str = translations[$language];
|
||||
@ -1,2 +0,0 @@
|
||||
const path = 'M10,21V19H6.41L10.91,14.5L9.5,13.09L5,17.59V14H3V21H10M14.5,10.91L19,6.41V10H21V3H14V5H17.59L13.09,9.5L14.5,10.91Z';
|
||||
export default path;
|
||||
1
src/lib/icons/arrow-expand.ts
Normal file
1
src/lib/icons/arrow-expand.ts
Normal file
@ -0,0 +1 @@
|
||||
export default 'M10,21V19H6.41L10.91,14.5L9.5,13.09L5,17.59V14H3V21H10M14.5,10.91L19,6.41V10H21V3H14V5H17.59L13.09,9.5L14.5,10.91Z';
|
||||
@ -1,2 +0,0 @@
|
||||
const path = 'M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z';
|
||||
export default path;
|
||||
1
src/lib/icons/arrow-left.ts
Normal file
1
src/lib/icons/arrow-left.ts
Normal file
@ -0,0 +1 @@
|
||||
export default 'M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z';
|
||||
@ -1,103 +0,0 @@
|
||||
import { error } from "@sveltejs/kit";
|
||||
import StreamZip from "node-stream-zip";
|
||||
|
||||
/** @param {string} zipName */
|
||||
async function _getZip(zipName) {
|
||||
let zip = null;
|
||||
try {
|
||||
zip = new StreamZip.async({ file: `./zip/${zipName}` });
|
||||
} catch (err) {
|
||||
console.error(`${err.stack}`.replaceAll('/home/sveltekit', '.'));
|
||||
throw error(404, `Album not found.`);
|
||||
}
|
||||
return zip;
|
||||
}
|
||||
/** @param {import('node-stream-zip').StreamZipAsync} zip @param {string} entryName */
|
||||
async function _getFile(zip, entryName) {
|
||||
return await zip.entryData(entryName);
|
||||
}
|
||||
/** @param {import('node-stream-zip').StreamZipAsync} zip */
|
||||
async function _getMetadata(zip) {
|
||||
const metadataContent = await _getFile(zip, 'album.json');
|
||||
return JSON.parse(metadataContent.toString());
|
||||
}
|
||||
/** @param {import('node-stream-zip').StreamZipAsync} zip @param {boolean} keepOpen */
|
||||
async function _close(zip, keepOpen) {
|
||||
if (!keepOpen)
|
||||
await zip.close();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} zipName
|
||||
* @param {boolean} keepOpen
|
||||
* @returns {Promise<{album: Album, zip: import('node-stream-zip').StreamZipAsync}>}
|
||||
*/
|
||||
export async function getMetadataOpen(zipName, keepOpen = true) {
|
||||
const zip = await _getZip(zipName);
|
||||
const album = await _getMetadata(zip);
|
||||
await _close(zip, keepOpen);
|
||||
return { album, zip };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} zipName
|
||||
* @returns {Promise<Album>}
|
||||
*/
|
||||
export async function getMetadata(zipName) {
|
||||
return (await getMetadataOpen(zipName, false)).album;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} zipName
|
||||
* @param {string} entryName
|
||||
* @param {boolean} keepOpen
|
||||
* @returns {Promise<{album: Album, content: Buffer, zip: import('node-stream-zip').StreamZipAsync}>}
|
||||
*/
|
||||
export async function getMetadataAndFileOpen(zipName, entryName, keepOpen = true) {
|
||||
const zip = await _getZip(zipName);
|
||||
const album = await _getMetadata(zip);
|
||||
const content = await _getFile(zip, entryName);
|
||||
await _close(zip, keepOpen);
|
||||
return { album, content, zip };
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} zipName
|
||||
* @param {string} entryName
|
||||
* @returns {Promise<{album: Album, content: Buffer}>}
|
||||
*/
|
||||
export async function getMetadataAndFile(zipName, entryName) {
|
||||
const { album, content } = await getMetadataAndFileOpen(zipName, entryName, false);
|
||||
return { album, content };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} zipName
|
||||
* @param {string} entryName
|
||||
* @param {boolean} keepOpen
|
||||
* @returns {Promise<{content: Buffer, zip: import('node-stream-zip').StreamZipAsync}>}
|
||||
*/
|
||||
export async function getFileOpen(zipName, entryName, keepOpen = true) {
|
||||
const zip = await _getZip(zipName);
|
||||
let content = null;
|
||||
try {
|
||||
console.log(`Getting ${entryName} from ${zipName}`);
|
||||
content = await zip.entryData(entryName);
|
||||
} catch (err) {
|
||||
console.error(`${err.stack}`.replaceAll('/home/sveltekit', '.'));
|
||||
throw error(404, `File ${entryName} not found.`);
|
||||
}
|
||||
await _close(zip, keepOpen);
|
||||
return { content, zip };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} zipName
|
||||
* @param {string} entryName
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
export async function getFile(zipName, entryName) {
|
||||
return (await getFileOpen(zipName, entryName, false)).content;
|
||||
}
|
||||
66
src/lib/util/album.ts
Normal file
66
src/lib/util/album.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { error } from "@sveltejs/kit";
|
||||
import StreamZip, { StreamZipAsync } from "node-stream-zip";
|
||||
|
||||
async function _getZip(zipName: string) {
|
||||
let zip = null;
|
||||
try {
|
||||
zip = new StreamZip.async({ file: `./zip/${zipName}` });
|
||||
} catch (err: any) {
|
||||
console.error(`${err.stack}`.replaceAll('/home/sveltekit', '.'));
|
||||
throw error(404, `Album not found.`);
|
||||
}
|
||||
return zip;
|
||||
}
|
||||
async function _getFile(zip: StreamZipAsync, entryName: string) {
|
||||
return await zip.entryData(entryName);
|
||||
}
|
||||
async function _getMetadata(zip: StreamZipAsync) {
|
||||
const metadataContent = await _getFile(zip, 'album.json');
|
||||
return JSON.parse(metadataContent.toString());
|
||||
}
|
||||
async function _close(zip: StreamZipAsync, keepOpen: boolean) {
|
||||
if (!keepOpen)
|
||||
await zip.close();
|
||||
}
|
||||
|
||||
export async function getMetadataOpen(zipName: string, keepOpen = true): Promise<{ album: Album, zip: StreamZipAsync }> {
|
||||
const zip = await _getZip(zipName);
|
||||
const album = await _getMetadata(zip);
|
||||
await _close(zip, keepOpen);
|
||||
return { album, zip };
|
||||
}
|
||||
|
||||
export async function getMetadata(zipName: string): Promise<Album> {
|
||||
return (await getMetadataOpen(zipName, false)).album;
|
||||
}
|
||||
|
||||
export async function getMetadataAndFileOpen(zipName: string, entryName: string, keepOpen = true): Promise<{ album: Album, content: Buffer, zip: StreamZipAsync }> {
|
||||
const zip = await _getZip(zipName);
|
||||
const album = await _getMetadata(zip);
|
||||
const content = await _getFile(zip, entryName);
|
||||
await _close(zip, keepOpen);
|
||||
return { album, content, zip };
|
||||
}
|
||||
|
||||
export async function getMetadataAndFile(zipName: string, entryName: string): Promise<{ album: Album, content: Buffer }> {
|
||||
const { album, content } = await getMetadataAndFileOpen(zipName, entryName, false);
|
||||
return { album, content };
|
||||
}
|
||||
|
||||
export async function getFileOpen(zipName: string, entryName: string, keepOpen = true): Promise<{ content: Buffer, zip: StreamZipAsync }> {
|
||||
const zip = await _getZip(zipName);
|
||||
let content = null;
|
||||
try {
|
||||
console.log(`Getting ${entryName} from ${zipName}`);
|
||||
content = await zip.entryData(entryName);
|
||||
} catch (err: any) {
|
||||
console.error(`${err.stack}`.replaceAll('/home/sveltekit', '.'));
|
||||
throw error(404, `File ${entryName} not found.`);
|
||||
}
|
||||
await _close(zip, keepOpen);
|
||||
return { content, zip };
|
||||
}
|
||||
|
||||
export async function getFile(zipName: string, entryName: string): Promise<Buffer> {
|
||||
return (await getFileOpen(zipName, entryName, false)).content;
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
export function safe(str) {
|
||||
return str.replace(/[^\w.-]/gi, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getFileName(str) {
|
||||
return safe(str.split('/').pop() || '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getFilePath(str) {
|
||||
console.log(`getFilePath(${str})`);
|
||||
return str.split('/').map(safe).join('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} params
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getZipName(params) {
|
||||
const { slug, timestamp } = params;
|
||||
return `${safe(slug)}${timestamp ? '-' + safe(timestamp) :''}.zip`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} params
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getAlbumUri(params) {
|
||||
const { slug, timestamp } = params;
|
||||
return `/g/${safe(slug)}${timestamp ? '/' + safe(timestamp) :''}`;
|
||||
}
|
||||
22
src/lib/util/links.ts
Normal file
22
src/lib/util/links.ts
Normal file
@ -0,0 +1,22 @@
|
||||
export function safe(str: string): string {
|
||||
return str.replace(/[^\w.-]/gi, '');
|
||||
}
|
||||
|
||||
export function getFileName(str: string): string {
|
||||
return safe(str.split('/').pop() || '');
|
||||
}
|
||||
|
||||
export function getFilePath(str: string): string {
|
||||
console.log(`getFilePath(${str})`);
|
||||
return str.split('/').map(safe).join('/');
|
||||
}
|
||||
|
||||
export function getZipName(params: any): string {
|
||||
const { slug, timestamp } = params;
|
||||
return `${safe(slug)}${timestamp ? '-' + safe(timestamp) :''}.zip`;
|
||||
}
|
||||
|
||||
export function getAlbumUri(params: any): string {
|
||||
const { slug, timestamp } = params;
|
||||
return `/g/${safe(slug)}${timestamp ? '/' + safe(timestamp) :''}`;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script type="ts">
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script type="ts">
|
||||
import "$lib/styles/base.css";
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script type="ts">
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { getMetadata } from '$lib/util/album';
|
||||
import { getAlbumUri, getZipName } from '$lib/util/links';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export async function load({ params }) {
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const album = await getMetadata(getZipName(params));
|
||||
const base = getAlbumUri(params);
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
<script>
|
||||
<script type="ts">
|
||||
import { strf } from '$lib/data/language.js';
|
||||
|
||||
import Header from '$lib/components/Header.svelte';
|
||||
import Gallery from '$lib/components/Gallery.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
|
||||
//const uriBase = `/s/apitest.php?slug=${data.slug}` + (data.timestamp ? `×tamp=${data.timestamp}` : '');
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<Header title={$strf(data.album.title)}/>
|
||||
|
||||
@ -2,9 +2,9 @@ import { getMetadata } from '$lib/util/album';
|
||||
import { getZipName } from '$lib/util/links';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import fs from 'node:fs/promises';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
/** @type {import('./$types').RequestHandler} */
|
||||
export async function GET({ params, url }) {
|
||||
export const GET: RequestHandler = async ({ params, url }) => {
|
||||
try {
|
||||
const zipName = getZipName(params);
|
||||
const album = await getMetadata(zipName);
|
||||
@ -1,8 +1,8 @@
|
||||
import { getMetadata } from '$lib/util/album';
|
||||
import { getFileName, getFilePath, getZipName } from '$lib/util/links.js';
|
||||
import { getFileName, getFilePath, getZipName } from '$lib/util/links';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export async function load({ params }) {
|
||||
export const load: PageServerLoad = async ({ params }) => {
|
||||
const album = await getMetadata(getZipName(params));
|
||||
const filePath = getFilePath(params.item);
|
||||
const item = album.items.find(item => item.item === filePath);
|
||||
@ -1,8 +1,8 @@
|
||||
<script>
|
||||
<script type="ts">
|
||||
import { strf } from '$lib/data/language';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { getMetadataAndFile } from '$lib/util/album';
|
||||
import { getZipName, getFileName, getFilePath } from '$lib/util/links';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
/** @type {import('./$types').RequestHandler} */
|
||||
export async function GET({ params }) {
|
||||
export const GET: RequestHandler = async ({ params }) => {
|
||||
try {
|
||||
const {album, content} = await getMetadataAndFile(getZipName(params), getFilePath(params.item));
|
||||
const allowDownload = album.allowDownload === false ? false : true;
|
||||
@ -2,9 +2,10 @@ import { getFile } from '$lib/util/album';
|
||||
import { getFileName, getFilePath, getZipName } from '$lib/util/links';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import sharp from 'sharp';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
/** @type {import('./$types').RequestHandler} */
|
||||
export async function GET({ params }) {
|
||||
/** @type {RequestHandler} */
|
||||
export const GET: RequestHandler = async ({ params }) => {
|
||||
let thumbnail = null;
|
||||
console.log(`Getting thumbnail for ${params}`);
|
||||
let width = 400;
|
||||
@ -24,7 +25,7 @@ export async function GET({ params }) {
|
||||
}
|
||||
}
|
||||
thumbnail = thumbnail || await sharp(content).resize(width).webp({ quality: 90 }).toBuffer();
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
console.error(`${/** @type {Error} */(err).stack}`.replaceAll('/home/sveltekit', '.'));
|
||||
throw error(500, 'Error getting thumbnail');
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
import StreamZip from 'node-stream-zip';
|
||||
import sharp from 'sharp';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
/** @type {import('./$types').RequestHandler} */
|
||||
export async function GET({ params }) {
|
||||
/** @type {RequestHandler} */
|
||||
export const GET: RequestHandler = async ({ params }) => {
|
||||
|
||||
let entryData = null;
|
||||
try {
|
||||
@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script type="ts">
|
||||
import { Player, Video, DefaultUi } from '@vime/svelte';
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user