Show assets on web (#168)

* Implemented lazy loading thumbnail
* Display assets as date-time grouping
* Update Readme
* Modify GitHub action to run from the latest update
This commit is contained in:
Alex 2022-05-21 16:50:56 -05:00 committed by GitHub
parent 171e7ffa77
commit 6023c3c624
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 350 additions and 752 deletions

View file

@ -5,15 +5,17 @@ type ISend = {
path: string,
data?: any,
token: string
customHeaders?: Record<string, string>,
}
type IOption = {
method: string,
headers: Record<string, string>,
body: any
}
async function send({ method, path, data, token }: ISend) {
async function send({ method, path, data, token, customHeaders }: ISend) {
const opts: IOption = { method, headers: {} } as IOption;
if (data) {
@ -21,6 +23,11 @@ async function send({ method, path, data, token }: ISend) {
opts.body = JSON.stringify(data);
}
if (customHeaders) {
console.log(customHeaders);
// opts.headers[customHeader.$1]
}
if (token) {
opts.headers['Authorization'] = `Bearer ${token}`;
}
@ -36,18 +43,18 @@ async function send({ method, path, data, token }: ISend) {
});
}
export function getRequest(path: string, token: string) {
return send({ method: 'GET', path, token });
export function getRequest(path: string, token: string, customHeaders?: Record<string, string>) {
return send({ method: 'GET', path, token, customHeaders });
}
export function delRequest(path: string, token: string) {
return send({ method: 'DELETE', path, token });
export function delRequest(path: string, token: string, customHeaders?: Record<string, string>) {
return send({ method: 'DELETE', path, token, customHeaders });
}
export function postRequest(path: string, data: any, token: string) {
return send({ method: 'POST', path, data, token });
export function postRequest(path: string, data: any, token: string, customHeaders?: Record<string, string>) {
return send({ method: 'POST', path, data, token, customHeaders });
}
export function putRequest(path: string, data: any, token: string) {
return send({ method: 'PUT', path, data, token });
export function putRequest(path: string, data: any, token: string, customHeaders?: Record<string, string>) {
return send({ method: 'PUT', path, data, token, customHeaders });
}

View file

@ -0,0 +1,46 @@
<script lang="ts">
import type { ImmichAsset } from '../../models/immich-asset';
import { session } from '$app/stores';
import { onDestroy } from 'svelte';
import { fade } from 'svelte/transition';
import { serverEndpoint } from '../../constants';
import IntersectionObserver from '$lib/components/photos/intersection-observer.svelte';
export let asset: ImmichAsset;
let imageContent: string;
const loadImageData = async () => {
if ($session.user) {
const res = await fetch(serverEndpoint + '/asset/thumbnail/' + asset.id, {
method: 'GET',
headers: {
Authorization: 'bearer ' + $session.user.accessToken,
},
});
imageContent = URL.createObjectURL(await res.blob());
return imageContent;
}
};
onDestroy(() => URL.revokeObjectURL(imageContent));
</script>
<IntersectionObserver once={true} let:intersecting>
<div class="h-[200px] w-[200px] bg-gray-100">
{#if intersecting}
{#await loadImageData()}
<div class="bg-immich-primary/10 h-[200px] w-[200px] flex place-items-center place-content-center">...</div>
{:then imageData}
<img
in:fade={{ duration: 200 }}
src={imageData}
alt={asset.id}
class="object-cover h-[200px] w-[200px] transition-all duration-100"
loading="lazy"
/>
{/await}
{/if}
</div>
</IntersectionObserver>

View file

@ -0,0 +1,55 @@
<script lang="ts">
import { onMount } from 'svelte';
export let once = false;
export let top = 0;
export let bottom = 0;
export let left = 0;
export let right = 0;
let intersecting = false;
let container: any;
onMount(() => {
if (typeof IntersectionObserver !== 'undefined') {
const rootMargin = `${bottom}px ${left}px ${top}px ${right}px`;
const observer = new IntersectionObserver(
(entries) => {
intersecting = entries[0].isIntersecting;
if (intersecting && once) {
observer.unobserve(container);
}
},
{
rootMargin,
},
);
observer.observe(container);
return () => observer.unobserve(container);
}
// The following is a fallback for older browsers
function handler() {
const bcr = container.getBoundingClientRect();
intersecting =
bcr.bottom + bottom > 0 &&
bcr.right + right > 0 &&
bcr.top - top < window.innerHeight &&
bcr.left - left < window.innerWidth;
if (intersecting && once) {
window.removeEventListener('scroll', handler);
}
}
window.addEventListener('scroll', handler);
return () => window.removeEventListener('scroll', handler);
});
</script>
<div bind:this={container}>
<slot {intersecting} />
</div>

View file

@ -16,10 +16,10 @@
<div class="flex border place-items-center px-6 py-2 ">
<a class="flex gap-2 place-items-center hover:cursor-pointer" href="/photos">
<img src="/immich-logo.svg" alt="immich logo" height="35" width="35" />
<h1 class="font-immich-title text-2xl text-immich-primary">Immich</h1>
<h1 class="font-immich-title text-2xl text-immich-primary">IMMICH</h1>
</a>
<div class="flex-1 ml-24">
<div class="w-[50%] border rounded-md bg-gray-200 px-8 py-4">Search</div>
<input class="w-[50%] border rounded-md bg-gray-200 px-8 py-4" placeholder="Search - Coming soon" />
</div>
<section class="flex gap-6 place-items-center">
<!-- <div>Upload</div> -->

View file

@ -0,0 +1,54 @@
export enum AssetType {
IMAGE = 'IMAGE',
VIDEO = 'VIDEO',
AUDIO = 'AUDIO',
OTHER = 'OTHER',
}
export type ImmichExif = {
id: string;
assetId: string;
make: string;
model: string;
imageName: string;
exifImageWidth: number;
exifImageHeight: number;
fileSizeInByte: number;
orientation: string;
dateTimeOriginal: Date;
modifyDate: Date;
lensModel: string;
fNumber: number;
focalLength: number;
iso: number;
exposureTime: number;
latitude: number;
longitude: number;
city: string;
state: string;
country: string;
}
export type ImmichAssetSmartInfo = {
id: string;
assetId: string;
tags: string[];
objects: string[];
}
export type ImmichAsset = {
id: string;
deviceAssetId: string;
userId: string;
deviceId: string;
type: AssetType;
originalPath: string;
resizePath: string;
createdAt: string;
modifiedAt: string;
isFavorite: boolean;
mimeType: string;
duration: string;
exifInfo?: ImmichExif;
smartInfo?: ImmichAssetSmartInfo;
}

View file

@ -0,0 +1,28 @@
import { writable, derived } from 'svelte/store';
import { getRequest } from '$lib/api';
import type { ImmichAsset } from '$lib/models/immich-asset'
import * as _ from 'lodash';
import moment from 'moment';
const assets = writable<ImmichAsset[]>([]);
const assetsGroupByDate = derived(assets, ($assets) => {
return _.chain($assets)
.groupBy((a) => moment(a.createdAt).format('ddd, MMM DD'))
.sortBy((group) => $assets.indexOf(group[0]))
.value();
})
const getAssetsInfo = async (accessToken: string) => {
const res = await getRequest('asset', accessToken);
assets.set(res);
}
export default {
assets,
assetsGroupByDate,
getAssetsInfo,
}