Skip to content

Commit 39cba3e

Browse files
authored
Improved export error handling (#117)
1 parent 2d36662 commit 39cba3e

6 files changed

Lines changed: 84 additions & 23 deletions

File tree

cider-app/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cider-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
"electron-builder-notarize": "^1.5.2",
4646
"file-saver": "^2.0.5",
4747
"handlebars": "^4.7.7",
48-
"html-to-image": "^1.11.11",
48+
"html-to-image": "^1.11.13",
4949
"jszip": "^3.10.0",
5050
"mime": "^3.0.0",
5151
"mime-wrapper": "^2.0.0",

cider-app/src/app/card-preview/card-preview.component.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CardToHtmlPipe } from '../shared/pipes/template-to-html.pipe';
99
import * as htmlToImage from 'html-to-image';
1010
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
1111
import GeneralUtils from '../shared/utils/general-utils';
12+
import { error } from 'console';
1213

1314
@Component({
1415
selector: 'app-card-preview',
@@ -27,6 +28,7 @@ export class CardPreviewComponent implements OnInit, AfterViewChecked, OnChanges
2728
assetUrls: any;
2829
uuid: string = uuid();
2930
cachedImageUrl?: string;
31+
invalidTemplate: boolean = false;
3032
private isLoadedSubject: AsyncSubject<boolean>;
3133
private isCacheLoadedSubject: AsyncSubject<boolean>;
3234

@@ -67,12 +69,19 @@ export class CardPreviewComponent implements OnInit, AfterViewChecked, OnChanges
6769
lastValueFrom(this.isLoadedSubject).then(() => {
6870
this.renderCacheService.getOrSet(this.getHash(),
6971
() => GeneralUtils.delay(2000).then(() => this.toImageUrl()))
70-
.subscribe((cachedImageUrl) => {
71-
this.cachedImageUrl = cachedImageUrl;
72-
GeneralUtils.delay(1000).then(() => {
73-
this.isCacheLoadedSubject.next(true);
74-
this.isCacheLoadedSubject.complete();
75-
});
72+
.subscribe({
73+
next: (cachedImageUrl) => {
74+
this.cachedImageUrl = cachedImageUrl;
75+
GeneralUtils.delay(1000).then(() => {
76+
this.isCacheLoadedSubject.next(true);
77+
this.isCacheLoadedSubject.complete();
78+
});
79+
},
80+
error: (error) => {
81+
this.invalidTemplate = true;
82+
this.isCacheLoadedSubject.error('Failed to generate card image');
83+
this.isCacheLoadedSubject.complete();
84+
}
7685
});
7786
});
7887
}

cider-app/src/app/data-services/services/render-cache.service.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export class RenderCacheService {
3434
const url = URL.createObjectURL(file);
3535
imageSubject.next(url);
3636
imageSubject.complete();
37+
}).catch(err => {
38+
imageSubject.error('Failed to generate cache image');
3739
});
3840
return imageSubject.asObservable();
3941
}

cider-app/src/app/export-cards/export-cards.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
</p-card>
167167
</div>
168168
</div>
169+
<p-confirmDialog [style]="{width: '450px'}"></p-confirmDialog>
169170
<app-export-selection-dialog [(visible)]="exportSelectionDialogVisible"
170171
(onApply)="updateSelection($event)" [records]="originalCards"></app-export-selection-dialog>
171172
<p-dialog header="Exporting Data" [(visible)]="displayLoading" [style]="{width: '450px'}" [modal]="true" >

cider-app/src/app/export-cards/export-cards.component.ts

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import FileUtils from '../shared/utils/file-utils';
1111
import { lastValueFrom } from 'rxjs';
1212
import StringUtils from '../shared/utils/string-utils';
1313
import GeneralUtils from '../shared/utils/general-utils';
14+
import { ConfirmationService } from 'primeng/api';
1415

1516
@Component({
1617
selector: 'app-export-cards',
1718
templateUrl: './export-cards.component.html',
18-
styleUrls: ['./export-cards.component.scss']
19+
styleUrls: ['./export-cards.component.scss'],
20+
providers: [ConfirmationService]
1921
})
2022
export class ExportCardsComponent implements OnInit {
2123
private static readonly SINGULAR_EXPORT = 'singular-export';
@@ -76,7 +78,8 @@ export class ExportCardsComponent implements OnInit {
7678
];
7779

7880
constructor(cardsService: CardsService,
79-
public templatesService: CardTemplatesService) {
81+
public templatesService: CardTemplatesService,
82+
private confirmationService: ConfirmationService) {
8083
cardsService.getAll().then(cards => {
8184
// check cards for front/back templates being defined
8285
const cardsWithTemplatesDefined = cards.filter(card => card.backCardTemplateId && card.frontCardTemplateId);
@@ -160,14 +163,31 @@ export class ExportCardsComponent implements OnInit {
160163
public export() {
161164
if (this.exportType === ExportCardsComponent.SHEET_EXPORT
162165
&& this.selectedPaper.name === 'Tabletop Simulator') {
163-
this.exportCardSheetsAsImages();
166+
this.exportCardSheetsAsImages().catch(err => {
167+
this.displayErrorDialog(err);
168+
});;
164169
} else if (this.exportType === ExportCardsComponent.SHEET_EXPORT) {
165-
this.exportCardSheets();
170+
this.exportCardSheets().catch(err => {
171+
this.displayErrorDialog(err);
172+
});
166173
} else {
167-
this.exportIndividualImages();
174+
this.exportIndividualImages().catch(err => {
175+
this.displayErrorDialog(err);
176+
});;
168177
}
169178
}
170179

180+
private displayErrorDialog(message: string) {
181+
this.confirmationService.confirm({
182+
message: message + '\nCheck for errors in this template and card data.',
183+
header: 'Error',
184+
acceptLabel: "OK",
185+
icon: 'pi pi-exclamation-triangle',
186+
rejectVisible: false,
187+
accept: () => {return;},
188+
});
189+
}
190+
171191
private async prerenderCardImages() {
172192
const sliceSize = 3;
173193
const renderSlices = this.sliceIntoChunks(this.cards, sliceSize);
@@ -182,7 +202,13 @@ export class ExportCardsComponent implements OnInit {
182202
this.loadingInfo = 'Pre-rendering cards ' + (sliceSize * index)
183203
+ '-' + (sliceSize * index + slice.length - 1) + '/' + this.cards.length + '...';
184204
await GeneralUtils.delay(1000);
185-
const promisedCards$ = this.cardSheetCards.map(card => lastValueFrom(card.isCacheLoaded()));
205+
const promisedCards$ = this.cardSheetCards.map(cardPreview => lastValueFrom(cardPreview.isCacheLoaded()).catch(() => {
206+
this.loadingInfo = 'Failed to load card cache.';
207+
this.loadingPercent = 0;
208+
this.displayLoading = false;
209+
this.renderCache = false;
210+
return Promise.reject('Failed to render card "' + cardPreview.card.name + '" with template "' + cardPreview.template.name + '".');
211+
}));
186212
const completedPromises = await Promise.all(promisedCards$);
187213
this.loadingPercent += 100.0/(renderSlices.length);
188214
return completedPromises;
@@ -210,7 +236,13 @@ export class ExportCardsComponent implements OnInit {
210236
this.loadingInfo = 'Rendering sheet ' + sheetIndex + ' '
211237
+ (showFront ? 'front' : 'back') + ' card images...';
212238
await GeneralUtils.delay(1000);
213-
const promisedCards$ = this.cardSheetCards.map(card => lastValueFrom(card.isCacheLoaded()));
239+
const promisedCards$ = this.cardSheetCards.map(cardPreview => lastValueFrom(cardPreview.isCacheLoaded()).catch(() => {
240+
this.loadingInfo = 'Failed to load card cache.';
241+
this.loadingPercent = 0;
242+
this.displayLoading = false;
243+
this.renderCache = false;
244+
return Promise.reject('Failed to render card "' + cardPreview.card.name + '" with template "' + cardPreview.template.name + '".');
245+
}));
214246
await Promise.all(promisedCards$);
215247

216248
this.loadingInfo = 'Generating sheet ' + sheetIndex + ' '
@@ -252,12 +284,18 @@ export class ExportCardsComponent implements OnInit {
252284
this.sheet = sheet;
253285
this.loadingInfo = 'Rendering sheet ' + sheetIndex + ' card images...';
254286
await GeneralUtils.delay(1000);
255-
const promisedCards$ = this.cardSheetCards.map(card => lastValueFrom(card.isCacheLoaded()));
287+
const promisedCards$ = this.cardSheetCards.map(cardPreview => lastValueFrom(cardPreview.isCacheLoaded()).catch(() => {
288+
this.loadingInfo = 'Failed to load card cache.';
289+
this.loadingPercent = 0;
290+
this.displayLoading = false;
291+
this.renderCache = false;
292+
return Promise.reject('Failed to render card "' + cardPreview.card.name + '" with template "' + cardPreview.template.name + '".');
293+
}));
256294
await Promise.all(promisedCards$);
257295

258296
this.loadingInfo = 'Generating sheet ' + sheetIndex + ' images...';
259297
const cardSheets = await Promise.all(this.cardSheets.map(cardSheet => limit(() => {
260-
return htmlToImage.toPng((<any>cardSheet).nativeElement, {pixelRatio: 1.0});
298+
return htmlToImage.toPng((<any>cardSheet).nativeElement, {pixelRatio: 1.0, onImageErrorHandler: (error) => {console.log('error', error);}});
261299
})));
262300
this.loadingPercent += 100.0/(this.slicedCards.length + 1);
263301
return cardSheets;
@@ -304,7 +342,9 @@ export class ExportCardsComponent implements OnInit {
304342
this.loadingInfo = 'Generating card images...';
305343
const limit = pLimit(3);
306344
const frontCards$ = this.frontCards.map(async cardPreview => {
307-
await lastValueFrom(cardPreview.isCacheLoaded());
345+
await lastValueFrom(cardPreview.isCacheLoaded()).catch(() => {
346+
return Promise.reject('Failed to render card "' + cardPreview.card.name + '" with template "' + cardPreview.template.name + '".');
347+
});
308348
const imgUri = await limit(() => htmlToImage.toPng((<any>cardPreview).element.nativeElement,
309349
{pixelRatio: this.individualExportPixelRatio}));
310350
const imgIdentifier = this.individualExportUseCardName
@@ -314,7 +354,9 @@ export class ExportCardsComponent implements OnInit {
314354
return this.dataUrlToFile(imgUri, imgName);
315355
});
316356
const backCards$ = this.backCards.map(async cardPreview => {
317-
await lastValueFrom(cardPreview.isCacheLoaded());
357+
await lastValueFrom(cardPreview.isCacheLoaded()).catch(() => {
358+
return Promise.reject('Failed to render card "' + cardPreview.card.name + '" with template "' + cardPreview.template.name + '".');
359+
});
318360
const imgUri = await limit(() => htmlToImage.toPng((<any>cardPreview).element.nativeElement,
319361
{pixelRatio: this.individualExportPixelRatio}));
320362
const imgIdentifier = this.individualExportUseCardName
@@ -324,7 +366,7 @@ export class ExportCardsComponent implements OnInit {
324366
return this.dataUrlToFile(imgUri, imgName);
325367
});
326368
const allCards$ = frontCards$.concat(backCards$);
327-
Promise.all(this.promisesProgress(allCards$, () => this.loadingPercent += 100.0/(allCards$.length + 1)))
369+
return Promise.all(this.promisesProgress(allCards$, () => this.loadingPercent += 100.0/(allCards$.length + 1)))
328370
.then(promisedImages => {
329371
this.loadingInfo = 'Zipping up files...';
330372
return this.zipFiles(promisedImages);
@@ -337,6 +379,13 @@ export class ExportCardsComponent implements OnInit {
337379
this.loadingPercent = 100;
338380
this.displayLoading = false
339381
this.renderCache = false;
382+
})
383+
.catch(err => {
384+
this.loadingInfo = 'Failed to load card cache.';
385+
this.loadingPercent = 0;
386+
this.displayLoading = false;
387+
this.renderCache = false;
388+
return Promise.reject(err);
340389
});
341390
}
342391

0 commit comments

Comments
 (0)