Compare commits

...

9 Commits

Author SHA1 Message Date
Santo Shakil 26e535fb75 use a page scoped future provider for the loading state 2026-06-12 11:19:53 +06:00
Santo Shakil 039a855db1 fix(mobile): endless spinner on album selection when device has no albums 2026-06-11 23:13:40 +06:00
Alex aa6af7ce36 chore: workflow trigger i18n (#28992) 2026-06-11 10:55:18 -05:00
Santo Shakil 59d036a2ed fix(mobile): give android notification channels proper names (#28986) 2026-06-11 15:07:37 +00:00
renovate[bot] 7a5c014558 fix(deps): update typescript-projects (#28627)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-06-11 17:02:54 +02:00
Santo Shakil e2954b6411 fix(mobile): show albums whose assets are all trashed (#28985) 2026-06-11 09:41:02 -05:00
renovate[bot] 0fb18ed241 chore(deps): update dependency commander to v15 (#28936) 2026-06-11 12:18:25 +02:00
renovate[bot] c0b3b08ce6 chore(deps): update exiftool to v35.21.0 (#28933) 2026-06-11 12:16:13 +02:00
Mees Frensel e8a1084e5b fix(web): heatmap layout and date formatting (#28976)
* fix(web): heatmap layout and date formatting

* chore

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2026-06-11 08:36:34 +00:00
17 changed files with 1445 additions and 1216 deletions
+1 -1
View File
@@ -28,4 +28,4 @@ run = "prettier --write ."
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
[tools]
wrangler = "4.91.0"
wrangler = "4.98.0"
+2
View File
@@ -2389,6 +2389,8 @@
"trash_page_title": "Trash ({count})",
"trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.",
"trigger": "Trigger",
"trigger_asset_metadata_extraction": "Asset Metadata Extraction",
"trigger_asset_metadata_extraction_description": "Triggered when the EXIF of an asset is extracted",
"trigger_asset_uploaded": "Asset Upload",
"trigger_asset_uploaded_description": "Triggered when a new asset is uploaded",
"trigger_description": "An event that kicks off the workflow",
+14 -14
View File
@@ -83,7 +83,7 @@ version = "7.1.3-6"
backend = "github:jellyfin/jellyfin-ffmpeg"
[tools."github:jellyfin/jellyfin-ffmpeg".options]
asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz"
asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz"
[[tools."github:webassembly/binaryen"]]
version = "version_124"
@@ -217,37 +217,37 @@ checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c70773
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
[[tools.pnpm]]
version = "11.4.0"
version = "11.5.2"
backend = "aqua:pnpm/pnpm"
[tools.pnpm."platforms.linux-arm64"]
checksum = "sha256:cc38ebd5b2610a5744f84576b963c49e6609a8df5aed714ae3de749998d4478c"
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-arm64.tar.gz"
checksum = "sha256:7fef0c74081135d777754fccf25272f698e504b26ba0568504846c0cea402f8f"
url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-linux-arm64.tar.gz"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-arm64-musl"]
checksum = "sha256:a1e2ec9123c709fd04b704227cfcf3b50cd2bbbc1bd39d2df414530b5697eb75"
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-arm64-musl.tar.gz"
checksum = "sha256:843beed7bca760276d29f8950ca219600995d345dbc93fad8150b3e5f83b74d4"
url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-linux-arm64-musl.tar.gz"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-x64"]
checksum = "sha256:f3f8d1217eef013bbc71a24d52efb1f1041e4aff55edd80e0b08e25f409305a4"
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-x64.tar.gz"
checksum = "sha256:2033a702618c8576dc6bb0f6adb3a67ab506031351ddd59ca50d1bcaf5d13dc5"
url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-linux-x64.tar.gz"
provenance = "github-attestations"
[tools.pnpm."platforms.linux-x64-musl"]
checksum = "sha256:60010ad00a96b71e20d1618acaca7a71395e710cbd5e88946c030a1d07c56916"
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-linux-x64-musl.tar.gz"
checksum = "sha256:0b794b23461c7475f7ffc29c4945692838b51ddadd857a2ace8edf0018798305"
url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-linux-x64-musl.tar.gz"
provenance = "github-attestations"
[tools.pnpm."platforms.macos-arm64"]
checksum = "sha256:ba59014c2c1ce8b76af9f559385206a2623de4ff2b694b5c91598a8f44abb4e2"
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-darwin-arm64.tar.gz"
checksum = "sha256:54993dae26bea0f3c1b0e15f9427f6f6a86827d56f32d1d1554d8cda59a62399"
url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-darwin-arm64.tar.gz"
provenance = "github-attestations"
[tools.pnpm."platforms.windows-x64"]
checksum = "sha256:84ce90e38bc0b1164173eb853a0fbffc7edcb050cb0d5c8ce4ca609f5c808e0a"
url = "https://github.com/pnpm/pnpm/releases/download/v11.4.0/pnpm-win32-x64.zip"
checksum = "sha256:b3ddff2c2bf87d3996fadf074bac58cd2259f718a17912a04ae930e3775b30e9"
url = "https://github.com/pnpm/pnpm/releases/download/v11.5.2/pnpm-win32-x64.zip"
provenance = "github-attestations"
[[tools.terragrunt]]
+1 -1
View File
@@ -16,7 +16,7 @@ config_roots = [
[tools]
node = "24.15.0"
pnpm = "11.4.0"
pnpm = "11.5.2"
terragrunt = "1.0.3"
opentofu = "1.11.6"
"npm:oazapfts" = "7.5.0"
@@ -69,7 +69,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
val notificationChannel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_ID,
ctx.getString(R.string.background_worker_notification_channel_name),
NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(notificationChannel)
@@ -5,4 +5,9 @@
<string name="memory_widget_description">See memories from Immich.</string>
<string name="random_widget_description">View a random image from your library or a specific album.</string>
<string name="bg_downloader_notification_channel_name">Uploads and downloads</string>
<string name="bg_downloader_notification_channel_description">Progress updates for uploads and downloads</string>
<string name="background_worker_notification_channel_name">Background backup</string>
</resources>
@@ -20,7 +20,10 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
const DriftRemoteAlbumRepository(this._db) : super(_db);
Future<List<RemoteAlbum>> getAll({Set<SortRemoteAlbumsBy> sortBy = const {SortRemoteAlbumsBy.updatedAt}}) {
final assetCount = _db.remoteAlbumAssetEntity.assetId.count(distinct: true);
// Count non-trashed assets via the joined asset table. Filtering trashed assets in the
// join condition (instead of the where clause) keeps albums whose assets are all trashed
// in the result, the same way truly empty albums are kept
final assetCount = _db.remoteAssetEntity.id.count(distinct: true);
final query = _db.remoteAlbumEntity.select().join([
leftOuterJoin(
@@ -30,7 +33,8 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
),
leftOuterJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId) &
_db.remoteAssetEntity.deletedAt.isNull(),
useColumns: false,
),
leftOuterJoin(
@@ -47,7 +51,6 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
),
]);
query
..where(_db.remoteAssetEntity.deletedAt.isNull())
..addColumns([assetCount])
..addColumns([_db.userEntity.name, _db.userEntity.id])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
@@ -79,7 +82,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
}
Future<RemoteAlbum?> get(String albumId) {
final assetCount = _db.remoteAlbumAssetEntity.assetId.count(distinct: true);
final assetCount = _db.remoteAssetEntity.id.count(distinct: true);
final query =
_db.remoteAlbumEntity.select().join([
@@ -90,7 +93,8 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
),
leftOuterJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId) &
_db.remoteAssetEntity.deletedAt.isNull(),
useColumns: false,
),
leftOuterJoin(
@@ -106,7 +110,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
useColumns: false,
),
])
..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull())
..where(_db.remoteAlbumEntity.id.equals(albumId))
..addColumns([assetCount])
..addColumns([_db.userEntity.name, _db.userEntity.id])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
@@ -515,7 +519,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
return [];
}
final assetCount = _db.remoteAlbumAssetEntity.assetId.count(distinct: true);
final assetCount = _db.remoteAssetEntity.id.count(distinct: true);
final query =
_db.remoteAlbumEntity.select().join([
leftOuterJoin(
@@ -525,7 +529,8 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
),
leftOuterJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId) &
_db.remoteAssetEntity.deletedAt.isNull(),
useColumns: false,
),
leftOuterJoin(
@@ -541,7 +546,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
useColumns: false,
),
])
..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull())
..where(_db.remoteAlbumEntity.id.isIn(albumIds))
..addColumns([assetCount])
..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)])
..addColumns([_db.userEntity.name, _db.userEntity.id])
@@ -19,6 +19,11 @@ import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart';
import 'package:immich_mobile/widgets/common/search_field.dart';
import 'package:logging/logging.dart';
final backupAlbumCountProvider = FutureProvider.autoDispose<int>((ref) async {
await ref.read(backupAlbumProvider.notifier).getAll();
return ref.read(backupAlbumProvider).length;
});
@RoutePage()
class DriftBackupAlbumSelectionPage extends ConsumerStatefulWidget {
const DriftBackupAlbumSelectionPage({super.key});
@@ -44,7 +49,6 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
_searchFocusNode = FocusNode();
_enableSyncUploadAlbum.value = ref.read(appConfigProvider).backup.syncAlbums;
ref.read(backupAlbumProvider.notifier).getAll();
_initialTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
}
@@ -79,6 +83,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
@override
Widget build(BuildContext context) {
final isLoading = ref.watch(backupAlbumCountProvider).isLoading;
final albums = ref.watch(backupAlbumProvider);
final albumCount = albums.length;
// Filter albums based on search query
@@ -249,9 +254,17 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
SliverLayoutBuilder(
builder: (context, constraints) {
if (constraints.crossAxisExtent > 600) {
return _AlbumSelectionGrid(filteredAlbums: filteredAlbums, searchQuery: _searchQuery);
return _AlbumSelectionGrid(
filteredAlbums: filteredAlbums,
searchQuery: _searchQuery,
isLoading: isLoading,
);
} else {
return _AlbumSelectionList(filteredAlbums: filteredAlbums, searchQuery: _searchQuery);
return _AlbumSelectionList(
filteredAlbums: filteredAlbums,
searchQuery: _searchQuery,
isLoading: isLoading,
);
}
},
),
@@ -292,8 +305,9 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
class _AlbumSelectionList extends StatelessWidget {
final List<LocalAlbum> filteredAlbums;
final String searchQuery;
final bool isLoading;
const _AlbumSelectionList({required this.filteredAlbums, required this.searchQuery});
const _AlbumSelectionList({required this.filteredAlbums, required this.searchQuery, required this.isLoading});
@override
Widget build(BuildContext context) {
@@ -309,7 +323,18 @@ class _AlbumSelectionList extends StatelessWidget {
}
if (filteredAlbums.isEmpty) {
return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator()));
if (isLoading) {
return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator()));
}
return SliverToBoxAdapter(
child: Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Text('no_albums_found'.t(context: context)),
),
),
);
}
return SliverPadding(
@@ -326,8 +351,9 @@ class _AlbumSelectionList extends StatelessWidget {
class _AlbumSelectionGrid extends StatelessWidget {
final List<LocalAlbum> filteredAlbums;
final String searchQuery;
final bool isLoading;
const _AlbumSelectionGrid({required this.filteredAlbums, required this.searchQuery});
const _AlbumSelectionGrid({required this.filteredAlbums, required this.searchQuery, required this.isLoading});
@override
Widget build(BuildContext context) {
@@ -343,7 +369,18 @@ class _AlbumSelectionGrid extends StatelessWidget {
}
if (filteredAlbums.isEmpty) {
return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator()));
if (isLoading) {
return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator()));
}
return SliverToBoxAdapter(
child: Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Text('no_albums_found'.t(context: context)),
),
),
);
}
return SliverPadding(
@@ -44,6 +44,94 @@ void main() {
});
});
group('getAll', () {
test('returns album when all of its assets are trashed', () async {
final user = await ctx.newUser();
final album = await ctx.newRemoteAlbum(ownerId: user.id);
final asset1 = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1));
final asset2 = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1));
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset1.id);
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset2.id);
final albums = await sut.getAll();
expect(albums, hasLength(1));
expect(albums.first.id, album.id);
expect(albums.first.assetCount, 0);
});
test('excludes trashed assets from assetCount', () async {
final user = await ctx.newUser();
final album = await ctx.newRemoteAlbum(ownerId: user.id);
final active1 = await ctx.newRemoteAsset(ownerId: user.id);
final active2 = await ctx.newRemoteAsset(ownerId: user.id);
final trashed = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1));
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: active1.id);
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: active2.id);
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: trashed.id);
final albums = await sut.getAll();
expect(albums, hasLength(1));
expect(albums.first.assetCount, 2);
});
test('returns album without assets', () async {
final user = await ctx.newUser();
final album = await ctx.newRemoteAlbum(ownerId: user.id);
final albums = await sut.getAll();
expect(albums, hasLength(1));
expect(albums.first.id, album.id);
expect(albums.first.assetCount, 0);
});
});
group('get', () {
test('returns the album when all of its assets are trashed', () async {
final user = await ctx.newUser();
final album = await ctx.newRemoteAlbum(ownerId: user.id);
final asset = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1));
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset.id);
final result = await sut.get(album.id);
expect(result, isNotNull);
expect(result?.id, album.id);
expect(result?.assetCount, 0);
});
});
group('getAlbumsContainingAsset', () {
test('excludes trashed assets from assetCount', () async {
final user = await ctx.newUser();
final album = await ctx.newRemoteAlbum(ownerId: user.id);
final asset = await ctx.newRemoteAsset(ownerId: user.id);
final trashed = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1));
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset.id);
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: trashed.id);
final albums = await sut.getAlbumsContainingAsset(asset.id);
expect(albums, hasLength(1));
expect(albums.first.id, album.id);
expect(albums.first.assetCount, 1);
});
test('returns albums for a trashed asset', () async {
final user = await ctx.newUser();
final album = await ctx.newRemoteAlbum(ownerId: user.id);
final trashed = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1));
await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: trashed.id);
final albums = await sut.getAlbumsContainingAsset(trashed.id);
expect(albums, hasLength(1));
expect(albums.first.assetCount, 0);
});
});
group('getSortedAlbumIds', () {
late String userId;
+1 -1
View File
@@ -11,7 +11,7 @@
"release": "./misc/release/pump-version.sh",
"pump": "node ./misc/release/pump-wrapper.js"
},
"packageManager": "pnpm@11.4.0",
"packageManager": "pnpm@11.5.2",
"engines": {
"pnpm": ">=10.0.0"
},
+1 -1
View File
@@ -29,7 +29,7 @@
"@vitest/coverage-v8": "^4.0.0",
"byte-size": "^9.0.0",
"cli-progress": "^3.12.0",
"commander": "^12.0.0",
"commander": "^15.0.0",
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
+1 -1
View File
@@ -13,5 +13,5 @@
"oidc-provider": "^9.0.0",
"tsx": "^4.20.6"
},
"packageManager": "pnpm@11.4.0"
"packageManager": "pnpm@11.5.2"
}
+1 -1
View File
@@ -24,7 +24,7 @@
"keywords": [],
"author": "",
"license": "GNU Affero General Public License version 3",
"packageManager": "pnpm@11.4.0",
"packageManager": "pnpm@11.5.2",
"devDependencies": {
"@extism/js-pdk": "^1.1.1",
"@immich/sdk": "workspace:*",
+1230 -1135
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -105,7 +105,7 @@
"prettier-plugin-sort-json": "^4.1.1",
"prettier-plugin-svelte": "^4.0.0",
"rollup-plugin-visualizer": "^7.0.0",
"svelte": "5.55.8",
"svelte": "5.56.2",
"svelte-check": "^4.4.6",
"svelte-eslint-parser": "^1.3.3",
"tailwindcss": "^4.2.4",
+34 -43
View File
@@ -1,7 +1,8 @@
<script lang="ts">
import { locale } from '$lib/stores/preferences.store';
import type { CalendarHeatmapResponseDto } from '@immich/sdk';
import { DateTime } from 'luxon';
import { Text } from '@immich/ui';
import { DateTime, Info } from 'luxon';
import { t } from 'svelte-i18n';
type Props = {
@@ -12,20 +13,8 @@
const { data, itemLabel, totalLabel }: Props = $props();
const { rows } = $derived.by(() => {
const weeks = Array.from({ length: Math.ceil(data.series.length / 7) }, (_, index) =>
data.series.slice(index * 7, index * 7 + 7),
);
const rows = Array.from({ length: 7 }, (_, dayIndex) => weeks.map((week) => week[dayIndex]).filter(Boolean));
const endDate = DateTime.fromISO(data.to, { zone: 'utc' });
const months = Array.from({ length: 4 }, (_, index) =>
endDate.minus({ months: 11 - index * 4 }).toLocaleString({ month: 'short' }, { locale: $locale }),
);
return { rows, months };
});
const startDate = $derived(DateTime.fromISO(data.from, { zone: 'utc' }));
const padding = $derived(startDate.diff(startDate.startOf('week', { useLocaleWeeks: true })).as('days'));
const maxCount = $derived(Math.max(...data.series.map((item) => item.count), 0));
@@ -49,45 +38,47 @@
return 'bg-immich-primary';
};
// const dayLabels = $derived([
// '',
// dayOfWeek('monday', { locale: $locale, style: 'short' }),
// '',
// dayOfWeek('wednesday', { locale: $locale, style: 'short' }),
// '',
// dayOfWeek('friday', { locale: $locale, style: 'short' }),
// '',
// ]);
const weekdays = $derived([
Info.weekdays('short', { locale: $locale })[0],
Info.weekdays('short', { locale: $locale })[2],
Info.weekdays('short', { locale: $locale })[4],
Info.weekdays('short', { locale: $locale })[6],
]);
</script>
<div class="mt-4 w-full">
<div class="relative w-full">
<!-- TODO -->
<!-- <div class="absolute top-4 left-0 flex flex-col gap-0.5">
{#each dayLabels as dayLabel, i (i)}
<div class="relative flex h-3 w-6 items-center text-xs text-gray-500 dark:text-gray-400">
{dayLabel}
</div>
{/each}
</div> -->
<!-- <div class="mb-1 flex justify-between text-xs text-gray-500 dark:text-gray-400">
{#each getUploadActivityMonths() as month (month)}
<div>{month}</div>
{/each}
</div> -->
<div class="grid grid-rows-7 gap-0.5">
{#each rows as row, dayIndex (dayIndex)}
<div class="grid grid-cols-52 gap-0.5">
{#each row as day (day.date)}
<div
class="aspect-square w-full min-w-0 rounded-sm {itemColors(day.count)}"
title={itemLabel({ date: day.date, count: day.count })}
aria-label={itemLabel({ date: day.date, count: day.count })}
></div>
{/each}
</div>
<div class="grid grid-flow-col grid-rows-7 gap-0.5">
<div class="row-span-7 grid grid-rows-subgrid">
{#if Info.getStartOfWeek({ locale: $locale }) === 7}
<div></div>
{/if}
<div class="row-span-2 -mt-1"><Text size="tiny" class="mr-0.5 font-mono">{weekdays[0]}</Text></div>
<div class="row-span-2 -mt-1"><Text size="tiny" class="mr-0.5 font-mono">{weekdays[1]}</Text></div>
<div class="row-span-2 -mt-1"><Text size="tiny" class="mr-0.5 font-mono">{weekdays[2]}</Text></div>
{#if Info.getStartOfWeek({ locale: $locale }) === 1}
<div class="-my-1"><Text size="tiny" class="mr-0.5 font-mono">{weekdays[3]}</Text></div>
{/if}
</div>
{#each data.series as day, idx (day.date)}
{@const date = DateTime.fromISO(day.date, { zone: 'utc' }).toLocaleString(
{ month: 'short', day: 'numeric' },
{ locale: $locale },
)}
<div
class="aspect-square size-full rounded-sm {itemColors(day.count)} row-start-(--heatmap-row-start)"
style:--heatmap-row-start={idx === 0 ? padding + 1 : undefined}
title={itemLabel({ date, count: day.count })}
aria-label={itemLabel({ date, count: day.count })}
></div>
{/each}
</div>
+6
View File
@@ -9,6 +9,9 @@ export const getTriggerName = ($t: MessageFormatter, type: WorkflowTrigger) => {
// case WorkflowTrigger.PersonRecognized: {
// return $t('trigger_person_recognized');
// }
case WorkflowTrigger.AssetMetadataExtraction: {
return $t('trigger_asset_metadata_extraction');
}
default: {
return type;
}
@@ -23,6 +26,9 @@ export const getTriggerDescription = ($t: MessageFormatter, type: WorkflowTrigge
// case WorkflowTrigger.PersonRecognized: {
// return $t('trigger_person_recognized_description');
// }
case WorkflowTrigger.AssetMetadataExtraction: {
return $t('trigger_asset_metadata_extraction_description');
}
default: {
return type;
}