mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 20:25:32 -04:00
feat(web): responsive date group header height (#17944)
* feat: responsive date group header height * update tests * feat(web): improve perf when changing mobile orientation (#17945) fix: improve perf when changing mobile orientation
This commit is contained in:
parent
07290580a6
commit
0e4cf9ac57
@ -131,7 +131,7 @@
|
|||||||
>
|
>
|
||||||
<!-- Date group title -->
|
<!-- Date group title -->
|
||||||
<div
|
<div
|
||||||
class="flex z-[100] pt-[calc(1.75rem+1px)] pb-5 h-6 place-items-center text-xs font-medium text-immich-fg bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg md:text-sm"
|
class="flex z-[100] pt-7 pb-5 max-md:pt-5 max-md:pb-3 h-6 place-items-center text-xs font-medium text-immich-fg bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg md:text-sm"
|
||||||
style:width={dateGroup.width + 'px'}
|
style:width={dateGroup.width + 'px'}
|
||||||
>
|
>
|
||||||
{#if !singleSelect && ((hoveredDateGroup === dateGroup.groupTitle && isMouseOverGroup) || assetInteraction.selectedGroup.has(dateGroup.groupTitle))}
|
{#if !singleSelect && ((hoveredDateGroup === dateGroup.groupTitle && isMouseOverGroup) || assetInteraction.selectedGroup.has(dateGroup.groupTitle))}
|
||||||
|
@ -88,7 +88,16 @@
|
|||||||
const usingMobileDevice = $derived(mobileDevice.pointerCoarse);
|
const usingMobileDevice = $derived(mobileDevice.pointerCoarse);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
assetStore.rowHeight = maxMd ? 100 : 235;
|
const layoutOptions = maxMd
|
||||||
|
? {
|
||||||
|
rowHeight: 100,
|
||||||
|
headerHeight: 32,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
rowHeight: 235,
|
||||||
|
headerHeight: 48,
|
||||||
|
};
|
||||||
|
assetStore.setLayoutOptions(layoutOptions);
|
||||||
});
|
});
|
||||||
|
|
||||||
const scrollTo = (top: number) => {
|
const scrollTo = (top: number) => {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<div class="overflow-clip" style:height={height + 'px'}>
|
<div class="overflow-clip" style:height={height + 'px'}>
|
||||||
<div
|
<div
|
||||||
class="flex z-[100] pt-[calc(1.75rem+1px)] pb-5 h-6 place-items-center text-xs font-medium text-immich-fg bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg md:text-sm"
|
class="flex z-[100] pt-7 pb-5 h-6 place-items-center text-xs font-medium text-immich-fg bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg md:text-sm"
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,15 +48,15 @@ describe('AssetStore', () => {
|
|||||||
|
|
||||||
expect(plainBuckets).toEqual(
|
expect(plainBuckets).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({ bucketDate: '2024-03-01T00:00:00.000Z', bucketHeight: 304 }),
|
expect.objectContaining({ bucketDate: '2024-03-01T00:00:00.000Z', bucketHeight: 303 }),
|
||||||
expect.objectContaining({ bucketDate: '2024-02-01T00:00:00.000Z', bucketHeight: 4515.333_333_333_333 }),
|
expect.objectContaining({ bucketDate: '2024-02-01T00:00:00.000Z', bucketHeight: 4514.333_333_333_333 }),
|
||||||
expect.objectContaining({ bucketDate: '2024-01-01T00:00:00.000Z', bucketHeight: 286 }),
|
expect.objectContaining({ bucketDate: '2024-01-01T00:00:00.000Z', bucketHeight: 286 }),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calculates timeline height', () => {
|
it('calculates timeline height', () => {
|
||||||
expect(assetStore.timelineHeight).toBe(5105.333_333_333_333);
|
expect(assetStore.timelineHeight).toBe(5103.333_333_333_333);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -35,9 +35,7 @@ export type AssetStoreOptions = Omit<AssetApiGetTimeBucketsRequest, 'size'> & {
|
|||||||
timelineAlbumId?: string;
|
timelineAlbumId?: string;
|
||||||
deferInit?: boolean;
|
deferInit?: boolean;
|
||||||
};
|
};
|
||||||
export type AssetStoreLayoutOptions = {
|
|
||||||
rowHeight: number;
|
|
||||||
};
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function updateObject(target: any, source: any): boolean {
|
function updateObject(target: any, source: any): boolean {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
@ -110,7 +108,6 @@ export class AssetDateGroup {
|
|||||||
readonly date: DateTime;
|
readonly date: DateTime;
|
||||||
readonly dayOfMonth: number;
|
readonly dayOfMonth: number;
|
||||||
intersetingAssets: IntersectingAsset[] = $state([]);
|
intersetingAssets: IntersectingAsset[] = $state([]);
|
||||||
dodo: IntersectingAsset[] = $state([]);
|
|
||||||
|
|
||||||
height = $state(0);
|
height = $state(0);
|
||||||
width = $state(0);
|
width = $state(0);
|
||||||
@ -121,6 +118,7 @@ export class AssetDateGroup {
|
|||||||
left: number = $state(0);
|
left: number = $state(0);
|
||||||
row = $state(0);
|
row = $state(0);
|
||||||
col = $state(0);
|
col = $state(0);
|
||||||
|
deferredLayout = false;
|
||||||
|
|
||||||
constructor(bucket: AssetBucket, index: number, date: DateTime, dayOfMonth: number) {
|
constructor(bucket: AssetBucket, index: number, date: DateTime, dayOfMonth: number) {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
@ -195,6 +193,10 @@ export class AssetDateGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout(options: CommonLayoutOptions) {
|
layout(options: CommonLayoutOptions) {
|
||||||
|
if (!this.bucket.intersecting) {
|
||||||
|
this.deferredLayout = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const assets = this.intersetingAssets.map((intersetingAsset) => intersetingAsset.asset!);
|
const assets = this.intersetingAssets.map((intersetingAsset) => intersetingAsset.asset!);
|
||||||
const geometry = getJustifiedLayoutFromAssets(assets, options);
|
const geometry = getJustifiedLayoutFromAssets(assets, options);
|
||||||
this.width = geometry.containerWidth;
|
this.width = geometry.containerWidth;
|
||||||
@ -547,6 +549,11 @@ export type LiteBucket = {
|
|||||||
bucketDateFormattted: string;
|
bucketDateFormattted: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AssetStoreLayoutOptions = {
|
||||||
|
rowHeight?: number;
|
||||||
|
headerHeight?: number;
|
||||||
|
gap?: number;
|
||||||
|
};
|
||||||
export class AssetStore {
|
export class AssetStore {
|
||||||
// --- public ----
|
// --- public ----
|
||||||
isInitialized = $state(false);
|
isInitialized = $state(false);
|
||||||
@ -596,7 +603,7 @@ export class AssetStore {
|
|||||||
#unsubscribers: Unsubscriber[] = [];
|
#unsubscribers: Unsubscriber[] = [];
|
||||||
|
|
||||||
#rowHeight = $state(235);
|
#rowHeight = $state(235);
|
||||||
#headerHeight = $state(49);
|
#headerHeight = $state(48);
|
||||||
#gap = $state(12);
|
#gap = $state(12);
|
||||||
|
|
||||||
#options: AssetStoreOptions = AssetStore.#INIT_OPTIONS;
|
#options: AssetStoreOptions = AssetStore.#INIT_OPTIONS;
|
||||||
@ -608,36 +615,46 @@ export class AssetStore {
|
|||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
set headerHeight(value) {
|
setLayoutOptions({ headerHeight = 48, rowHeight = 235, gap = 12 }: AssetStoreLayoutOptions) {
|
||||||
|
let changed = false;
|
||||||
|
changed ||= this.#setHeaderHeight(headerHeight);
|
||||||
|
changed ||= this.#setGap(gap);
|
||||||
|
changed ||= this.#setRowHeight(rowHeight);
|
||||||
|
if (changed) {
|
||||||
|
this.refreshLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#setHeaderHeight(value: number) {
|
||||||
if (this.#headerHeight == value) {
|
if (this.#headerHeight == value) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
this.#headerHeight = value;
|
this.#headerHeight = value;
|
||||||
this.refreshLayout();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get headerHeight() {
|
get headerHeight() {
|
||||||
return this.#headerHeight;
|
return this.#headerHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
set gap(value) {
|
#setGap(value: number) {
|
||||||
if (this.#gap == value) {
|
if (this.#gap == value) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
this.#gap = value;
|
this.#gap = value;
|
||||||
this.refreshLayout();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get gap() {
|
get gap() {
|
||||||
return this.#gap;
|
return this.#gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
set rowHeight(value) {
|
#setRowHeight(value: number) {
|
||||||
if (this.#rowHeight == value) {
|
if (this.#rowHeight == value) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
this.#rowHeight = value;
|
this.#rowHeight = value;
|
||||||
this.refreshLayout();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
get rowHeight() {
|
get rowHeight() {
|
||||||
@ -815,6 +832,15 @@ export class AssetStore {
|
|||||||
}
|
}
|
||||||
bucket.intersecting = actuallyIntersecting || preIntersecting;
|
bucket.intersecting = actuallyIntersecting || preIntersecting;
|
||||||
bucket.actuallyIntersecting = actuallyIntersecting;
|
bucket.actuallyIntersecting = actuallyIntersecting;
|
||||||
|
if (preIntersecting || actuallyIntersecting) {
|
||||||
|
const hasDeferred = bucket.dateGroups.some((group) => group.deferredLayout);
|
||||||
|
if (hasDeferred) {
|
||||||
|
this.#updateGeometry(bucket, true);
|
||||||
|
for (const group of bucket.dateGroups) {
|
||||||
|
group.deferredLayout = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#processPendingChanges = throttle(() => {
|
#processPendingChanges = throttle(() => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user