Change many lines
This commit is contained in:
@ -1,11 +0,0 @@
|
||||
<script>
|
||||
|
||||
</script>
|
||||
|
||||
<section class="gallery">
|
||||
<slot />
|
||||
</section>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@ -1,29 +0,0 @@
|
||||
[
|
||||
{
|
||||
"slug": "sts",
|
||||
"title": "Testgalerie",
|
||||
"description": "Das ist eine Testgalerie",
|
||||
"timestamp": "2023-08-04T00:00:00.000Z",
|
||||
"place": "Berlin",
|
||||
"tags": [
|
||||
"Berlin",
|
||||
"Test"
|
||||
],
|
||||
"license": "CC BY-NC-ND 4.0",
|
||||
"items": "img.zip",
|
||||
"itemsMeta": [
|
||||
{
|
||||
"item": "cover.jpg",
|
||||
"title": "Cover",
|
||||
"description": "Das ist das Cover",
|
||||
"timestamp": "2023-08-04T00:00:00.000Z",
|
||||
"place": "Berlin",
|
||||
"tags": [
|
||||
"Berlin",
|
||||
"Cover"
|
||||
],
|
||||
"license": "CC BY-NC-ND 4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
7
src/lib/components/Footer.svelte
Normal file
7
src/lib/components/Footer.svelte
Normal file
@ -0,0 +1,7 @@
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
36
src/lib/components/Gallery.svelte
Normal file
36
src/lib/components/Gallery.svelte
Normal file
@ -0,0 +1,36 @@
|
||||
<script>
|
||||
import { strf } from '$lib/data/language.js';
|
||||
|
||||
function sortItems() {
|
||||
items.sort((a, b) => {
|
||||
if (a.item < b.item) return -1;
|
||||
if (a.item > b.item) return 1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {Item[]} */
|
||||
export let items = [];
|
||||
|
||||
export let base = '';
|
||||
</script>
|
||||
|
||||
<section class="gallery">
|
||||
<ul>
|
||||
{#each items as item (item.item)}
|
||||
<!-- <Photo src={`${uriBase}&item=${item.item}`} alt={$strf(item.title)} /> -->
|
||||
<li>
|
||||
<h3>{item.item}</h3>
|
||||
<p>
|
||||
<b>Title:</b> {$strf(item.title)}<br />
|
||||
<b>Description:</b> {#if item.description}{$strf(item.description)}{:else}<i>no description</i>{/if}
|
||||
</p>
|
||||
<img src={`${base}&item=${item.item}`} alt={$strf(item.title)} />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
20
src/lib/components/Header.svelte
Normal file
20
src/lib/components/Header.svelte
Normal file
@ -0,0 +1,20 @@
|
||||
<script>
|
||||
import Icon from "./Icon.svelte";
|
||||
|
||||
export let title = 'Galerie';
|
||||
export let description = '';
|
||||
/** @type {string | undefined} */
|
||||
export let back = undefined;
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<section class="title">
|
||||
{#if back}
|
||||
<a href={back}>
|
||||
<Icon name="arrow-left" />
|
||||
</a>
|
||||
{/if}
|
||||
<h1>{title}</h1>
|
||||
<p>{description}</p>
|
||||
</section>
|
||||
</header>
|
||||
@ -1,5 +1,6 @@
|
||||
<script>
|
||||
|
||||
export let name = '';
|
||||
name;
|
||||
</script>
|
||||
|
||||
|
||||
5
src/lib/data/album.js
Normal file
5
src/lib/data/album.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
const album = {error: "Not found"};
|
||||
/** @type {import('svelte/store').Writable<ApiError | Album>} */
|
||||
export default writable(album);
|
||||
69
src/lib/data/language.js
Normal file
69
src/lib/data/language.js
Normal file
@ -0,0 +1,69 @@
|
||||
import { writable, derived } from "svelte/store";
|
||||
|
||||
/** @type {import('svelte/store').Writable<TranslationKey>} */
|
||||
export const language = writable('de');
|
||||
|
||||
/**
|
||||
* @type {Record<TranslationKey, Record<String, String>>}
|
||||
*/
|
||||
const translations = {
|
||||
de: {
|
||||
'gallery': 'Galerie',
|
||||
'album': 'Album',
|
||||
'albums': 'Alben',
|
||||
'photo': 'Foto',
|
||||
'photos': 'Fotos',
|
||||
'video': 'Video',
|
||||
'videos': 'Videos',
|
||||
'back': 'Zurück',
|
||||
'small': 'Klein',
|
||||
'medium': 'Mittel',
|
||||
'large': 'Groß',
|
||||
'open': 'Öffnen',
|
||||
'download': 'Herunterladen',
|
||||
'download-all': 'Alle herunterladen',
|
||||
},
|
||||
en: {
|
||||
'gallery': 'Gallery',
|
||||
'album': 'Album',
|
||||
'albums': 'Albums',
|
||||
'photo': 'Photo',
|
||||
'photos': 'Photos',
|
||||
'video': 'Video',
|
||||
'videos': 'Videos',
|
||||
'back': 'Back',
|
||||
'small': 'Small',
|
||||
'medium': 'Medium',
|
||||
'large': 'Large',
|
||||
'open': 'Open',
|
||||
'download': 'Download',
|
||||
'download-all': 'Download all',
|
||||
}
|
||||
};
|
||||
|
||||
export const str = derived(language, $language => {
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {...any} args
|
||||
*/
|
||||
function translate(key, ...args) {
|
||||
const str = translations[$language][key];
|
||||
if (str === undefined) return key;
|
||||
return str.replace(/\{(\d+)\}/g, (_, i) => args[i]);
|
||||
}
|
||||
return translate;
|
||||
});
|
||||
|
||||
export const strf = derived(language, $language => {
|
||||
/**
|
||||
* @param {Translation | string} translations
|
||||
* @param {...any} args
|
||||
*/
|
||||
function translate(translations, ...args) {
|
||||
if (typeof translations === 'string') return translations;
|
||||
const str = translations[$language];
|
||||
if (str === undefined) return translations.de;
|
||||
return str.replace(/\{(\w+)\}/g, (_, i) => args[i]);
|
||||
}
|
||||
return translate;
|
||||
});
|
||||
73
src/lib/data/licenses.json
Normal file
73
src/lib/data/licenses.json
Normal file
@ -0,0 +1,73 @@
|
||||
[
|
||||
{
|
||||
"type": "cc0",
|
||||
"title": "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication",
|
||||
"text": {
|
||||
"de": "Sie sind berechtigt, dieses Werk zu kopieren, verändern, verbreiten und aufzuführen, selbst für kommerzielle Zwecke, <b>ohne um weitere Erlaubnis bitten zu müssen</b>.",
|
||||
"en": "You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission."
|
||||
},
|
||||
"url": "https://creativecommons.org/publicdomain/zero/1.0/"
|
||||
},
|
||||
{
|
||||
"type": "cc-by",
|
||||
"title": "Creative Commons Attribution 4.0 International (CC BY 4.0)",
|
||||
"text": {
|
||||
"de": "Sie sind berechtigt, dieses Werk zu kopieren, verändern, verbreiten und aufzuführen, selbst für kommerzielle Zwecke, <b>wenn Sie den Namen des Autors nennen</b>, die Lizenz erwähnen und ggf. angeben, ob Veränderungen vorgenommen wurden.",
|
||||
"en": "You can copy, modify, distribute and perform the work, even for commercial purposes, <b>if you give appropriate credit</b>, mention the license and indicate if changes were made."
|
||||
},
|
||||
"url": "https://creativecommons.org/licenses/by/4.0/"
|
||||
},
|
||||
{
|
||||
"type": "cc-by-sa",
|
||||
"title": "Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)",
|
||||
"text": {
|
||||
"de": "Sie sind berechtigt, dieses Werk zu kopieren, verändern, verbreiten und aufzuführen, selbst für kommerzielle Zwecke, <b>wenn Sie den Namen des Autors nennen</b>, die Lizenz erwähnen und ggf. angeben, ob Veränderungen vorgenommen wurden. Wenn Sie das Werk verändern, dürfen Sie das veränderte Werk nur <b>unter derselben Lizenz</b> verbreiten.",
|
||||
"en": "You can copy, modify, distribute and perform the work, even for commercial purposes, <b>if you give appropriate credit</b>, mention the license and indicate if changes were made. If you remix, transform, or build upon the material, you must distribute your contributions <b>under the same license</b> as the original."
|
||||
},
|
||||
"url": "https://creativecommons.org/licenses/by-sa/4.0/"
|
||||
},
|
||||
{
|
||||
"type": "cc-by-nc",
|
||||
"title": "Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)",
|
||||
"text": {
|
||||
"de": "Sie sind berechtigt, dieses Werk zu kopieren, verändern, verbreiten und aufzuführen, <b>wenn Sie den Namen des Autors nennen</b>, die Lizenz erwähnen und ggf. angeben, ob Veränderungen vorgenommen wurden. Sie dürfen das Werk <b>nicht für kommerzielle Zwecke</b> nutzen.",
|
||||
"en": "You can copy, modify, distribute and perform the work, <b>if you give appropriate credit</b>, mention the license and indicate if changes were made. You may use the material only for <b>non commercial purposes</b>."
|
||||
},
|
||||
"url": "https://creativecommons.org/licenses/by-nc/4.0/"
|
||||
},
|
||||
{
|
||||
"type": "cc-by-nc-sa",
|
||||
"title": "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)",
|
||||
"text": {
|
||||
"de": "Sie sind berechtigt, dieses Werk zu kopieren, verändern, verbreiten und aufzuführen, <b>wenn Sie den Namen des Autors nennen</b>, die Lizenz erwähnen und ggf. angeben, ob Veränderungen vorgenommen wurden. Sie dürfen das Werk <b>nicht für kommerzielle Zwecke</b> nutzen. Wenn Sie das Werk verändern, dürfen Sie das veränderte Werk nur <b>unter derselben Lizenz</b> verbreiten.",
|
||||
"en": "You can copy, modify, distribute and perform the work, <b>if you give appropriate credit</b>, mention the license and indicate if changes were made. You may use the material only for <b>non commercial purposes</b>. If you remix, transform, or build upon the material, you must distribute your contributions <b>under the same license</b> as the original."
|
||||
},
|
||||
"url": "https://creativecommons.org/licenses/by-nc-sa/4.0/"
|
||||
},
|
||||
{
|
||||
"type": "cc-by-nd",
|
||||
"title": "Creative Commons Attribution-NoDerivatives 4.0 International (CC BY-ND 4.0)",
|
||||
"text": {
|
||||
"de": "Sie sind berechtigt, dieses Werk zu kopieren, verbreiten und aufzuführen, selbst für kommerzielle Zwecke, <b>wenn Sie den Namen des Autors nennen</b> und die Lizenz erwähnen. Sie dürfen das Werk <b>nicht verändern</b>.",
|
||||
"en": "You can copy, distribute and perform the work, even for commercial purposes, <b>if you give appropriate credit</b> and mention the license. You may <b>not alter</b> the work in any way."
|
||||
},
|
||||
"url": "https://creativecommons.org/licenses/by-nd/4.0/"
|
||||
},
|
||||
{
|
||||
"type": "cc-by-nc-nd",
|
||||
"title": "Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)",
|
||||
"text": {
|
||||
"de": "Sie sind berechtigt, dieses Werk zu kopieren, verbreiten und aufzuführen, <b>wenn Sie den Namen des Autors nennen</b> und die Lizenz erwähnen. Sie dürfen das Werk <b>nicht verändern</b> und <b>nicht für kommerzielle Zwecke</b> nutzen.",
|
||||
"en": "You can copy, distribute and perform the work, <b>if you give appropriate credit</b> and mention the license. You may <b>not alter</b> the work in any way and may use the material only for <b>non commercial purposes</b>."
|
||||
},
|
||||
"url": "https://creativecommons.org/licenses/by-nc-nd/4.0/"
|
||||
},
|
||||
{
|
||||
"type": "all-rights-reserved",
|
||||
"title": "All rights reserved",
|
||||
"text": {
|
||||
"de": "Alle Rechte vorbehalten. Dieses Werk darf ohne Erlaubnis des Autors <b>nicht kopiert, verändert, verbreitet oder aufgeführt</b> werden.",
|
||||
"en": "All rights reserved. This work <b>may not be copied, modified, distributed or performed</b> without the permission of the author."
|
||||
}
|
||||
}
|
||||
]
|
||||
43
src/lib/types.d.ts
vendored
Normal file
43
src/lib/types.d.ts
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
type TranslationKey = 'de' | 'en';
|
||||
|
||||
type Translation = Record<TranslationKey, string>;
|
||||
|
||||
type LicenseType = 'cc0' | 'cc-by' | 'cc-by-sa' | 'cc-by-nc' | 'cc-by-nc-sa' | 'cc-by-nd' | 'cc-by-nc-nd' | 'all-rights-reserved';
|
||||
|
||||
type License = {
|
||||
type: LicenseType;
|
||||
title: Translation | string;
|
||||
text: Translation | string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
type Metadata = {
|
||||
title: Translation | string;
|
||||
description?: Translation | string;
|
||||
authors?: string[] | string;
|
||||
place?: string;
|
||||
tags?: string[];
|
||||
license?: License;
|
||||
}
|
||||
|
||||
type AlbumMetadata = Metadata & {
|
||||
date: string; // ISO 8601, e.g. 2020-12-24, used for sorting
|
||||
cover?: string;
|
||||
};
|
||||
|
||||
type ItemMetadata = Metadata & {
|
||||
item: string;
|
||||
timestamp?: string; // ISO 8601, e.g. 2020-12-24T12:00:00Z
|
||||
};
|
||||
|
||||
type Item = ItemMetadata;
|
||||
|
||||
type Album = AlbumMetadata & {
|
||||
slug: string;
|
||||
uriTimestamp?: string;
|
||||
items: Item[];
|
||||
};
|
||||
|
||||
type ApiError = {
|
||||
error: string;
|
||||
};
|
||||
@ -2,5 +2,30 @@
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
<h1>{$page.status}</h1>
|
||||
<p>{$page.error?.message}</p>
|
||||
<div class="container">
|
||||
<h1>{$page.status} {$page.error?.message}</h1>
|
||||
<a href="https://bosin.ch/">back to bosin.ch</a>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
*{
|
||||
margin: 0px 0px 0px 0px;
|
||||
padding: 0px 0px 0px 0px;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
:global(body){
|
||||
background-color: #22313F;
|
||||
}
|
||||
.container {
|
||||
font-family: Roboto, open-sans, Arial;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
color: #FFF;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
a {
|
||||
color: #FFF;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,4 @@
|
||||
<script>
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,16 +1,26 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import albums from '$lib/albums.json';
|
||||
import StreamZip from 'node-stream-zip';
|
||||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export function load({ params }) {
|
||||
const album = albums.find((album) => album.slug === params.slug);
|
||||
if (album) {
|
||||
return {
|
||||
title: album.title,
|
||||
content: album.description,
|
||||
image: '/s/lga/?i=LFB04128-Enhanced-NR.jpg'
|
||||
};
|
||||
}
|
||||
export async function load({ params }) {
|
||||
const { slug, timestamp } = params;
|
||||
const cslug = slug.replace(/[^\w-]/gi, '');
|
||||
const ctimestamp = timestamp?.replace(/[^\w-]/gi, '');
|
||||
|
||||
throw error(404, 'Not found');
|
||||
const zipFile = `./zip/${cslug}${ctimestamp ? '-' + ctimestamp :''}.zip`;
|
||||
|
||||
let entries = null;
|
||||
try {
|
||||
const zip = new StreamZip.async({ file: zipFile });
|
||||
entries = await zip.entries();
|
||||
await zip.close();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw error(404, 'Not found');
|
||||
}
|
||||
return {
|
||||
slug: params.slug,
|
||||
timestamp: params.timestamp,
|
||||
entries
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
<script>
|
||||
import { str, strf } from '$lib/data/language.js';
|
||||
|
||||
import Header from '$lib/components/Header.svelte';
|
||||
import Gallery from '$lib/components/Gallery.svelte';
|
||||
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
|
||||
const uriBase = `/s/apitest.php?slug=${data.slug}` + (data.timestamp ? `×tamp=${data.timestamp}` : '');
|
||||
</script>
|
||||
|
||||
<h1>{data.title}</h1>
|
||||
<div>{@html data.content}</div>
|
||||
<img src={data.image} alt={data.title} />
|
||||
<Header title={$strf(data.slug)}/>
|
||||
<Gallery items={$album.items} base={uriBase} />
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
/** @type {import('./$types').RequestHandler} */
|
||||
export function GET({ url }) {
|
||||
const noAttachment = url.searchParams.has('r');
|
||||
|
||||
|
||||
|
||||
const min = Number(url.searchParams.get('min') ?? '0');
|
||||
const max = Number(url.searchParams.get('max') ?? '1');
|
||||
|
||||
const d = max - min;
|
||||
|
||||
if (isNaN(d) || d < 0) {
|
||||
throw error(400, 'min and max must be numbers, and min must be less than max');
|
||||
}
|
||||
|
||||
const random = min + Math.random() * d;
|
||||
|
||||
return new Response(String(random), {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
'Content-Disposition': noAttachment ? 'inline' : 'attachment'
|
||||
}
|
||||
});
|
||||
}
|
||||
20
src/routes/g/[slug]/[[timestamp]]/o/+page.js
Normal file
20
src/routes/g/[slug]/[[timestamp]]/o/+page.js
Normal file
@ -0,0 +1,20 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import StreamZip from 'node-stream-zip';
|
||||
|
||||
/** @type {import('./$types').PageLoad} */
|
||||
export async function load({ params }) {
|
||||
let entries = null;
|
||||
try {
|
||||
const zip = new StreamZip.async({ file: `./zip/${params.slug}.zip` });
|
||||
entries = await zip.entries();
|
||||
await zip.close();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
error(404, 'Not found');
|
||||
return {
|
||||
slug: params.slug,
|
||||
timestamp: params.timestamp,
|
||||
entries
|
||||
};
|
||||
}
|
||||
13
src/routes/g/[slug]/[[timestamp]]/o/+page.svelte
Normal file
13
src/routes/g/[slug]/[[timestamp]]/o/+page.svelte
Normal file
@ -0,0 +1,13 @@
|
||||
<script>
|
||||
/** @type {import('./$types').PageData} */
|
||||
export let data;
|
||||
/** @type {import('node-stream-zip').ZipEntry[]} */
|
||||
$: entries = data.entries !== null ? (Array.isArray(data.entries) ? data.entries : Object.values(data.entries)) : [];
|
||||
</script>
|
||||
|
||||
<h2>Zip Entries</h2>
|
||||
{#each entries as entry}
|
||||
{entry.name}
|
||||
{:else}
|
||||
No entries
|
||||
{/each}
|
||||
24
src/routes/g/[slug]/[[timestamp]]/zip/+server.js
Normal file
24
src/routes/g/[slug]/[[timestamp]]/zip/+server.js
Normal file
@ -0,0 +1,24 @@
|
||||
import StreamZip from 'node-stream-zip';
|
||||
import sharp from 'sharp';
|
||||
|
||||
/** @type {import('./$types').RequestHandler} */
|
||||
export async function GET({ params }) {
|
||||
|
||||
let entryData = null;
|
||||
try {
|
||||
const zip = new StreamZip.async({ file: `./zip/${params.slug}.zip` });
|
||||
const entries = Object.values(await zip.entries()).filter(entry => entry.name.endsWith('.jpg'));
|
||||
const entry = entries[Math.floor(Math.random() * entries.length)];
|
||||
const content = await zip.entryData(entry.name);
|
||||
await zip.close();
|
||||
entryData = await sharp(content).resize(400).avif({ quality: 70, effort: 0 }).toBuffer()
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
return new Response(entryData, {
|
||||
headers: {
|
||||
'Content-Type': 'image/avif',
|
||||
'Content-Disposition': 'inline'
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user