Don't overwhelm server with delete calls
parent
2fff228777
commit
69fb546cf2
|
@ -224,7 +224,7 @@ export default class Photo extends Mixins(GlobalMixin) {
|
||||||
&.exit-left {
|
&.exit-left {
|
||||||
transition: all 0.2s ease-in;
|
transition: all 0.2s ease-in;
|
||||||
transform: translateX(-20%);
|
transform: translateX(-20%);
|
||||||
opacity: 0.4;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
&.enter-right {
|
&.enter-right {
|
||||||
animation: enter-right 0.2s ease-out forwards;
|
animation: enter-right 0.2s ease-out forwards;
|
||||||
|
@ -232,7 +232,7 @@ export default class Photo extends Mixins(GlobalMixin) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes enter-right {
|
@keyframes enter-right {
|
||||||
from { transform: translateX(20%); opacity: 0.4; }
|
from { transform: translateX(20%); opacity: 0.8; }
|
||||||
to { transform: translateX(0); opacity: 1; }
|
to { transform: translateX(0); opacity: 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -870,14 +870,13 @@ export default class Timeline extends Mixins(GlobalMixin) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clear all selected photos */
|
/** Clear all selected photos */
|
||||||
clearSelection() {
|
clearSelection(only?: Set<IPhoto>) {
|
||||||
const heads = new Set<IHeadRow>();
|
const heads = new Set<IHeadRow>();
|
||||||
for (const photo of this.selection) {
|
new Set(only || this.selection).forEach((photo: IPhoto) => {
|
||||||
photo.flag &= ~this.c.FLAG_SELECTED;
|
photo.flag &= ~this.c.FLAG_SELECTED;
|
||||||
heads.add(this.heads[photo.d.dayid]);
|
heads.add(this.heads[photo.d.dayid]);
|
||||||
}
|
this.selection.delete(photo);
|
||||||
|
});
|
||||||
this.selection.clear();
|
|
||||||
heads.forEach(this.updateHeadSelected);
|
heads.forEach(this.updateHeadSelected);
|
||||||
this.$forceUpdate();
|
this.$forceUpdate();
|
||||||
}
|
}
|
||||||
|
@ -935,11 +934,12 @@ export default class Timeline extends Mixins(GlobalMixin) {
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
const list = [...this.selection];
|
const list = [...this.selection];
|
||||||
const delIds = await dav.deleteFilesByIds(list.map(p => p.fileid));
|
for await (const delIds of dav.deleteFilesByIds(list.map(p => p.fileid))) {
|
||||||
|
const delIdsSet = new Set(delIds.filter(i => i));
|
||||||
|
const updatedDays = new Set(list.filter(f => delIdsSet.has(f.fileid)).map(f => f.d));
|
||||||
|
await this.deleteFromViewWithAnimation(delIdsSet, updatedDays);
|
||||||
|
}
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
const updatedDays = new Set(list.filter(f => delIds.has(f.fileid)).map(f => f.d));
|
|
||||||
await this.deleteFromViewWithAnimation(delIds, updatedDays);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -959,12 +959,16 @@ export default class Timeline extends Mixins(GlobalMixin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set of photos that are being deleted
|
||||||
|
const delPhotos = new Set<IPhoto>();
|
||||||
|
|
||||||
// Animate the deletion
|
// Animate the deletion
|
||||||
for (const day of updatedDays) {
|
for (const day of updatedDays) {
|
||||||
for (const row of day.rows) {
|
for (const row of day.rows) {
|
||||||
for (const photo of row.photos) {
|
for (const photo of row.photos) {
|
||||||
if (delIds.has(photo.fileid)) {
|
if (delIds.has(photo.fileid)) {
|
||||||
photo.flag |= this.c.FLAG_LEAVING;
|
photo.flag |= this.c.FLAG_LEAVING;
|
||||||
|
delPhotos.add(photo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -973,8 +977,11 @@ export default class Timeline extends Mixins(GlobalMixin) {
|
||||||
// wait for 200ms
|
// wait for 200ms
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
|
|
||||||
|
// clear selection at this point
|
||||||
|
this.clearSelection(delPhotos);
|
||||||
|
|
||||||
// Speculate day reflow for animation
|
// Speculate day reflow for animation
|
||||||
const exitedLeft = new Set();
|
const exitedLeft = new Set<IPhoto>();
|
||||||
for (const day of updatedDays) {
|
for (const day of updatedDays) {
|
||||||
let nextExit = false;
|
let nextExit = false;
|
||||||
for (const row of day.rows) {
|
for (const row of day.rows) {
|
||||||
|
@ -1005,9 +1012,6 @@ export default class Timeline extends Mixins(GlobalMixin) {
|
||||||
photo.flag |= this.c.FLAG_ENTER_RIGHT;
|
photo.flag |= this.c.FLAG_ENTER_RIGHT;
|
||||||
});
|
});
|
||||||
|
|
||||||
// clear selection at this point
|
|
||||||
this.clearSelection();
|
|
||||||
|
|
||||||
// wait for 200ms
|
// wait for 200ms
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
|
|
||||||
|
|
|
@ -150,6 +150,20 @@ export async function getFolderPreviewFileIds(folderPath: string, limit: number)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run promises in parallel, but only n at a time
|
||||||
|
* @param promises Array of promise generator funnction (async functions)
|
||||||
|
* @param n Number of promises to run in parallel
|
||||||
|
*/
|
||||||
|
export async function* runInParallel<T>(promises: (() => Promise<T>)[], n: number) {
|
||||||
|
while (promises.length > 0) {
|
||||||
|
const promisesToRun = promises.splice(0, n);
|
||||||
|
const resultsForThisBatch = await Promise.all(promisesToRun.map(p => p()));
|
||||||
|
yield resultsForThisBatch;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a single file
|
* Delete a single file
|
||||||
*
|
*
|
||||||
|
@ -166,12 +180,11 @@ export async function deleteFile(path: string) {
|
||||||
* @param fileIds list of file ids
|
* @param fileIds list of file ids
|
||||||
* @returns list of file ids that were deleted
|
* @returns list of file ids that were deleted
|
||||||
*/
|
*/
|
||||||
export async function deleteFilesByIds(fileIds: number[]) {
|
export async function* deleteFilesByIds(fileIds: number[]) {
|
||||||
const delIds = new Set<number>();
|
|
||||||
const fileIdsSet = new Set(fileIds);
|
const fileIdsSet = new Set(fileIds);
|
||||||
|
|
||||||
if (fileIds.length === 0) {
|
if (fileIds.length === 0) {
|
||||||
return delIds;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get files data
|
// Get files data
|
||||||
|
@ -180,31 +193,22 @@ export async function deleteFilesByIds(fileIds: number[]) {
|
||||||
fileInfos = await getFiles(fileIds.filter(f => f));
|
fileInfos = await getFiles(fileIds.filter(f => f));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to get file info for files to delete', fileIds, e);
|
console.error('Failed to get file info for files to delete', fileIds, e);
|
||||||
return delIds;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run all promises together
|
|
||||||
const promises: Promise<void>[] = [];
|
|
||||||
|
|
||||||
// Delete each file
|
// Delete each file
|
||||||
for (const fileInfo of fileInfos) {
|
fileInfos = fileInfos.filter((f) => fileIdsSet.has(f.fileid));
|
||||||
if (!fileIdsSet.has(fileInfo.fileid)) {
|
const calls = fileInfos.map((fileInfo) => async () => {
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
promises.push((async () => {
|
|
||||||
try {
|
try {
|
||||||
await deleteFile(fileInfo.filename);
|
await deleteFile(fileInfo.filename);
|
||||||
delIds.add(fileInfo.fileid);
|
return fileInfo.fileid as number;
|
||||||
} catch {
|
} catch {
|
||||||
console.error('Failed to delete', fileInfo.filename)
|
console.error('Failed to delete', fileInfo.filename)
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
})());
|
});
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.allSettled(promises);
|
yield* runInParallel(calls, 10);
|
||||||
|
|
||||||
return delIds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue