Files
immich/web/src/lib/components/photos-page/asset-date-group.svelte
Jason Rasmussen f55b3add80 chore(web): prettier (#2821)
Co-authored-by: Thomas Way <thomas@6f.io>
2023-06-30 23:50:47 -05:00

237 lines
7.5 KiB
Svelte

<script lang="ts">
import {
assetInteractionStore,
assetsInAlbumStoreState,
isMultiSelectStoreState,
selectedAssets,
selectedGroup,
} from '$lib/stores/asset-interaction.store';
import { assetStore } from '$lib/stores/assets.store';
import { locale } from '$lib/stores/preferences.store';
import type { AssetResponseDto } from '@api';
import justifiedLayout from 'justified-layout';
import lodash from 'lodash-es';
import CheckCircle from 'svelte-material-icons/CheckCircle.svelte';
import CircleOutline from 'svelte-material-icons/CircleOutline.svelte';
import { fly } from 'svelte/transition';
import { getAssetRatio } from '$lib/utils/asset-utils';
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
import { createEventDispatcher } from 'svelte';
export let assets: AssetResponseDto[];
export let bucketDate: string;
export let bucketHeight: number;
export let isAlbumSelectionMode = false;
export let viewportWidth: number;
const groupDateFormat: Intl.DateTimeFormatOptions = {
weekday: 'short',
month: 'short',
day: 'numeric',
year: 'numeric',
};
const dispatch = createEventDispatcher();
let isMouseOverGroup = false;
let actualBucketHeight: number;
let hoveredDateGroup = '';
interface LayoutBox {
top: number;
left: number;
width: number;
}
$: assetsGroupByDate = lodash
.chain(assets)
.groupBy((a) => new Date(a.fileCreatedAt).toLocaleDateString($locale, groupDateFormat))
.sortBy((group) => assets.indexOf(group[0]))
.value();
$: geometry = (() => {
const geometry = [];
for (let group of assetsGroupByDate) {
const justifiedLayoutResult = justifiedLayout(group.map(getAssetRatio), {
boxSpacing: 2,
containerWidth: Math.floor(viewportWidth),
containerPadding: 0,
targetRowHeightTolerance: 0.15,
targetRowHeight: 235,
});
geometry.push({
...justifiedLayoutResult,
containerWidth: calculateWidth(justifiedLayoutResult.boxes),
});
}
return geometry;
})();
$: {
if (actualBucketHeight && actualBucketHeight != 0 && actualBucketHeight != bucketHeight) {
const heightDelta = assetStore.updateBucketHeight(bucketDate, actualBucketHeight);
if (heightDelta !== 0) {
scrollTimeline(heightDelta);
}
}
}
function scrollTimeline(heightDelta: number) {
dispatch('shift', {
heightDelta,
});
}
const calculateWidth = (boxes: LayoutBox[]): number => {
let width = 0;
for (const box of boxes) {
if (box.top < 100) {
width = box.left + box.width;
}
}
return width;
};
const assetClickHandler = (
asset: AssetResponseDto,
assetsInDateGroup: AssetResponseDto[],
dateGroupTitle: string,
) => {
if (isAlbumSelectionMode) {
assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle);
return;
}
if ($isMultiSelectStoreState) {
assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle);
} else {
assetInteractionStore.setViewingAsset(asset);
}
};
const selectAssetGroupHandler = (selectAssetGroupHandler: AssetResponseDto[], dateGroupTitle: string) => {
if ($selectedGroup.has(dateGroupTitle)) {
assetInteractionStore.removeGroupFromMultiselectGroup(dateGroupTitle);
selectAssetGroupHandler.forEach((asset) => {
assetInteractionStore.removeAssetFromMultiselectGroup(asset);
});
} else {
assetInteractionStore.addGroupToMultiselectGroup(dateGroupTitle);
selectAssetGroupHandler.forEach((asset) => {
assetInteractionStore.addAssetToMultiselectGroup(asset);
});
}
};
const assetSelectHandler = (
asset: AssetResponseDto,
assetsInDateGroup: AssetResponseDto[],
dateGroupTitle: string,
) => {
if ($selectedAssets.has(asset)) {
assetInteractionStore.removeAssetFromMultiselectGroup(asset);
} else {
assetInteractionStore.addAssetToMultiselectGroup(asset);
}
// Check if all assets are selected in a group to toggle the group selection's icon
let selectedAssetsInGroupCount = 0;
assetsInDateGroup.forEach((asset) => {
if ($selectedAssets.has(asset)) {
selectedAssetsInGroupCount++;
}
});
// if all assets are selected in a group, add the group to selected group
if (selectedAssetsInGroupCount == assetsInDateGroup.length) {
assetInteractionStore.addGroupToMultiselectGroup(dateGroupTitle);
} else {
assetInteractionStore.removeGroupFromMultiselectGroup(dateGroupTitle);
}
};
const assetMouseEventHandler = (dateGroupTitle: string) => {
// Show multi select icon on hover on date group
hoveredDateGroup = dateGroupTitle;
};
</script>
<section
id="asset-group-by-date"
class="flex flex-wrap gap-x-12"
bind:clientHeight={actualBucketHeight}
bind:clientWidth={viewportWidth}
>
{#each assetsGroupByDate as assetsInDateGroup, groupIndex (assetsInDateGroup[0].id)}
{@const dateGroupTitle = new Date(assetsInDateGroup[0].fileCreatedAt).toLocaleDateString($locale, groupDateFormat)}
<!-- Asset Group By Date -->
<div
class="flex flex-col mt-5"
on:mouseenter={() => {
isMouseOverGroup = true;
assetMouseEventHandler(dateGroupTitle);
}}
on:mouseleave={() => (isMouseOverGroup = false)}
>
<!-- Date group title -->
<p
class="font-medium text-xs md:text-sm text-immich-fg dark:text-immich-dark-fg mb-2 flex place-items-center h-6"
style="width: {geometry[groupIndex].containerWidth}px"
>
{#if (hoveredDateGroup == dateGroupTitle && isMouseOverGroup) || $selectedGroup.has(dateGroupTitle)}
<div
transition:fly={{ x: -24, duration: 200, opacity: 0.5 }}
class="inline-block px-2 hover:cursor-pointer"
on:click={() => selectAssetGroupHandler(assetsInDateGroup, dateGroupTitle)}
on:keydown={() => selectAssetGroupHandler(assetsInDateGroup, dateGroupTitle)}
>
{#if $selectedGroup.has(dateGroupTitle)}
<CheckCircle size="24" color="#4250af" />
{:else}
<CircleOutline size="24" color="#757575" />
{/if}
</div>
{/if}
<span class="truncate" title={dateGroupTitle}>
{dateGroupTitle}
</span>
</p>
<!-- Image grid -->
<div
class="relative"
style="height: {geometry[groupIndex].containerHeight}px;width: {geometry[groupIndex].containerWidth}px"
>
{#each assetsInDateGroup as asset, index (asset.id)}
{@const box = geometry[groupIndex].boxes[index]}
<div
class="absolute"
style="width: {box.width}px; height: {box.height}px; top: {box.top}px; left: {box.left}px"
>
<Thumbnail
{asset}
{groupIndex}
on:click={() => assetClickHandler(asset, assetsInDateGroup, dateGroupTitle)}
on:select={() => assetSelectHandler(asset, assetsInDateGroup, dateGroupTitle)}
on:mouse-event={() => assetMouseEventHandler(dateGroupTitle)}
selected={$selectedAssets.has(asset) || $assetsInAlbumStoreState.findIndex((a) => a.id == asset.id) != -1}
disabled={$assetsInAlbumStoreState.findIndex((a) => a.id == asset.id) != -1}
thumbnailWidth={box.width}
thumbnailHeight={box.height}
/>
</div>
{/each}
</div>
</div>
{/each}
</section>
<style>
#asset-group-by-date {
contain: layout;
}
</style>