immich/web/src/lib/utils/timeline-util.ts

234 lines
8.2 KiB
TypeScript
Raw Normal View History

import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { locale } from '$lib/stores/preferences.store';
import { getAssetRatio } from '$lib/utils/asset-utils';
import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
import { DateTime, type LocaleOptions } from 'luxon';
import { SvelteSet } from 'svelte/reactivity';
import { get } from 'svelte/store';
// Move type definitions to the top
export type TimelineYearMonth = {
year: number;
month: number;
};
export type TimelineDate = TimelineYearMonth & {
day: number;
};
export type TimelineDateTime = TimelineDate & {
hour: number;
minute: number;
second: number;
millisecond: number;
};
export type ScrubberListener = (
refactor: timeline manager renames (#19007) * refactor: timeline manager renames * refactor(web): improve timeline manager naming consistency - Rename AddContext → GroupInsertionCache for clearer purpose - Rename TimelineDay → DayGroup for better clarity - Rename TimelineMonth → MonthGroup for better clarity - Replace all "bucket" references with "monthGroup" terminology - Update all component props, method names, and variable references - Maintain consistent naming patterns across TypeScript and Svelte files * refactor(web): rename buckets to months in timeline manager - Rename TimelineManager.buckets property to months - Update all store.buckets references to store.months - Use 'month' shorthand for monthGroup arguments (not method names) - Update component templates and test files for consistency - Maintain API-related 'bucket' terminology (bucketHeight, getTimeBucket) * refactor(web): rename assetStore to timelineManager and update types - Rename assetStore variables to timelineManager in all .svelte files - Update parameter names in actions.ts and asset-utils.ts functions - Rename AssetStoreLayoutOptions to TimelineManagerLayoutOptions - Rename AssetStoreOptions to TimelineManagerOptions - Move assets-store.spec.ts to timeline-manager.spec.ts * refactor(web): rename intersectingAssets to viewerAssets and fix property references - Rename intersectingAssets to viewerAssets in DayGroup and MonthGroup classes - Update arrow function parameters to use viewerAsset/viewAsset shorthand - Rename topIntersectingBucket to topIntersectingMonthGroup - Fix dateGroups references to dayGroups in asset-utils.ts and album page - Update template loops and variable names in Svelte components * refactor(web): rename #initializeTimeBuckets to #initializeMonthGroups and bucketDateFormatted to monthGroupTitle * refactor(web): rename monthGroupHeight to height * refactor(web): rename bucketCount to assetsCount, bucketsIterator to monthGroupIterator, and related properties * refactor(web): rename count to assetCount in TimelineManager * refactor(web): rename LiteBucket to ScrubberMonth and update scrubber variables - Rename LiteBucket type to ScrubberMonth - Rename bucketDateFormattted to title in ScrubberMonth type - Rename bucketPercentY to monthGroupPercentY in scrubber component - Rename scrubBucket to scrubberMonth and scrubBucketPercent to scrubberMonthPercent * fix remaining refs to bucket * reset submodule to correct commit * reset submodule to correct commit * refactor(web): extract TimelineManager internals into separate modules - Move search-related functions to internal/search-support.svelte.ts - Extract websocket event handling into WebsocketSupport class - Move utility functions (updateObject, isMismatched) to internal/utils.svelte.ts - Update imports in tests to use new module structure * refactor(web): extract intersection logic from TimelineManager - Create intersection-support.svelte.ts with updateIntersection and calculateIntersecting functions - Remove private intersection methods from TimelineManager - Export findMonthGroupForAsset from search-support for reuse - Update TimelineManager to use the extracted intersection functions * refactor(web): rename a few methods in intersecting * refactor(web): rename a few methods in intersecting * refactor(web): extract layout logic from TimelineManager - Create layout-support.svelte.ts with updateGeometry and layoutMonthGroup functions - Remove private layout methods from TimelineManager - Update TimelineManager to use the extracted layout functions - Remove unused UpdateGeometryOptions import * refactor(web): extract asset operations from TimelineManager - Create operations-support.svelte.ts with addAssetsToMonthGroups and runAssetOperation functions - Remove private asset operation methods from TimelineManager - Update TimelineManager to use extracted operation functions with proper AssetOrder handling - Rename getMonthGroupIndexByAssetId to getMonthGroupByAssetId for consistency - Move utility functions from utils.svelte.ts to internal/utils.svelte.ts - Fix method name references in asset-grid.svelte and tests * refactor(web): extract loading logic from TimelineManager - Create load-support.svelte.ts with loadFromTimeBuckets function - Extract time bucket loading, album asset handling, and error logging - Simplify TimelineManager's loadMonthGroup method to use extracted function * refresh timeline after archive keyboard shortcut * remove debugger * rename * Review comments - remove shadowed var * reduce indents - early return * review comment * refactor: simplify asset filtering in addAssets method Replace for loop with filter operation for better readability * fix: bad merge * refactor(web): simplify timeline layout algorithm - Replace rowSpaceRemaining array with direct cumulative width tracking - Invert logic from tracking remaining space to tracking used space - Fix spelling: cummulative to cumulative - Rename lastRowHeight to currentRowHeight for clarity - Remove confusing lastRow variable and simplify final height calculation - Add explanatory comments for clarity - Rename loop variable assetGroup to dayGroup for consistency * simplify assetsIterator usage * merge/lint --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-06-10 10:30:13 -04:00
scrubberMonth: { year: number; month: number },
overallScrollPercent: number,
refactor: timeline manager renames (#19007) * refactor: timeline manager renames * refactor(web): improve timeline manager naming consistency - Rename AddContext → GroupInsertionCache for clearer purpose - Rename TimelineDay → DayGroup for better clarity - Rename TimelineMonth → MonthGroup for better clarity - Replace all "bucket" references with "monthGroup" terminology - Update all component props, method names, and variable references - Maintain consistent naming patterns across TypeScript and Svelte files * refactor(web): rename buckets to months in timeline manager - Rename TimelineManager.buckets property to months - Update all store.buckets references to store.months - Use 'month' shorthand for monthGroup arguments (not method names) - Update component templates and test files for consistency - Maintain API-related 'bucket' terminology (bucketHeight, getTimeBucket) * refactor(web): rename assetStore to timelineManager and update types - Rename assetStore variables to timelineManager in all .svelte files - Update parameter names in actions.ts and asset-utils.ts functions - Rename AssetStoreLayoutOptions to TimelineManagerLayoutOptions - Rename AssetStoreOptions to TimelineManagerOptions - Move assets-store.spec.ts to timeline-manager.spec.ts * refactor(web): rename intersectingAssets to viewerAssets and fix property references - Rename intersectingAssets to viewerAssets in DayGroup and MonthGroup classes - Update arrow function parameters to use viewerAsset/viewAsset shorthand - Rename topIntersectingBucket to topIntersectingMonthGroup - Fix dateGroups references to dayGroups in asset-utils.ts and album page - Update template loops and variable names in Svelte components * refactor(web): rename #initializeTimeBuckets to #initializeMonthGroups and bucketDateFormatted to monthGroupTitle * refactor(web): rename monthGroupHeight to height * refactor(web): rename bucketCount to assetsCount, bucketsIterator to monthGroupIterator, and related properties * refactor(web): rename count to assetCount in TimelineManager * refactor(web): rename LiteBucket to ScrubberMonth and update scrubber variables - Rename LiteBucket type to ScrubberMonth - Rename bucketDateFormattted to title in ScrubberMonth type - Rename bucketPercentY to monthGroupPercentY in scrubber component - Rename scrubBucket to scrubberMonth and scrubBucketPercent to scrubberMonthPercent * fix remaining refs to bucket * reset submodule to correct commit * reset submodule to correct commit * refactor(web): extract TimelineManager internals into separate modules - Move search-related functions to internal/search-support.svelte.ts - Extract websocket event handling into WebsocketSupport class - Move utility functions (updateObject, isMismatched) to internal/utils.svelte.ts - Update imports in tests to use new module structure * refactor(web): extract intersection logic from TimelineManager - Create intersection-support.svelte.ts with updateIntersection and calculateIntersecting functions - Remove private intersection methods from TimelineManager - Export findMonthGroupForAsset from search-support for reuse - Update TimelineManager to use the extracted intersection functions * refactor(web): rename a few methods in intersecting * refactor(web): rename a few methods in intersecting * refactor(web): extract layout logic from TimelineManager - Create layout-support.svelte.ts with updateGeometry and layoutMonthGroup functions - Remove private layout methods from TimelineManager - Update TimelineManager to use the extracted layout functions - Remove unused UpdateGeometryOptions import * refactor(web): extract asset operations from TimelineManager - Create operations-support.svelte.ts with addAssetsToMonthGroups and runAssetOperation functions - Remove private asset operation methods from TimelineManager - Update TimelineManager to use extracted operation functions with proper AssetOrder handling - Rename getMonthGroupIndexByAssetId to getMonthGroupByAssetId for consistency - Move utility functions from utils.svelte.ts to internal/utils.svelte.ts - Fix method name references in asset-grid.svelte and tests * refactor(web): extract loading logic from TimelineManager - Create load-support.svelte.ts with loadFromTimeBuckets function - Extract time bucket loading, album asset handling, and error logging - Simplify TimelineManager's loadMonthGroup method to use extracted function * refresh timeline after archive keyboard shortcut * remove debugger * rename * Review comments - remove shadowed var * reduce indents - early return * review comment * refactor: simplify asset filtering in addAssets method Replace for loop with filter operation for better readability * fix: bad merge * refactor(web): simplify timeline layout algorithm - Replace rowSpaceRemaining array with direct cumulative width tracking - Invert logic from tracking remaining space to tracking used space - Fix spelling: cummulative to cumulative - Rename lastRowHeight to currentRowHeight for clarity - Remove confusing lastRow variable and simplify final height calculation - Add explanatory comments for clarity - Rename loop variable assetGroup to dayGroup for consistency * simplify assetsIterator usage * merge/lint --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-06-10 10:30:13 -04:00
scrubberMonthScrollPercent: number,
) => void | Promise<void>;
// used for AssetResponseDto.dateTimeOriginal, amongst others
export const fromISODateTime = (isoDateTime: string, timeZone: string): DateTime<true> =>
DateTime.fromISO(isoDateTime, { zone: timeZone, locale: get(locale) }) as DateTime<true>;
export const fromISODateTimeToObject = (isoDateTime: string, timeZone: string): TimelineDateTime =>
(fromISODateTime(isoDateTime, timeZone) as DateTime<true>).toObject();
// used for AssetResponseDto.localDateTime, amongst others
export const fromISODateTimeUTC = (isoDateTimeUtc: string) => fromISODateTime(isoDateTimeUtc, 'UTC');
export const fromISODateTimeUTCToObject = (isoDateTimeUtc: string): TimelineDateTime =>
(fromISODateTimeUTC(isoDateTimeUtc) as DateTime<true>).toObject();
// used to create equivalent of AssetResponseDto.localDateTime in UTC, but without timezone information
export const fromISODateTimeTruncateTZToObject = (
isoDateTimeUtc: string,
timeZone: string | undefined,
): TimelineDateTime =>
(
fromISODateTime(isoDateTimeUtc, timeZone ?? 'UTC').setZone('UTC', { keepLocalTime: true }) as DateTime<true>
).toObject();
// Used to derive a local date time from an ISO string and a UTC offset in hours
export const fromISODateTimeWithOffsetToObject = (isoDateTimeUtc: string, utcOffsetHours: number): TimelineDateTime => {
const utcDateTime = fromISODateTimeUTC(isoDateTimeUtc);
// Apply the offset to get the local time
// Note: offset is in hours (may be fractional), positive for east of UTC, negative for west
const localDateTime = utcDateTime.plus({ hours: utcOffsetHours });
// Return as plain object (keeping the local time but in UTC zone context)
return (localDateTime.setZone('UTC', { keepLocalTime: true }) as DateTime<true>).toObject();
};
export const getTimes = (isoDateTimeUtc: string, localUtcOffsetHours: number) => {
const utcDateTime = fromISODateTimeUTC(isoDateTimeUtc);
const fileCreatedAt = (utcDateTime as DateTime<true>).toObject();
// Apply the offset to get the local time
// Note: offset is in hours (may be fractional), positive for east of UTC, negative for west
const luxonLocalDateTime = utcDateTime.plus({ hours: localUtcOffsetHours });
// Return as plain object (keeping the local time but in UTC zone context)
const localDateTime = (luxonLocalDateTime.setZone('UTC', { keepLocalTime: true }) as DateTime<true>).toObject();
return {
fileCreatedAt,
localDateTime,
};
};
export const fromTimelinePlainDateTime = (timelineDateTime: TimelineDateTime): DateTime<true> =>
DateTime.fromObject(timelineDateTime, { zone: 'local', locale: get(locale) }) as DateTime<true>;
export const fromTimelinePlainDate = (timelineYearMonth: TimelineDate): DateTime<true> =>
DateTime.fromObject(
{ year: timelineYearMonth.year, month: timelineYearMonth.month, day: timelineYearMonth.day },
{ zone: 'local', locale: get(locale) },
) as DateTime<true>;
export const fromTimelinePlainYearMonth = (timelineYearMonth: TimelineYearMonth): DateTime<true> =>
DateTime.fromObject(
{ year: timelineYearMonth.year, month: timelineYearMonth.month },
{ zone: 'local', locale: get(locale) },
) as DateTime<true>;
export const toISOYearMonthUTC = ({ year, month }: TimelineYearMonth): string =>
`${year}-${month.toString().padStart(2, '0')}-01T00:00:00.000Z`;
refactor: timeline manager renames (#19007) * refactor: timeline manager renames * refactor(web): improve timeline manager naming consistency - Rename AddContext → GroupInsertionCache for clearer purpose - Rename TimelineDay → DayGroup for better clarity - Rename TimelineMonth → MonthGroup for better clarity - Replace all "bucket" references with "monthGroup" terminology - Update all component props, method names, and variable references - Maintain consistent naming patterns across TypeScript and Svelte files * refactor(web): rename buckets to months in timeline manager - Rename TimelineManager.buckets property to months - Update all store.buckets references to store.months - Use 'month' shorthand for monthGroup arguments (not method names) - Update component templates and test files for consistency - Maintain API-related 'bucket' terminology (bucketHeight, getTimeBucket) * refactor(web): rename assetStore to timelineManager and update types - Rename assetStore variables to timelineManager in all .svelte files - Update parameter names in actions.ts and asset-utils.ts functions - Rename AssetStoreLayoutOptions to TimelineManagerLayoutOptions - Rename AssetStoreOptions to TimelineManagerOptions - Move assets-store.spec.ts to timeline-manager.spec.ts * refactor(web): rename intersectingAssets to viewerAssets and fix property references - Rename intersectingAssets to viewerAssets in DayGroup and MonthGroup classes - Update arrow function parameters to use viewerAsset/viewAsset shorthand - Rename topIntersectingBucket to topIntersectingMonthGroup - Fix dateGroups references to dayGroups in asset-utils.ts and album page - Update template loops and variable names in Svelte components * refactor(web): rename #initializeTimeBuckets to #initializeMonthGroups and bucketDateFormatted to monthGroupTitle * refactor(web): rename monthGroupHeight to height * refactor(web): rename bucketCount to assetsCount, bucketsIterator to monthGroupIterator, and related properties * refactor(web): rename count to assetCount in TimelineManager * refactor(web): rename LiteBucket to ScrubberMonth and update scrubber variables - Rename LiteBucket type to ScrubberMonth - Rename bucketDateFormattted to title in ScrubberMonth type - Rename bucketPercentY to monthGroupPercentY in scrubber component - Rename scrubBucket to scrubberMonth and scrubBucketPercent to scrubberMonthPercent * fix remaining refs to bucket * reset submodule to correct commit * reset submodule to correct commit * refactor(web): extract TimelineManager internals into separate modules - Move search-related functions to internal/search-support.svelte.ts - Extract websocket event handling into WebsocketSupport class - Move utility functions (updateObject, isMismatched) to internal/utils.svelte.ts - Update imports in tests to use new module structure * refactor(web): extract intersection logic from TimelineManager - Create intersection-support.svelte.ts with updateIntersection and calculateIntersecting functions - Remove private intersection methods from TimelineManager - Export findMonthGroupForAsset from search-support for reuse - Update TimelineManager to use the extracted intersection functions * refactor(web): rename a few methods in intersecting * refactor(web): rename a few methods in intersecting * refactor(web): extract layout logic from TimelineManager - Create layout-support.svelte.ts with updateGeometry and layoutMonthGroup functions - Remove private layout methods from TimelineManager - Update TimelineManager to use the extracted layout functions - Remove unused UpdateGeometryOptions import * refactor(web): extract asset operations from TimelineManager - Create operations-support.svelte.ts with addAssetsToMonthGroups and runAssetOperation functions - Remove private asset operation methods from TimelineManager - Update TimelineManager to use extracted operation functions with proper AssetOrder handling - Rename getMonthGroupIndexByAssetId to getMonthGroupByAssetId for consistency - Move utility functions from utils.svelte.ts to internal/utils.svelte.ts - Fix method name references in asset-grid.svelte and tests * refactor(web): extract loading logic from TimelineManager - Create load-support.svelte.ts with loadFromTimeBuckets function - Extract time bucket loading, album asset handling, and error logging - Simplify TimelineManager's loadMonthGroup method to use extracted function * refresh timeline after archive keyboard shortcut * remove debugger * rename * Review comments - remove shadowed var * reduce indents - early return * review comment * refactor: simplify asset filtering in addAssets method Replace for loop with filter operation for better readability * fix: bad merge * refactor(web): simplify timeline layout algorithm - Replace rowSpaceRemaining array with direct cumulative width tracking - Invert logic from tracking remaining space to tracking used space - Fix spelling: cummulative to cumulative - Rename lastRowHeight to currentRowHeight for clarity - Remove confusing lastRow variable and simplify final height calculation - Add explanatory comments for clarity - Rename loop variable assetGroup to dayGroup for consistency * simplify assetsIterator usage * merge/lint --------- Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-06-10 10:30:13 -04:00
export function formatMonthGroupTitle(_date: DateTime): string {
if (!_date.isValid) {
return _date.toString();
}
const date = _date as DateTime<true>;
return date.toLocaleString(
{
month: 'short',
year: 'numeric',
},
{ locale: get(locale) },
);
}
export function formatGroupTitle(_date: DateTime): string {
if (!_date.isValid) {
return _date.toString();
}
const date = _date as DateTime<true>;
const today = DateTime.now().startOf('day');
// Today
if (today.hasSame(date, 'day')) {
return date.toRelativeCalendar({ locale: get(locale) });
}
// Yesterday
if (today.minus({ days: 1 }).hasSame(date, 'day')) {
return date.toRelativeCalendar({ locale: get(locale) });
}
// Last week
if (date >= today.minus({ days: 6 }) && date < today) {
return date.toLocaleString({ weekday: 'long' }, { locale: get(locale) });
}
// This year
if (today.hasSame(date, 'year')) {
return date.toLocaleString(
{
weekday: 'short',
month: 'short',
day: 'numeric',
},
{ locale: get(locale) },
);
}
return getDateLocaleString(date, { locale: get(locale) });
}
export const getDateLocaleString = (date: DateTime, opts?: LocaleOptions): string =>
date.toLocaleString(DateTime.DATE_MED_WITH_WEEKDAY, opts);
export const getDateTimeOffsetLocaleString = (date: DateTime, opts?: LocaleOptions): string =>
date.toLocaleString(
{ year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZoneName: 'longOffset' },
opts,
);
export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): TimelineAsset => {
if (isTimelineAsset(unknownAsset)) {
return unknownAsset;
}
feat(server): lighter buckets (#17831) * feat(web): lighter timeline buckets * GalleryViewer * weird ssr * Remove generics from AssetInteraction * ensure keys on getAssetInfo, alt-text * empty - trigger ci * re-add alt-text * test fix * update tests * tests * missing import * feat(server): lighter buckets * fix: flappy e2e test * lint * revert settings * unneeded cast * fix after merge * Adapt web client to consume new server response format * test * missing import * lint * Use nulls, make-sql * openapi battle * date->string * tests * tests * lint/tests * lint * test * push aggregation to query * openapi * stack as tuple * openapi * update references to description * update alt text tests * update sql * update sql * update timeline tests * linting, fix expected response * string tuple * fix spec * fix * silly generator * rename patch * minimize sorting * review * lint * lint * sql * test * avoid abbreviations * review comment - type safety in test * merge conflicts * lint * lint/abbreviations * remove unncessary code * review comments * sql * re-add package-lock * use booleans, fix visibility in openapi spec, less cursed controller * update sql * no need to use sql template * array access actually doesn't seem to matter * remove redundant code * re-add sql decorator * unused type * remove null assertions * bad merge * Fix test * shave * extra clean shave * use decorator for content type * redundant types * redundant comment * update comment * unnecessary res --------- Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-05-19 17:40:48 -04:00
const assetResponse = unknownAsset;
const { width, height } = getAssetRatio(assetResponse);
const ratio = width / height;
const city = assetResponse.exifInfo?.city;
const country = assetResponse.exifInfo?.country;
const people = assetResponse.people?.map((person) => person.name) || [];
const localDateTime = fromISODateTimeUTCToObject(assetResponse.localDateTime);
const fileCreatedAt = fromISODateTimeToObject(assetResponse.fileCreatedAt, assetResponse.exifInfo?.timeZone ?? 'UTC');
return {
id: assetResponse.id,
ownerId: assetResponse.ownerId,
ratio,
thumbhash: assetResponse.thumbhash,
localDateTime,
fileCreatedAt,
isFavorite: assetResponse.isFavorite,
visibility: assetResponse.visibility,
isTrashed: assetResponse.isTrashed,
isVideo: assetResponse.type == AssetTypeEnum.Video,
isImage: assetResponse.type == AssetTypeEnum.Image,
stack: assetResponse.stack || null,
duration: assetResponse.duration || null,
projectionType: assetResponse.exifInfo?.projectionType || null,
livePhotoVideoId: assetResponse.livePhotoVideoId || null,
feat(server): lighter buckets (#17831) * feat(web): lighter timeline buckets * GalleryViewer * weird ssr * Remove generics from AssetInteraction * ensure keys on getAssetInfo, alt-text * empty - trigger ci * re-add alt-text * test fix * update tests * tests * missing import * feat(server): lighter buckets * fix: flappy e2e test * lint * revert settings * unneeded cast * fix after merge * Adapt web client to consume new server response format * test * missing import * lint * Use nulls, make-sql * openapi battle * date->string * tests * tests * lint/tests * lint * test * push aggregation to query * openapi * stack as tuple * openapi * update references to description * update alt text tests * update sql * update sql * update timeline tests * linting, fix expected response * string tuple * fix spec * fix * silly generator * rename patch * minimize sorting * review * lint * lint * sql * test * avoid abbreviations * review comment - type safety in test * merge conflicts * lint * lint/abbreviations * remove unncessary code * review comments * sql * re-add package-lock * use booleans, fix visibility in openapi spec, less cursed controller * update sql * no need to use sql template * array access actually doesn't seem to matter * remove redundant code * re-add sql decorator * unused type * remove null assertions * bad merge * Fix test * shave * extra clean shave * use decorator for content type * redundant types * redundant comment * update comment * unnecessary res --------- Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-05-19 17:40:48 -04:00
city: city || null,
country: country || null,
people,
latitude: assetResponse.exifInfo?.latitude || null,
longitude: assetResponse.exifInfo?.longitude || null,
};
};
export const isTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): unknownAsset is TimelineAsset =>
(unknownAsset as TimelineAsset).ratio !== undefined;
export const plainDateTimeCompare = (ascending: boolean, a: TimelineDateTime, b: TimelineDateTime) => {
const [aDateTime, bDateTime] = ascending ? [a, b] : [b, a];
if (aDateTime.year !== bDateTime.year) {
return aDateTime.year - bDateTime.year;
}
if (aDateTime.month !== bDateTime.month) {
return aDateTime.month - bDateTime.month;
}
if (aDateTime.day !== bDateTime.day) {
return aDateTime.day - bDateTime.day;
}
if (aDateTime.hour !== bDateTime.hour) {
return aDateTime.hour - bDateTime.hour;
}
if (aDateTime.minute !== bDateTime.minute) {
return aDateTime.minute - bDateTime.minute;
}
if (aDateTime.second !== bDateTime.second) {
return aDateTime.second - bDateTime.second;
}
return aDateTime.millisecond - bDateTime.millisecond;
};
export function setDifference<T>(setA: Set<T>, setB: Set<T>): SvelteSet<T> {
const result = new SvelteSet<T>();
for (const value of setA) {
if (!setB.has(value)) {
result.add(value);
}
}
return result;
}