Move to TypeScript

This commit is contained in:
Luca Bosin
2023-08-25 13:16:49 +02:00
parent c3e571fcb8
commit dc728b86fe
27 changed files with 160 additions and 246 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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];

View File

@ -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;

View 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';

View File

@ -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;

View File

@ -0,0 +1 @@
export default 'M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z';

View File

@ -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
View 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;
}

View File

@ -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
View 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) :''}`;
}

View File

@ -1,4 +1,4 @@
<script>
<script type="ts">
import { page } from '$app/stores';
</script>

View File

@ -1,4 +1,4 @@
<script>
<script type="ts">
import "$lib/styles/base.css";
</script>

View File

@ -1,4 +1,4 @@
<script>
<script type="ts">
</script>

View File

@ -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);

View File

@ -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 ? `&timestamp=${data.timestamp}` : '');
export let data: PageData;
</script>
<Header title={$strf(data.album.title)}/>

View File

@ -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);

View File

@ -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);

View File

@ -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">

View File

@ -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;

View File

@ -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');
}

View File

@ -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 {

View File

@ -1,4 +1,4 @@
<script>
<script type="ts">
import { Player, Video, DefaultUi } from '@vime/svelte';
</script>