feat(web): remove upload file limit with rxjs and improve import size (#1743)

* feat(web): remove upload file limit with rxjs

* refactor: remove exif

* refactor: remove unused code

* fix: import lodash-es instead of lodash

* refactor: optimize import
This commit is contained in:
Alex 2023-02-13 13:18:11 -06:00 committed by GitHub
parent 37cfac27b8
commit 2c1aab154a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 62 additions and 66 deletions

23
web/package-lock.json generated
View File

@ -16,6 +16,7 @@
"leaflet": "^1.8.0", "leaflet": "^1.8.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"luxon": "^3.1.1", "luxon": "^3.1.1",
"rxjs": "^7.8.0",
"socket.io-client": "^4.5.1", "socket.io-client": "^4.5.1",
"svelte-material-icons": "^2.0.2" "svelte-material-icons": "^2.0.2"
}, },
@ -10148,6 +10149,14 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"node_modules/rxjs": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
"integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/sade": { "node_modules/sade": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@ -10842,8 +10851,7 @@
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
"dev": true
}, },
"node_modules/tsutils": { "node_modules/tsutils": {
"version": "3.21.0", "version": "3.21.0",
@ -18691,6 +18699,14 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"rxjs": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz",
"integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==",
"requires": {
"tslib": "^2.1.0"
}
},
"sade": { "sade": {
"version": "1.8.1", "version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@ -19193,8 +19209,7 @@
"tslib": { "tslib": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
"dev": true
}, },
"tsutils": { "tsutils": {
"version": "3.21.0", "version": "3.21.0",

View File

@ -63,11 +63,11 @@
"axios": "^0.27.2", "axios": "^0.27.2",
"cookie": "^0.4.2", "cookie": "^0.4.2",
"copy-image-clipboard": "^2.1.2", "copy-image-clipboard": "^2.1.2",
"exifr": "^7.1.3",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"leaflet": "^1.8.0", "leaflet": "^1.8.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"luxon": "^3.1.1", "luxon": "^3.1.1",
"rxjs": "^7.8.0",
"socket.io-client": "^4.5.1", "socket.io-client": "^4.5.1",
"svelte-material-icons": "^2.0.2" "svelte-material-icons": "^2.0.2"
} }

View File

@ -8,7 +8,7 @@
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte'; import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import SettingSelect from '../setting-select.svelte'; import SettingSelect from '../setting-select.svelte';
import SettingSwitch from '../setting-switch.svelte'; import SettingSwitch from '../setting-switch.svelte';
import _ from 'lodash'; import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
export let ffmpegConfig: SystemConfigFFmpegDto; // this is the config that is being edited export let ffmpegConfig: SystemConfigFFmpegDto; // this is the config that is being edited
@ -130,7 +130,7 @@
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={saveSetting}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!_.isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(savedConfig, defaultConfig)}
/> />
</div> </div>
</form> </form>

View File

@ -5,7 +5,7 @@
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { api, SystemConfigOAuthDto } from '@api'; import { api, SystemConfigOAuthDto } from '@api';
import _ from 'lodash'; import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import ConfirmDisableLogin from '../confirm-disable-login.svelte'; import ConfirmDisableLogin from '../confirm-disable-login.svelte';
import SettingButtonsRow from '../setting-buttons-row.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte';
@ -202,7 +202,7 @@
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={saveSetting}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!_.isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(savedConfig, defaultConfig)}
/> />
</form> </form>
</div> </div>

View File

@ -5,7 +5,7 @@
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { api, SystemConfigPasswordLoginDto } from '@api'; import { api, SystemConfigPasswordLoginDto } from '@api';
import _ from 'lodash'; import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import ConfirmDisableLogin from '../confirm-disable-login.svelte'; import ConfirmDisableLogin from '../confirm-disable-login.svelte';
import SettingButtonsRow from '../setting-buttons-row.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte';
@ -109,7 +109,7 @@
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={saveSetting}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!_.isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(savedConfig, defaultConfig)}
/> />
</div> </div>
</div> </div>

View File

@ -12,7 +12,7 @@
import SupportedDatetimePanel from './supported-datetime-panel.svelte'; import SupportedDatetimePanel from './supported-datetime-panel.svelte';
import SupportedVariablesPanel from './supported-variables-panel.svelte'; import SupportedVariablesPanel from './supported-variables-panel.svelte';
import SettingButtonsRow from '../setting-buttons-row.svelte'; import SettingButtonsRow from '../setting-buttons-row.svelte';
import _ from 'lodash'; import { isEqual } from 'lodash-es';
import { import {
notificationController, notificationController,
NotificationType NotificationType
@ -230,7 +230,7 @@
on:reset={reset} on:reset={reset}
on:save={saveSetting} on:save={saveSetting}
on:reset-to-default={resetToDefault} on:reset-to-default={resetToDefault}
showResetToDefault={!_.isEqual(savedConfig, defaultConfig)} showResetToDefault={!isEqual(savedConfig, defaultConfig)}
/> />
</form> </form>
</div> </div>

View File

@ -4,7 +4,7 @@
import CircleOutline from 'svelte-material-icons/CircleOutline.svelte'; import CircleOutline from 'svelte-material-icons/CircleOutline.svelte';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { AssetResponseDto } from '@api'; import { AssetResponseDto } from '@api';
import lodash from 'lodash-es'; import { chain } from 'lodash-es';
import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte'; import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte';
import { import {
assetInteractionStore, assetInteractionStore,
@ -29,8 +29,7 @@
let isMouseOverGroup = false; let isMouseOverGroup = false;
let actualBucketHeight: number; let actualBucketHeight: number;
let hoveredDateGroup = ''; let hoveredDateGroup = '';
$: assetsGroupByDate = lodash $: assetsGroupByDate = chain(assets)
.chain(assets)
.groupBy((a) => new Date(a.createdAt).toLocaleDateString(locale, groupDateFormat)) .groupBy((a) => new Date(a.createdAt).toLocaleDateString(locale, groupDateFormat))
.sortBy((group) => assets.indexOf(group[0])) .sortBy((group) => assets.indexOf(group[0]))
.value(); .value();

View File

@ -2,7 +2,7 @@ import { AssetGridState } from '$lib/models/asset-grid-state';
import { api, AssetResponseDto } from '@api'; import { api, AssetResponseDto } from '@api';
import { derived, writable } from 'svelte/store'; import { derived, writable } from 'svelte/store';
import { assetGridState, assetStore } from './assets.store'; import { assetGridState, assetStore } from './assets.store';
import _ from 'lodash-es'; import { sortBy } from 'lodash-es';
// Asset Viewer // Asset Viewer
export const viewingAssetStoreState = writable<AssetResponseDto>(); export const viewingAssetStoreState = writable<AssetResponseDto>();
@ -65,7 +65,7 @@ function createAssetInteractionStore() {
const navigateAsset = async (direction: 'next' | 'previous') => { const navigateAsset = async (direction: 'next' | 'previous') => {
// Flatten and sort the asset by date if there are new assets // Flatten and sort the asset by date if there are new assets
if (assetSortedByDate.length === 0 || savedAssetLength !== _assetGridState.assets.length) { if (assetSortedByDate.length === 0 || savedAssetLength !== _assetGridState.assets.length) {
assetSortedByDate = _.sortBy(_assetGridState.assets, (a) => a.createdAt); assetSortedByDate = sortBy(_assetGridState.assets, (a) => a.createdAt);
savedAssetLength = _assetGridState.assets.length; savedAssetLength = _assetGridState.assets.length;
} }

View File

@ -1,7 +1,7 @@
import { AssetGridState } from '$lib/models/asset-grid-state'; import { AssetGridState } from '$lib/models/asset-grid-state';
import { calculateViewportHeightByNumberOfAsset } from '$lib/utils/viewport-utils'; import { calculateViewportHeightByNumberOfAsset } from '$lib/utils/viewport-utils';
import { api, AssetCountByTimeBucketResponseDto } from '@api'; import { api, AssetCountByTimeBucketResponseDto } from '@api';
import lodash from 'lodash-es'; import { sumBy, flatMap } from 'lodash-es';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
/** /**
@ -46,7 +46,7 @@ function createAssetStore() {
// Update timeline height based on calculated bucket height // Update timeline height based on calculated bucket height
assetGridState.update((state) => { assetGridState.update((state) => {
state.timelineHeight = lodash.sumBy(state.buckets, (d) => d.bucketHeight); state.timelineHeight = sumBy(state.buckets, (d) => d.bucketHeight);
return state; return state;
}); });
}; };
@ -77,7 +77,7 @@ function createAssetStore() {
assetGridState.update((state) => { assetGridState.update((state) => {
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket); const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucket);
state.buckets[bucketIndex].assets = assets; state.buckets[bucketIndex].assets = assets;
state.assets = lodash.flatMap(state.buckets, (b) => b.assets); state.assets = flatMap(state.buckets, (b) => b.assets);
return state; return state;
}); });
@ -100,7 +100,7 @@ function createAssetStore() {
if (state.buckets[bucketIndex].assets.length === 0) { if (state.buckets[bucketIndex].assets.length === 0) {
_removeBucket(state.buckets[bucketIndex].bucketDate); _removeBucket(state.buckets[bucketIndex].bucketDate);
} }
state.assets = lodash.flatMap(state.buckets, (b) => b.assets); state.assets = flatMap(state.buckets, (b) => b.assets);
return state; return state;
}); });
}; };
@ -109,7 +109,7 @@ function createAssetStore() {
assetGridState.update((state) => { assetGridState.update((state) => {
const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucketDate); const bucketIndex = state.buckets.findIndex((b) => b.bucketDate === bucketDate);
state.buckets.splice(bucketIndex, 1); state.buckets.splice(bucketIndex, 1);
state.assets = lodash.flatMap(state.buckets, (b) => b.assets); state.assets = flatMap(state.buckets, (b) => b.assets);
return state; return state;
}); });
}; };
@ -147,7 +147,7 @@ function createAssetStore() {
const assetIndex = state.buckets[bucketIndex].assets.findIndex((a) => a.id === assetId); const assetIndex = state.buckets[bucketIndex].assets.findIndex((a) => a.id === assetId);
state.buckets[bucketIndex].assets[assetIndex].isFavorite = isFavorite; state.buckets[bucketIndex].assets[assetIndex].isFavorite = isFavorite;
state.assets = lodash.flatMap(state.buckets, (b) => b.assets); state.assets = flatMap(state.buckets, (b) => b.assets);
return state; return state;
}); });
}; };

View File

@ -2,12 +2,11 @@ import {
notificationController, notificationController,
NotificationType NotificationType
} from './../components/shared-components/notification/notification'; } from './../components/shared-components/notification/notification';
/* @vite-ignore */
import * as exifr from 'exifr';
import { uploadAssetsStore } from '$lib/stores/upload'; import { uploadAssetsStore } from '$lib/stores/upload';
import type { UploadAsset } from '../models/upload-asset'; import type { UploadAsset } from '../models/upload-asset';
import { api, AssetFileUploadResponseDto } from '@api'; import { api, AssetFileUploadResponseDto } from '@api';
import { addAssetsToAlbum, getFileMimeType, getFilenameExtension } from '$lib/utils/asset-utils'; import { addAssetsToAlbum, getFileMimeType, getFilenameExtension } from '$lib/utils/asset-utils';
import { Subject, mergeMap } from 'rxjs';
export const openFileUploadDialog = ( export const openFileUploadDialog = (
albumId: string | undefined = undefined, albumId: string | undefined = undefined,
@ -46,25 +45,20 @@ export const fileUploadHandler = async (
sharedKey: string | undefined = undefined, sharedKey: string | undefined = undefined,
onDone?: (id: string) => void onDone?: (id: string) => void
) => { ) => {
if (files.length > 50) { const files$ = new Subject<File>();
notificationController.show({ files$
type: NotificationType.Error, .pipe(
message: `Cannot upload more than 50 files at a time - you are uploading ${files.length} files. mergeMap(async (file) => {
Please check out <u>the bulk upload documentation</u> if you need to upload more than 50 files.`, await fileUploader(file, albumId, sharedKey, onDone);
timeout: 10000, }, 2)
action: { type: 'link', target: 'https://immich.app/docs/features/bulk-upload' } )
}); .subscribe();
return;
}
const acceptedFile = files.filter((file) => { const acceptedFile = files.filter((file) => {
const assetType = getFileMimeType(file).split('/')[0]; const assetType = getFileMimeType(file).split('/')[0];
return assetType === 'video' || assetType === 'image'; return assetType === 'video' || assetType === 'image';
}); });
for (const file of acceptedFile) {
for (const asset of acceptedFile) { files$.next(file);
await fileUploader(asset, albumId, sharedKey, onDone);
} }
}; };
@ -75,25 +69,15 @@ async function fileUploader(
sharedKey: string | undefined = undefined, sharedKey: string | undefined = undefined,
onDone?: (id: string) => void onDone?: (id: string) => void
) { ) {
console.log('uploading', asset.name);
const mimeType = getFileMimeType(asset); const mimeType = getFileMimeType(asset);
const assetType = mimeType.split('/')[0].toUpperCase(); const assetType = mimeType.split('/')[0].toUpperCase();
const fileExtension = getFilenameExtension(asset.name); const fileExtension = getFilenameExtension(asset.name);
const formData = new FormData(); const formData = new FormData();
const createdAt = new Date(asset.lastModified).toISOString();
try {
let exifData = null;
if (assetType !== 'VIDEO') {
exifData = await exifr.parse(asset).catch((e) => console.log('error parsing exif', e));
}
const createdAt =
exifData && exifData.DateTimeOriginal != null
? new Date(exifData.DateTimeOriginal).toISOString()
: new Date(asset.lastModified).toISOString();
const deviceAssetId = 'web' + '-' + asset.name + '-' + asset.lastModified; const deviceAssetId = 'web' + '-' + asset.name + '-' + asset.lastModified;
try {
// Create and add Unique ID of asset on the device // Create and add Unique ID of asset on the device
formData.append('deviceAssetId', deviceAssetId); formData.append('deviceAssetId', deviceAssetId);
@ -160,7 +144,6 @@ async function fileUploader(
}; };
request.upload.onload = () => { request.upload.onload = () => {
setTimeout(() => {
uploadAssetsStore.removeUploadAsset(deviceAssetId); uploadAssetsStore.removeUploadAsset(deviceAssetId);
const res: AssetFileUploadResponseDto = JSON.parse(request.response || '{}'); const res: AssetFileUploadResponseDto = JSON.parse(request.response || '{}');
if (albumId) { if (albumId) {
@ -173,7 +156,6 @@ async function fileUploader(
} }
} }
onDone && onDone(res.id); onDone && onDone(res.id);
}, 1000);
}; };
// listen for `error` event // listen for `error` event