@nexterajs/report-viewer 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { input, inject, DOCUMENT, ElementRef, ChangeDetectionStrategy, Component, Injectable, signal,
|
|
3
|
+
import { input, computed, inject, DOCUMENT, ElementRef, ChangeDetectionStrategy, Component, Injectable, signal, DestroyRef, output, viewChild, linkedSignal } from '@angular/core';
|
|
4
4
|
import { HttpErrorResponse, HttpClient } from '@angular/common/http';
|
|
5
5
|
import { defer, map, catchError, from, switchMap, throwError, timeout, Subject, Subscription, finalize, interval, exhaustMap, EMPTY } from 'rxjs';
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ import { defer, map, catchError, from, switchMap, throwError, timeout, Subject,
|
|
|
8
8
|
* The version of the viewer protocol this package speaks: the `protocol` member of every
|
|
9
9
|
* `AngularViewerData` answer from `Nextera.Report.AspNetCore`.
|
|
10
10
|
*/
|
|
11
|
-
const
|
|
11
|
+
const NXE_VIEWER_PROTOCOL = 1;
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Page HTML from the server, made safe to place in the application's document.
|
|
@@ -137,8 +137,19 @@ function adoptedSheet(cssText) {
|
|
|
137
137
|
return sheet;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
/**
|
|
141
|
-
|
|
140
|
+
/**
|
|
141
|
+
* The viewer draws each page's frame and the space between pages, so the page CSS's own are cancelled.
|
|
142
|
+
*
|
|
143
|
+
* Zoom scales the page's own box from its top left corner. Stimulsoft re-renders the page on the server
|
|
144
|
+
* at the asked-for zoom and never scales on the client; this engine's page HTML is absolutely positioned
|
|
145
|
+
* in points, so scaling it here puts the same marks in the same places without a round trip, and the text
|
|
146
|
+
* is re-rasterised at the new size rather than resampled. The host element is sized to the scaled box so
|
|
147
|
+
* the pages around it lay out against what is on screen.
|
|
148
|
+
*/
|
|
149
|
+
function pageFrameCss(zoom) {
|
|
150
|
+
const transform = zoom === 1 ? '' : `transform:scale(${zoom});transform-origin:top left;`;
|
|
151
|
+
return `.nextera-page{margin:0;box-shadow:none;${transform}}`;
|
|
152
|
+
}
|
|
142
153
|
/**
|
|
143
154
|
* One report page, in its own shadow root.
|
|
144
155
|
*
|
|
@@ -147,7 +158,7 @@ const PAGE_FRAME_CSS = '.nextera-page{margin:0;box-shadow:none;}';
|
|
|
147
158
|
* report's styles and the application's never meet. `contain: paint` keeps even a fixed-position
|
|
148
159
|
* element inside the page's box.
|
|
149
160
|
*/
|
|
150
|
-
class
|
|
161
|
+
class NxeReportPageComponent {
|
|
151
162
|
/** The page's number and size. */
|
|
152
163
|
page = input.required(/* @ts-ignore */
|
|
153
164
|
...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
|
|
@@ -157,13 +168,20 @@ class NxrReportPageComponent {
|
|
|
157
168
|
/** The stylesheet every page of the report needs. */
|
|
158
169
|
css = input.required(/* @ts-ignore */
|
|
159
170
|
...(ngDevMode ? [{ debugName: "css" }] : /* istanbul ignore next */ []));
|
|
171
|
+
/** The zoom to draw the page at, as a factor: 1 is 100 %. */
|
|
172
|
+
zoom = input(1, /* @ts-ignore */
|
|
173
|
+
...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
|
|
174
|
+
scaledWidth = computed(() => this.page().widthPoints * this.zoom(), /* @ts-ignore */
|
|
175
|
+
...(ngDevMode ? [{ debugName: "scaledWidth" }] : /* istanbul ignore next */ []));
|
|
176
|
+
scaledHeight = computed(() => this.page().heightPoints * this.zoom(), /* @ts-ignore */
|
|
177
|
+
...(ngDevMode ? [{ debugName: "scaledHeight" }] : /* istanbul ignore next */ []));
|
|
160
178
|
document = inject(DOCUMENT);
|
|
161
179
|
root = inject(ElementRef).nativeElement.attachShadow({
|
|
162
180
|
mode: 'open',
|
|
163
181
|
});
|
|
164
182
|
ngOnChanges(changes) {
|
|
165
|
-
if (changes['css']) {
|
|
166
|
-
this.root.adoptedStyleSheets = [adoptedSheet(this.css()), adoptedSheet(
|
|
183
|
+
if (changes['css'] || changes['zoom']) {
|
|
184
|
+
this.root.adoptedStyleSheets = [adoptedSheet(this.css()), adoptedSheet(pageFrameCss(this.zoom()))];
|
|
167
185
|
}
|
|
168
186
|
if (changes['html']) {
|
|
169
187
|
const html = this.html();
|
|
@@ -175,26 +193,26 @@ class NxrReportPageComponent {
|
|
|
175
193
|
}
|
|
176
194
|
}
|
|
177
195
|
}
|
|
178
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
179
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type:
|
|
196
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeReportPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
197
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.5", type: NxeReportPageComponent, isStandalone: true, selector: "nxe-report-page", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null }, html: { classPropertyName: "html", publicName: "html", isSignal: true, isRequired: false, transformFunction: null }, css: { classPropertyName: "css", publicName: "css", isSignal: true, isRequired: true, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-page": "page().pageNumber", "style.width.pt": "scaledWidth()", "style.height.pt": "scaledHeight()" }, classAttribute: "nxe-report-page" }, usesOnChanges: true, ngImport: i0, template: '', isInline: true, styles: [":host{display:block;position:relative;box-sizing:content-box;contain:paint;background:#fff}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
180
198
|
}
|
|
181
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
199
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeReportPageComponent, decorators: [{
|
|
182
200
|
type: Component,
|
|
183
|
-
args: [{ selector: '
|
|
184
|
-
class: '
|
|
201
|
+
args: [{ selector: 'nxe-report-page', standalone: true, template: '', changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
202
|
+
class: 'nxe-report-page',
|
|
185
203
|
'[attr.data-page]': 'page().pageNumber',
|
|
186
|
-
'[style.width.pt]': '
|
|
187
|
-
'[style.height.pt]': '
|
|
204
|
+
'[style.width.pt]': 'scaledWidth()',
|
|
205
|
+
'[style.height.pt]': 'scaledHeight()',
|
|
188
206
|
}, styles: [":host{display:block;position:relative;box-sizing:content-box;contain:paint;background:#fff}\n"] }]
|
|
189
|
-
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], html: [{ type: i0.Input, args: [{ isSignal: true, alias: "html", required: false }] }], css: [{ type: i0.Input, args: [{ isSignal: true, alias: "css", required: true }] }] } });
|
|
207
|
+
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }], html: [{ type: i0.Input, args: [{ isSignal: true, alias: "html", required: false }] }], css: [{ type: i0.Input, args: [{ isSignal: true, alias: "css", required: true }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }] } });
|
|
190
208
|
|
|
191
209
|
/**
|
|
192
210
|
* Every caption and message the viewer shows, in its built-in English. A key in PascalCase is the name
|
|
193
211
|
* Stimulsoft's viewer gives the same text, such as `PageOf`; a key in camelCase names a message only this
|
|
194
212
|
* viewer has. The English is this package's own. A later plan lets a host's translation file replace any
|
|
195
|
-
* of them, so code reads them from an `
|
|
213
|
+
* of them, so code reads them from an `NxeViewerStrings` value wherever one is at hand.
|
|
196
214
|
*/
|
|
197
|
-
const
|
|
215
|
+
const NXE_ENGLISH = {
|
|
198
216
|
/** The label before the page-number box. */
|
|
199
217
|
Page: 'Page',
|
|
200
218
|
/** The label between the page-number box and the page count. */
|
|
@@ -203,6 +221,18 @@ const NXR_ENGLISH = {
|
|
|
203
221
|
PrevPage: 'Previous page',
|
|
204
222
|
NextPage: 'Next page',
|
|
205
223
|
LastPage: 'Last page',
|
|
224
|
+
/** The save button's caption and tooltip; its menu items are captioned by the format they write. */
|
|
225
|
+
Save: 'Save',
|
|
226
|
+
SaveToolTip: 'Save',
|
|
227
|
+
/** The view-mode button's tooltip, and its menu items, whose captions are the mode names. */
|
|
228
|
+
ViewModeToolTip: 'View mode',
|
|
229
|
+
SinglePage: 'Single Page',
|
|
230
|
+
Continuous: 'Continuous',
|
|
231
|
+
MultiplePages: 'Multiple Pages',
|
|
232
|
+
/** The zoom button's tooltip; its caption is the current percentage, so it has no key. */
|
|
233
|
+
ZoomToolTip: 'Zoom',
|
|
234
|
+
ZoomOnePage: 'One Page',
|
|
235
|
+
ZoomPageWidth: 'Page Width',
|
|
206
236
|
errorUnreachable: 'The report server could not be reached.',
|
|
207
237
|
errorStatus: 'The report server answered with HTTP status {0}.',
|
|
208
238
|
errorTimeout: 'The report server did not answer within {0} seconds.',
|
|
@@ -218,25 +248,25 @@ function formatString(template, ...values) {
|
|
|
218
248
|
* A success answer that is not what the protocol says its action answers: the wrong shape, or an
|
|
219
249
|
* AngularViewerData answer from another version of the protocol.
|
|
220
250
|
*/
|
|
221
|
-
class
|
|
251
|
+
class NxeMalformedAnswerError extends Error {
|
|
222
252
|
action;
|
|
223
253
|
protocol;
|
|
224
254
|
constructor(action,
|
|
225
255
|
/** The version an AngularViewerData answer declared, when it declared another one. */
|
|
226
|
-
protocol =
|
|
227
|
-
super(`The ${action} answer is not what viewer protocol ${
|
|
256
|
+
protocol = NXE_VIEWER_PROTOCOL) {
|
|
257
|
+
super(`The ${action} answer is not what viewer protocol ${NXE_VIEWER_PROTOCOL} answers.`);
|
|
228
258
|
this.action = action;
|
|
229
259
|
this.protocol = protocol;
|
|
230
|
-
this.name = '
|
|
260
|
+
this.name = 'NxeMalformedAnswerError';
|
|
231
261
|
}
|
|
232
262
|
}
|
|
233
263
|
/** A request that got no answer within the server options' `requestTimeout`. */
|
|
234
|
-
class
|
|
264
|
+
class NxeTimeoutError extends Error {
|
|
235
265
|
seconds;
|
|
236
266
|
constructor(seconds) {
|
|
237
267
|
super(`No answer within ${seconds} seconds.`);
|
|
238
268
|
this.seconds = seconds;
|
|
239
|
-
this.name = '
|
|
269
|
+
this.name = 'NxeTimeoutError';
|
|
240
270
|
}
|
|
241
271
|
}
|
|
242
272
|
const OPTION_GROUPS = ['actions', 'appearance', 'toolbar', 'exports', 'server'];
|
|
@@ -279,25 +309,25 @@ function isLocated(value, textKey) {
|
|
|
279
309
|
/** Reads the answer to AngularViewerData: the token, the viewer options and the host's translation. */
|
|
280
310
|
function readViewerData(body) {
|
|
281
311
|
if (!isObject(body))
|
|
282
|
-
throw new
|
|
312
|
+
throw new NxeMalformedAnswerError('AngularViewerData');
|
|
283
313
|
const { protocol, token, options, localization } = body;
|
|
284
|
-
if (isNumber(protocol) && protocol !==
|
|
285
|
-
throw new
|
|
314
|
+
if (isNumber(protocol) && protocol !== NXE_VIEWER_PROTOCOL) {
|
|
315
|
+
throw new NxeMalformedAnswerError('AngularViewerData', protocol);
|
|
286
316
|
}
|
|
287
|
-
const valid = protocol ===
|
|
317
|
+
const valid = protocol === NXE_VIEWER_PROTOCOL &&
|
|
288
318
|
typeof token === 'string' &&
|
|
289
319
|
token !== '' &&
|
|
290
320
|
isObject(options) &&
|
|
291
321
|
OPTION_GROUPS.every((group) => isObject(options[group])) &&
|
|
292
322
|
(localization === null || isObject(localization));
|
|
293
323
|
if (!valid)
|
|
294
|
-
throw new
|
|
324
|
+
throw new NxeMalformedAnswerError('AngularViewerData');
|
|
295
325
|
return body;
|
|
296
326
|
}
|
|
297
327
|
/** Reads the answer to GetReport: the report's name, page count, page sizes, bookmarks and page stylesheet. */
|
|
298
328
|
function readReport(body) {
|
|
299
329
|
if (!isObject(body))
|
|
300
|
-
throw new
|
|
330
|
+
throw new NxeMalformedAnswerError('GetReport');
|
|
301
331
|
const { reportName, pageCount, pages, bookmarks, pageCss, pagesHtml } = body;
|
|
302
332
|
const valid = typeof reportName === 'string' &&
|
|
303
333
|
isIndex(pageCount) &&
|
|
@@ -309,7 +339,7 @@ function readReport(body) {
|
|
|
309
339
|
typeof pageCss === 'string' &&
|
|
310
340
|
(pagesHtml === undefined || isPages(pagesHtml));
|
|
311
341
|
if (!valid)
|
|
312
|
-
throw new
|
|
342
|
+
throw new NxeMalformedAnswerError('GetReport');
|
|
313
343
|
return {
|
|
314
344
|
...body,
|
|
315
345
|
pagesHtml: pagesHtml ?? null,
|
|
@@ -318,20 +348,20 @@ function readReport(body) {
|
|
|
318
348
|
/** Reads the answer to GetPages. */
|
|
319
349
|
function readPages(body) {
|
|
320
350
|
if (!isPages(body))
|
|
321
|
-
throw new
|
|
351
|
+
throw new NxeMalformedAnswerError('GetPages');
|
|
322
352
|
return body;
|
|
323
353
|
}
|
|
324
354
|
/** Reads the answer to Find. */
|
|
325
355
|
function readFind(body) {
|
|
326
356
|
const hits = isObject(body) ? body['hits'] : undefined;
|
|
327
357
|
if (!Array.isArray(hits) || !hits.every((hit) => isLocated(hit, 'text')))
|
|
328
|
-
throw new
|
|
358
|
+
throw new NxeMalformedAnswerError('Find');
|
|
329
359
|
return body;
|
|
330
360
|
}
|
|
331
361
|
/** Reads the answer to UpdateCache. */
|
|
332
362
|
function readUpdateCache(body) {
|
|
333
363
|
if (!isObject(body) || typeof body['updated'] !== 'boolean')
|
|
334
|
-
throw new
|
|
364
|
+
throw new NxeMalformedAnswerError('UpdateCache');
|
|
335
365
|
return body;
|
|
336
366
|
}
|
|
337
367
|
function described(status, message) {
|
|
@@ -345,21 +375,21 @@ function described(status, message) {
|
|
|
345
375
|
* - a timeout;
|
|
346
376
|
* - a success answer the viewer cannot read.
|
|
347
377
|
*/
|
|
348
|
-
function readFailure(failure, action, strings =
|
|
378
|
+
function readFailure(failure, action, strings = NXE_ENGLISH) {
|
|
349
379
|
if (failure instanceof HttpErrorResponse) {
|
|
350
380
|
const body = failure.error;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
381
|
+
// status and message are all that is asked for. The rest of the protocol's error answer is taken when the server
|
|
382
|
+
// sent it and defaulted when it did not, because a host that writes its own error -- bright answers a failed
|
|
383
|
+
// GetReport with {status, message} from its controller's catch -- says the one thing the reader cannot work out
|
|
384
|
+
// for itself, and demanding type and retryable alongside it threw that sentence away and left the viewer showing
|
|
385
|
+
// the status code on its own.
|
|
386
|
+
if (isObject(body) && body['status'] === 'error' && typeof body['message'] === 'string') {
|
|
357
387
|
return {
|
|
358
388
|
status: failure.status,
|
|
359
|
-
type: body['type'],
|
|
389
|
+
type: typeof body['type'] === 'string' ? body['type'] : 'Error',
|
|
360
390
|
message: body['message'],
|
|
361
|
-
retryable: body['retryable'],
|
|
362
|
-
traceId,
|
|
391
|
+
retryable: typeof body['retryable'] === 'boolean' ? body['retryable'] : false,
|
|
392
|
+
traceId: typeof body['traceId'] === 'string' ? body['traceId'] : null,
|
|
363
393
|
};
|
|
364
394
|
}
|
|
365
395
|
if (failure.status === 0)
|
|
@@ -368,12 +398,12 @@ function readFailure(failure, action, strings = NXR_ENGLISH) {
|
|
|
368
398
|
return described(0, formatString(strings.errorMalformed, action));
|
|
369
399
|
return described(failure.status, formatString(strings.errorStatus, failure.status));
|
|
370
400
|
}
|
|
371
|
-
if (failure instanceof
|
|
401
|
+
if (failure instanceof NxeTimeoutError) {
|
|
372
402
|
return described(0, formatString(strings.errorTimeout, failure.seconds));
|
|
373
403
|
}
|
|
374
|
-
if (failure instanceof
|
|
375
|
-
return failure.protocol !==
|
|
376
|
-
? described(0, formatString(strings.errorProtocol, failure.protocol,
|
|
404
|
+
if (failure instanceof NxeMalformedAnswerError) {
|
|
405
|
+
return failure.protocol !== NXE_VIEWER_PROTOCOL
|
|
406
|
+
? described(0, formatString(strings.errorProtocol, failure.protocol, NXE_VIEWER_PROTOCOL))
|
|
377
407
|
: described(0, formatString(strings.errorMalformed, failure.action));
|
|
378
408
|
}
|
|
379
409
|
return described(0, failure instanceof Error ? failure.message : String(failure));
|
|
@@ -383,7 +413,7 @@ function readFailure(failure, action, strings = NXR_ENGLISH) {
|
|
|
383
413
|
* The seconds a request may take when no positive `requestTimeout` applies. That includes the first
|
|
384
414
|
* request, which is sent before the server's options are known. Stimulsoft's viewer uses the same value.
|
|
385
415
|
*/
|
|
386
|
-
const
|
|
416
|
+
const NXE_UNSET_REQUEST_TIMEOUT_SECONDS = 1000;
|
|
387
417
|
/**
|
|
388
418
|
* The URL of a host action. The action's path takes the place of `{action}` in `requestUrl`, and a query
|
|
389
419
|
* string on the action is appended to the URL's own.
|
|
@@ -429,7 +459,7 @@ function fileNameOf(disposition) {
|
|
|
429
459
|
}
|
|
430
460
|
function timeoutOf(session) {
|
|
431
461
|
const seconds = session?.options.server.requestTimeout ?? 0;
|
|
432
|
-
return seconds > 0 ? seconds :
|
|
462
|
+
return seconds > 0 ? seconds : NXE_UNSET_REQUEST_TIMEOUT_SECONDS;
|
|
433
463
|
}
|
|
434
464
|
function formOf(context, action, session, parameters) {
|
|
435
465
|
const form = new FormData();
|
|
@@ -473,10 +503,10 @@ async function readableFailure(failure) {
|
|
|
473
503
|
}
|
|
474
504
|
/**
|
|
475
505
|
* Sends one viewer's protocol requests through the application's `HttpClient`, so the application's
|
|
476
|
-
* interceptors apply, and reads each answer. Every observable fails with an `
|
|
506
|
+
* interceptors apply, and reads each answer. Every observable fails with an `NxeViewerFailure`. A request's
|
|
477
507
|
* form is built when the observable is subscribed.
|
|
478
508
|
*/
|
|
479
|
-
class
|
|
509
|
+
class NxeViewerClientService {
|
|
480
510
|
http = inject(HttpClient);
|
|
481
511
|
/** AngularViewerData, sent to the component's `action`: the token and the options. */
|
|
482
512
|
openViewer(context, action) {
|
|
@@ -516,30 +546,46 @@ class NxrViewerClientService {
|
|
|
516
546
|
const seconds = timeoutOf(session);
|
|
517
547
|
return defer(() => this.http.post(actionUrl(context.requestUrl, hostAction), formOf(context, action, session, parameters))).pipe(timeout({
|
|
518
548
|
first: seconds * 1000,
|
|
519
|
-
with: () => throwError(() => new
|
|
549
|
+
with: () => throwError(() => new NxeTimeoutError(seconds)),
|
|
520
550
|
}), map((body) => read(body)), catchError((failure) => throwError(() => readFailure(failure, action, context.strings))));
|
|
521
551
|
}
|
|
522
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
523
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
552
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeViewerClientService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
553
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeViewerClientService });
|
|
524
554
|
}
|
|
525
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
555
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeViewerClientService, decorators: [{
|
|
526
556
|
type: Injectable
|
|
527
557
|
}] });
|
|
528
558
|
|
|
529
559
|
/** How often an open viewer asks the server to keep its report: every 3 minutes, as Stimulsoft's viewer does. */
|
|
530
|
-
const
|
|
560
|
+
const NXE_KEEP_ALIVE_MS = 180_000;
|
|
531
561
|
function withPages(pages, answer) {
|
|
532
562
|
answer?.pages.forEach((html, offset) => pages.set(answer.startIndex + offset, html));
|
|
533
563
|
return pages;
|
|
534
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* Hands a file to the browser to save, under the name the server gave it. A blob URL is used rather
|
|
567
|
+
* than a data one because an exported report is far larger than a URL may be, and it is revoked on
|
|
568
|
+
* the next turn of the event loop, once the click has been taken.
|
|
569
|
+
*/
|
|
570
|
+
function saveBlob(blob, fileName) {
|
|
571
|
+
const url = URL.createObjectURL(blob);
|
|
572
|
+
const link = document.createElement('a');
|
|
573
|
+
link.href = url;
|
|
574
|
+
link.download = fileName;
|
|
575
|
+
link.style.display = 'none';
|
|
576
|
+
document.body.append(link);
|
|
577
|
+
link.click();
|
|
578
|
+
link.remove();
|
|
579
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
580
|
+
}
|
|
535
581
|
/**
|
|
536
582
|
* One viewer's conversation with the report server, and the state the viewer shows.
|
|
537
583
|
*
|
|
538
584
|
* Every signal changes only in this service's methods and in the answers they handle; nothing reacts to
|
|
539
585
|
* a signal changing. Each viewer provides its own session, so viewers on one page never share state.
|
|
540
586
|
*/
|
|
541
|
-
class
|
|
542
|
-
client = inject(
|
|
587
|
+
class NxeViewerSessionService {
|
|
588
|
+
client = inject(NxeViewerClientService);
|
|
543
589
|
optionsState = signal(null, /* @ts-ignore */
|
|
544
590
|
...(ngDevMode ? [{ debugName: "optionsState" }] : /* istanbul ignore next */ []));
|
|
545
591
|
reportState = signal(null, /* @ts-ignore */
|
|
@@ -575,6 +621,19 @@ class NxrViewerSessionService {
|
|
|
575
621
|
return { index, size: report.pages[index], html: this.htmlState().get(index) ?? null };
|
|
576
622
|
}, /* @ts-ignore */
|
|
577
623
|
...(ngDevMode ? [{ debugName: "shownPage" }] : /* istanbul ignore next */ []));
|
|
624
|
+
/**
|
|
625
|
+
* Every page of the report, in order, each with its HTML when it has arrived. The page modes that show
|
|
626
|
+
* more than one page at a time lay these out and ask for the ones that come into view; a page holds its
|
|
627
|
+
* size, and so its place, before its HTML is here.
|
|
628
|
+
*/
|
|
629
|
+
allPages = computed(() => {
|
|
630
|
+
const report = this.reportState();
|
|
631
|
+
if (!report)
|
|
632
|
+
return [];
|
|
633
|
+
const html = this.htmlState();
|
|
634
|
+
return report.pages.map((size, index) => ({ index, size, html: html.get(index) ?? null }));
|
|
635
|
+
}, /* @ts-ignore */
|
|
636
|
+
...(ngDevMode ? [{ debugName: "allPages" }] : /* istanbul ignore next */ []));
|
|
578
637
|
/** Emits after each successful GetReport answer. */
|
|
579
638
|
reportLoaded = new Subject();
|
|
580
639
|
/** Emits each failure put on show. */
|
|
@@ -629,6 +688,34 @@ class NxrViewerSessionService {
|
|
|
629
688
|
this.pageIndexState.set(index);
|
|
630
689
|
this.loadPage(index);
|
|
631
690
|
}
|
|
691
|
+
/**
|
|
692
|
+
* Asks for a page's HTML without moving the page on screen, for the modes that show several pages at
|
|
693
|
+
* once and fetch the ones that come into view. A page already here, or already on its way, is left alone.
|
|
694
|
+
*/
|
|
695
|
+
requestPage(index) {
|
|
696
|
+
if (this.destroyed || !Number.isInteger(index) || index < 0)
|
|
697
|
+
return;
|
|
698
|
+
this.loadPage(index);
|
|
699
|
+
}
|
|
700
|
+
/** Moves the page on screen without asking for it, for scroll tracking, which follows what is drawn. */
|
|
701
|
+
markCurrentPage(index) {
|
|
702
|
+
if (this.destroyed || !Number.isInteger(index) || index < 0 || index >= this.pageCount())
|
|
703
|
+
return;
|
|
704
|
+
this.pageIndexState.set(index);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* ExportReport: the report as a file, handed to the browser to save. The export never times out, so
|
|
708
|
+
* it is tracked as a request the user waits for and the toolbar stays disabled until the file is here.
|
|
709
|
+
*/
|
|
710
|
+
exportTo(format) {
|
|
711
|
+
if (this.destroyed || this.session === null || this.context === null)
|
|
712
|
+
return;
|
|
713
|
+
const { context, session } = this.connection();
|
|
714
|
+
this.track(this.client.exportReport(context, session, format), false, (answer) => {
|
|
715
|
+
const name = answer.fileName || `${this.reportState()?.reportName ?? 'report'}.${format.toLowerCase()}`;
|
|
716
|
+
saveBlob(answer.blob, name);
|
|
717
|
+
});
|
|
718
|
+
}
|
|
632
719
|
/** Takes the failure off show. */
|
|
633
720
|
dismissFailure() {
|
|
634
721
|
this.failureState.set(null);
|
|
@@ -700,7 +787,7 @@ class NxrViewerSessionService {
|
|
|
700
787
|
const { allowAutoUpdateCache, cacheMode } = session.options.server;
|
|
701
788
|
if (!allowAutoUpdateCache || cacheMode === 'None')
|
|
702
789
|
return;
|
|
703
|
-
this.keepAlive = interval(
|
|
790
|
+
this.keepAlive = interval(NXE_KEEP_ALIVE_MS)
|
|
704
791
|
.pipe(exhaustMap(() => this.client.updateCache(context, session).pipe(catchError(() => EMPTY))))
|
|
705
792
|
.subscribe();
|
|
706
793
|
}
|
|
@@ -719,15 +806,19 @@ class NxrViewerSessionService {
|
|
|
719
806
|
throw new Error('The viewer has no report open.');
|
|
720
807
|
return { context: this.context, session: this.session };
|
|
721
808
|
}
|
|
722
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
723
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
809
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeViewerSessionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
810
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeViewerSessionService });
|
|
724
811
|
}
|
|
725
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
812
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeViewerSessionService, decorators: [{
|
|
726
813
|
type: Injectable
|
|
727
814
|
}], ctorParameters: () => [] });
|
|
728
815
|
|
|
729
816
|
/** The width of the page-number box, as Stimulsoft's page control sizes it. */
|
|
730
817
|
const PAGE_BOX_WIDTH_PX = 45;
|
|
818
|
+
/** The zoom menu's fixed percentages, in Stimulsoft's order, above the two fit items. */
|
|
819
|
+
const ZOOM_PRESETS = [25, 50, 75, 100, 150, 200];
|
|
820
|
+
/** The view-mode menu's items, in Stimulsoft's order. */
|
|
821
|
+
const VIEW_MODES = ['SinglePage', 'Continuous', 'MultiplePages'];
|
|
731
822
|
/**
|
|
732
823
|
* The toolbar's page navigation.
|
|
733
824
|
*
|
|
@@ -742,7 +833,7 @@ const PAGE_BOX_WIDTH_PX = 45;
|
|
|
742
833
|
* report it would move past, and everything is disabled while a request the user waits for is on
|
|
743
834
|
* its way.
|
|
744
835
|
*/
|
|
745
|
-
class
|
|
836
|
+
class NxeViewerToolbarComponent {
|
|
746
837
|
/** The options from the server, or null before the first answer, when the toolbar shows nothing. */
|
|
747
838
|
options = input(null, /* @ts-ignore */
|
|
748
839
|
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
@@ -756,11 +847,62 @@ class NxrViewerToolbarComponent {
|
|
|
756
847
|
busy = input(false, /* @ts-ignore */
|
|
757
848
|
...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
758
849
|
/** The captions to show. */
|
|
759
|
-
strings = input(
|
|
850
|
+
strings = input(NXE_ENGLISH, /* @ts-ignore */
|
|
760
851
|
...(ngDevMode ? [{ debugName: "strings" }] : /* istanbul ignore next */ []));
|
|
852
|
+
/** The mode the pages are shown in. */
|
|
853
|
+
viewMode = input('SinglePage', /* @ts-ignore */
|
|
854
|
+
...(ngDevMode ? [{ debugName: "viewMode" }] : /* istanbul ignore next */ []));
|
|
855
|
+
/** The zoom the pages are drawn at, as a factor: 1 is 100 %. */
|
|
856
|
+
zoom = input(1, /* @ts-ignore */
|
|
857
|
+
...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
|
|
761
858
|
/** The 0-based page the user asked for. */
|
|
762
859
|
goToPage = output();
|
|
860
|
+
/** The mode the user chose. */
|
|
861
|
+
viewModeChange = output();
|
|
862
|
+
/** The zoom the user chose, as a factor. */
|
|
863
|
+
zoomChange = output();
|
|
864
|
+
/** The user asked to fit a whole page, or the page's width, in the panel. */
|
|
865
|
+
fitRequest = output();
|
|
866
|
+
/** The format the user asked the report to be saved as. */
|
|
867
|
+
saveRequest = output();
|
|
763
868
|
pageBoxWidth = PAGE_BOX_WIDTH_PX;
|
|
869
|
+
zoomPresets = ZOOM_PRESETS;
|
|
870
|
+
viewModes = VIEW_MODES;
|
|
871
|
+
/** Which menu is open, or null. Stimulsoft opens one at a time and closes it on the next click. */
|
|
872
|
+
openMenuState = signal(null, /* @ts-ignore */
|
|
873
|
+
...(ngDevMode ? [{ debugName: "openMenuState" }] : /* istanbul ignore next */ []));
|
|
874
|
+
openMenu = this.openMenuState.asReadonly();
|
|
875
|
+
/**
|
|
876
|
+
* The save menu's formats, in the order Stimulsoft lists them, and only those the server turned on.
|
|
877
|
+
* The button itself is left out when no format is on, as Stimulsoft forces it off then.
|
|
878
|
+
*/
|
|
879
|
+
saveFormats = computed(() => {
|
|
880
|
+
const exports = this.options()?.exports;
|
|
881
|
+
if (!exports)
|
|
882
|
+
return [];
|
|
883
|
+
const formats = [];
|
|
884
|
+
if (exports.showExportToPdf)
|
|
885
|
+
formats.push('Pdf');
|
|
886
|
+
if (exports.showExportToHtml)
|
|
887
|
+
formats.push('Html');
|
|
888
|
+
return formats;
|
|
889
|
+
}, /* @ts-ignore */
|
|
890
|
+
...(ngDevMode ? [{ debugName: "saveFormats" }] : /* istanbul ignore next */ []));
|
|
891
|
+
showSave = computed(() => this.toolbar()?.showSaveButton === true && this.saveFormats().length > 0, /* @ts-ignore */
|
|
892
|
+
...(ngDevMode ? [{ debugName: "showSave" }] : /* istanbul ignore next */ []));
|
|
893
|
+
save(format) {
|
|
894
|
+
this.openMenuState.set(null);
|
|
895
|
+
this.saveRequest.emit(format);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* The zoom as a rounded percentage, as Stimulsoft captions the button, and empty when the zoom is
|
|
899
|
+
* unknown or zero.
|
|
900
|
+
*/
|
|
901
|
+
zoomCaption = computed(() => {
|
|
902
|
+
const zoom = this.zoom();
|
|
903
|
+
return Number.isFinite(zoom) && zoom > 0 ? `${Math.round(zoom * 100)}%` : '';
|
|
904
|
+
}, /* @ts-ignore */
|
|
905
|
+
...(ngDevMode ? [{ debugName: "zoomCaption" }] : /* istanbul ignore next */ []));
|
|
764
906
|
toolbar = computed(() => this.options()?.toolbar ?? null, /* @ts-ignore */
|
|
765
907
|
...(ngDevMode ? [{ debugName: "toolbar" }] : /* istanbul ignore next */ []));
|
|
766
908
|
appearance = computed(() => this.options()?.appearance ?? null, /* @ts-ignore */
|
|
@@ -791,6 +933,30 @@ class NxrViewerToolbarComponent {
|
|
|
791
933
|
...(ngDevMode ? [{ debugName: "showNextPage" }] : /* istanbul ignore next */ []));
|
|
792
934
|
showLastPage = computed(() => this.toolbar()?.showLastPageButton === true, /* @ts-ignore */
|
|
793
935
|
...(ngDevMode ? [{ debugName: "showLastPage" }] : /* istanbul ignore next */ []));
|
|
936
|
+
showZoom = computed(() => this.toolbar()?.showZoomButton === true, /* @ts-ignore */
|
|
937
|
+
...(ngDevMode ? [{ debugName: "showZoom" }] : /* istanbul ignore next */ []));
|
|
938
|
+
showViewMode = computed(() => this.toolbar()?.showViewModeButton === true, /* @ts-ignore */
|
|
939
|
+
...(ngDevMode ? [{ debugName: "showViewMode" }] : /* istanbul ignore next */ []));
|
|
940
|
+
/** Whether a preset is the zoom on show, so its menu item reads as chosen. */
|
|
941
|
+
isZoom(percent) {
|
|
942
|
+
return Math.round(this.zoom() * 100) === percent;
|
|
943
|
+
}
|
|
944
|
+
/** Opens a menu, or closes it when it is the one already open. */
|
|
945
|
+
toggleMenu(menu) {
|
|
946
|
+
this.openMenuState.update((open) => (open === menu ? null : menu));
|
|
947
|
+
}
|
|
948
|
+
chooseZoom(percent) {
|
|
949
|
+
this.openMenuState.set(null);
|
|
950
|
+
this.zoomChange.emit(percent / 100);
|
|
951
|
+
}
|
|
952
|
+
fit(what) {
|
|
953
|
+
this.openMenuState.set(null);
|
|
954
|
+
this.fitRequest.emit(what);
|
|
955
|
+
}
|
|
956
|
+
chooseViewMode(mode) {
|
|
957
|
+
this.openMenuState.set(null);
|
|
958
|
+
this.viewModeChange.emit(mode);
|
|
959
|
+
}
|
|
794
960
|
atStart = computed(() => this.busy() || this.navigationDisabled() || this.pageIndex() <= 0, /* @ts-ignore */
|
|
795
961
|
...(ngDevMode ? [{ debugName: "atStart" }] : /* istanbul ignore next */ []));
|
|
796
962
|
atEnd = computed(() => this.busy() || this.navigationDisabled() || this.pageIndex() >= this.pageCount() - 1, /* @ts-ignore */
|
|
@@ -838,12 +1004,53 @@ class NxrViewerToolbarComponent {
|
|
|
838
1004
|
return;
|
|
839
1005
|
this.goToPage.emit(index);
|
|
840
1006
|
}
|
|
841
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
842
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type:
|
|
1007
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeViewerToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1008
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeViewerToolbarComponent, isStandalone: true, selector: "nxe-viewer-toolbar", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, pageIndex: { classPropertyName: "pageIndex", publicName: "pageIndex", isSignal: true, isRequired: false, transformFunction: null }, pageCount: { classPropertyName: "pageCount", publicName: "pageCount", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null }, strings: { classPropertyName: "strings", publicName: "strings", isSignal: true, isRequired: false, transformFunction: null }, viewMode: { classPropertyName: "viewMode", publicName: "viewMode", isSignal: true, isRequired: false, transformFunction: null }, zoom: { classPropertyName: "zoom", publicName: "zoom", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { goToPage: "goToPage", viewModeChange: "viewModeChange", zoomChange: "zoomChange", fitRequest: "fitRequest", saveRequest: "saveRequest" }, host: { attributes: { "role": "toolbar" }, properties: { "style.background-color": "backgroundColor()", "style.border": "border()", "style.color": "fontColor()", "style.font-family": "fontFamily()", "style.justify-content": "justifyContent()", "class.nxe-busy": "busy()" }, classAttribute: "nxe-viewer-toolbar" }, ngImport: i0, template: `
|
|
1009
|
+
@if (showSave()) {
|
|
1010
|
+
<div class="nxe-toolbar-menu">
|
|
1011
|
+
<button
|
|
1012
|
+
type="button"
|
|
1013
|
+
class="nxe-toolbar-button nxe-toolbar-captioned"
|
|
1014
|
+
[attr.aria-label]="strings().SaveToolTip"
|
|
1015
|
+
[attr.aria-expanded]="openMenu() === 'save'"
|
|
1016
|
+
(click)="toggleMenu('save')"
|
|
1017
|
+
>
|
|
1018
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
1019
|
+
<path
|
|
1020
|
+
d="M2 2h9.5L14 4.5V14H2zm2.5 0v4.5h6V2zm-1 7.5h9V14h-9zM8.5 3h1.5v2.5H8.5z"
|
|
1021
|
+
/>
|
|
1022
|
+
</svg>
|
|
1023
|
+
<span class="nxe-toolbar-caption">{{ strings().Save }}</span>
|
|
1024
|
+
<svg class="nxe-toolbar-arrow" viewBox="0 0 8 8" aria-hidden="true">
|
|
1025
|
+
<path d="M1 3h6L4 6.2z" />
|
|
1026
|
+
</svg>
|
|
1027
|
+
</button>
|
|
1028
|
+
@if (openMenu() === 'save') {
|
|
1029
|
+
<div class="nxe-toolbar-popup" role="menu">
|
|
1030
|
+
@for (format of saveFormats(); track format) {
|
|
1031
|
+
<button
|
|
1032
|
+
type="button"
|
|
1033
|
+
class="nxe-toolbar-item"
|
|
1034
|
+
role="menuitem"
|
|
1035
|
+
(click)="save(format)"
|
|
1036
|
+
>
|
|
1037
|
+
<span class="nxe-toolbar-item-icon">
|
|
1038
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
1039
|
+
<path d="M3 1h7l3 3v11H3zm6.6 1.2v2.2h2.2z" />
|
|
1040
|
+
</svg>
|
|
1041
|
+
</span>
|
|
1042
|
+
{{ format }}
|
|
1043
|
+
</button>
|
|
1044
|
+
}
|
|
1045
|
+
</div>
|
|
1046
|
+
}
|
|
1047
|
+
</div>
|
|
1048
|
+
<span class="nxe-toolbar-separator"></span>
|
|
1049
|
+
}
|
|
843
1050
|
@if (showFirstPage()) {
|
|
844
1051
|
<button
|
|
845
1052
|
type="button"
|
|
846
|
-
class="
|
|
1053
|
+
class="nxe-toolbar-button"
|
|
847
1054
|
[disabled]="atStart()"
|
|
848
1055
|
[attr.aria-label]="strings().FirstPage"
|
|
849
1056
|
(click)="goTo(0)"
|
|
@@ -856,7 +1063,7 @@ class NxrViewerToolbarComponent {
|
|
|
856
1063
|
@if (showPreviousPage()) {
|
|
857
1064
|
<button
|
|
858
1065
|
type="button"
|
|
859
|
-
class="
|
|
1066
|
+
class="nxe-toolbar-button"
|
|
860
1067
|
[disabled]="atStart()"
|
|
861
1068
|
[attr.aria-label]="strings().PrevPage"
|
|
862
1069
|
(click)="goTo(pageIndex() - 1)"
|
|
@@ -865,25 +1072,25 @@ class NxrViewerToolbarComponent {
|
|
|
865
1072
|
</button>
|
|
866
1073
|
}
|
|
867
1074
|
@if (showCurrentPage()) {
|
|
868
|
-
<span class="
|
|
869
|
-
<span class="
|
|
1075
|
+
<span class="nxe-page-control">
|
|
1076
|
+
<span class="nxe-page-label">{{ strings().Page }}</span>
|
|
870
1077
|
<input
|
|
871
1078
|
type="text"
|
|
872
1079
|
inputmode="numeric"
|
|
873
|
-
class="
|
|
1080
|
+
class="nxe-page-box"
|
|
874
1081
|
[style.width.px]="pageBoxWidth"
|
|
875
1082
|
[disabled]="pageControlDisabled()"
|
|
876
1083
|
[value]="pageIndex() + 1"
|
|
877
1084
|
[attr.aria-label]="strings().Page"
|
|
878
1085
|
(keydown.enter)="enterPage($event)"
|
|
879
1086
|
/>
|
|
880
|
-
<span class="
|
|
1087
|
+
<span class="nxe-page-label">{{ strings().PageOf }} {{ pageCount() }}</span>
|
|
881
1088
|
</span>
|
|
882
1089
|
}
|
|
883
1090
|
@if (showNextPage()) {
|
|
884
1091
|
<button
|
|
885
1092
|
type="button"
|
|
886
|
-
class="
|
|
1093
|
+
class="nxe-toolbar-button"
|
|
887
1094
|
[disabled]="atEnd()"
|
|
888
1095
|
[attr.aria-label]="strings().NextPage"
|
|
889
1096
|
(click)="goTo(pageIndex() + 1)"
|
|
@@ -894,7 +1101,7 @@ class NxrViewerToolbarComponent {
|
|
|
894
1101
|
@if (showLastPage()) {
|
|
895
1102
|
<button
|
|
896
1103
|
type="button"
|
|
897
|
-
class="
|
|
1104
|
+
class="nxe-toolbar-button"
|
|
898
1105
|
[disabled]="atEnd()"
|
|
899
1106
|
[attr.aria-label]="strings().LastPage"
|
|
900
1107
|
(click)="goTo(pageCount() - 1)"
|
|
@@ -904,24 +1111,166 @@ class NxrViewerToolbarComponent {
|
|
|
904
1111
|
</svg>
|
|
905
1112
|
</button>
|
|
906
1113
|
}
|
|
907
|
-
|
|
1114
|
+
@if (showZoom() || showViewMode()) {
|
|
1115
|
+
<span class="nxe-toolbar-separator"></span>
|
|
1116
|
+
}
|
|
1117
|
+
@if (showZoom()) {
|
|
1118
|
+
<div class="nxe-toolbar-menu">
|
|
1119
|
+
<button
|
|
1120
|
+
type="button"
|
|
1121
|
+
class="nxe-toolbar-button nxe-toolbar-captioned"
|
|
1122
|
+
[attr.aria-label]="strings().ZoomToolTip"
|
|
1123
|
+
[attr.aria-expanded]="openMenu() === 'zoom'"
|
|
1124
|
+
(click)="toggleMenu('zoom')"
|
|
1125
|
+
>
|
|
1126
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
1127
|
+
<path
|
|
1128
|
+
d="M3 2h7l3 3v9H3zm6.6 1.2v2.2h2.2zM5 8h6v1.1H5zm0 2.4h6v1.1H5z"
|
|
1129
|
+
/>
|
|
1130
|
+
</svg>
|
|
1131
|
+
<span class="nxe-toolbar-caption">{{ zoomCaption() }}</span>
|
|
1132
|
+
<svg class="nxe-toolbar-arrow" viewBox="0 0 8 8" aria-hidden="true">
|
|
1133
|
+
<path d="M1 3h6L4 6.2z" />
|
|
1134
|
+
</svg>
|
|
1135
|
+
</button>
|
|
1136
|
+
@if (openMenu() === 'zoom') {
|
|
1137
|
+
<div class="nxe-toolbar-popup" role="menu">
|
|
1138
|
+
@for (percent of zoomPresets; track percent) {
|
|
1139
|
+
<button
|
|
1140
|
+
type="button"
|
|
1141
|
+
class="nxe-toolbar-item"
|
|
1142
|
+
role="menuitem"
|
|
1143
|
+
[class.nxe-toolbar-item-on]="isZoom(percent)"
|
|
1144
|
+
(click)="chooseZoom(percent)"
|
|
1145
|
+
>
|
|
1146
|
+
<span class="nxe-toolbar-item-icon"></span>{{ percent }}%
|
|
1147
|
+
</button>
|
|
1148
|
+
}
|
|
1149
|
+
<span class="nxe-toolbar-item-separator"></span>
|
|
1150
|
+
<button type="button" class="nxe-toolbar-item" role="menuitem" (click)="fit('page')">
|
|
1151
|
+
<span class="nxe-toolbar-item-icon">
|
|
1152
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 2h10v12H3zm1.2 1.2v9.6h7.6V3.2z" /></svg>
|
|
1153
|
+
</span>
|
|
1154
|
+
{{ strings().ZoomOnePage }}
|
|
1155
|
+
</button>
|
|
1156
|
+
<button type="button" class="nxe-toolbar-item" role="menuitem" (click)="fit('width')">
|
|
1157
|
+
<span class="nxe-toolbar-item-icon">
|
|
1158
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M1 6h14v4H1zm1.2 1.2v1.6h11.6V7.2z" /></svg>
|
|
1159
|
+
</span>
|
|
1160
|
+
{{ strings().ZoomPageWidth }}
|
|
1161
|
+
</button>
|
|
1162
|
+
</div>
|
|
1163
|
+
}
|
|
1164
|
+
</div>
|
|
1165
|
+
}
|
|
1166
|
+
@if (showViewMode()) {
|
|
1167
|
+
<div class="nxe-toolbar-menu">
|
|
1168
|
+
<button
|
|
1169
|
+
type="button"
|
|
1170
|
+
class="nxe-toolbar-button nxe-toolbar-captioned"
|
|
1171
|
+
[attr.aria-label]="strings().ViewModeToolTip"
|
|
1172
|
+
[attr.aria-expanded]="openMenu() === 'viewMode'"
|
|
1173
|
+
(click)="toggleMenu('viewMode')"
|
|
1174
|
+
>
|
|
1175
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 2h10v12H3zm1.2 1.2v9.6h7.6V3.2z" /></svg>
|
|
1176
|
+
<span class="nxe-toolbar-caption">{{ strings()[viewMode()] }}</span>
|
|
1177
|
+
<svg class="nxe-toolbar-arrow" viewBox="0 0 8 8" aria-hidden="true">
|
|
1178
|
+
<path d="M1 3h6L4 6.2z" />
|
|
1179
|
+
</svg>
|
|
1180
|
+
</button>
|
|
1181
|
+
@if (openMenu() === 'viewMode') {
|
|
1182
|
+
<div class="nxe-toolbar-popup" role="menu">
|
|
1183
|
+
@for (mode of viewModes; track mode) {
|
|
1184
|
+
<button
|
|
1185
|
+
type="button"
|
|
1186
|
+
class="nxe-toolbar-item"
|
|
1187
|
+
role="menuitem"
|
|
1188
|
+
[class.nxe-toolbar-item-on]="viewMode() === mode"
|
|
1189
|
+
(click)="chooseViewMode(mode)"
|
|
1190
|
+
>
|
|
1191
|
+
<span class="nxe-toolbar-item-icon">
|
|
1192
|
+
@switch (mode) {
|
|
1193
|
+
@case ('SinglePage') {
|
|
1194
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 2h10v12H3zm1.2 1.2v9.6h7.6V3.2z" /></svg>
|
|
1195
|
+
}
|
|
1196
|
+
@case ('Continuous') {
|
|
1197
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
1198
|
+
<path d="M3 1h10v6H3zm1.2 1.2v3.6h7.6V2.2zM3 9h10v6H3zm1.2 1.2v3.6h7.6v-3.6z" />
|
|
1199
|
+
</svg>
|
|
1200
|
+
}
|
|
1201
|
+
@case ('MultiplePages') {
|
|
1202
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
1203
|
+
<path d="M2 2h5v5H2zm7 0h5v5H9zM2 9h5v5H2zm7 0h5v5H9z" />
|
|
1204
|
+
</svg>
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
</span>
|
|
1208
|
+
{{ strings()[mode] }}
|
|
1209
|
+
</button>
|
|
1210
|
+
}
|
|
1211
|
+
</div>
|
|
1212
|
+
}
|
|
1213
|
+
</div>
|
|
1214
|
+
}
|
|
1215
|
+
`, isInline: true, styles: [":host{display:flex;align-items:center;gap:2px;box-sizing:border-box;padding:3px 6px;min-height:30px;position:relative;-webkit-user-select:none;user-select:none}:host(.nxe-busy):after{content:\"\";position:absolute;inset:0;cursor:default}.nxe-toolbar-button{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:0;border-radius:2px;background:transparent;color:inherit;font:inherit;cursor:pointer}.nxe-toolbar-button:hover:not(:disabled){background:#00000014}.nxe-toolbar-button:disabled{opacity:.5;cursor:default}.nxe-toolbar-button svg{width:16px;height:16px;fill:currentColor}.nxe-page-control{display:inline-flex;align-items:center;gap:4px;padding:0 4px;white-space:nowrap}.nxe-page-box{height:21px;box-sizing:border-box;text-align:center;font:inherit;color:inherit}.nxe-page-box:disabled{color:inherit;-webkit-text-fill-color:currentColor;opacity:1}.nxe-toolbar-separator{width:1px;height:18px;margin:0 4px;background:currentColor;opacity:.25}.nxe-toolbar-menu{position:relative;display:flex;align-items:center}.nxe-toolbar-captioned{width:auto;gap:4px;padding:0 6px}.nxe-toolbar-caption{font-size:12px;white-space:nowrap}.nxe-toolbar-arrow{width:8px;height:8px;fill:currentColor;flex:none}.nxe-toolbar-popup{position:absolute;top:100%;left:0;z-index:10;min-width:150px;margin-top:2px;padding:3px 0;background:#fff;color:#000;border:1px solid #c8c8c8;box-shadow:0 2px 8px #00000040;display:flex;flex-direction:column}.nxe-toolbar-item{display:flex;align-items:center;gap:8px;border:0;background:transparent;color:inherit;font:inherit;font-size:12px;text-align:left;white-space:nowrap;padding:5px 14px 5px 6px;cursor:pointer}.nxe-toolbar-item:hover{background:#00000012}.nxe-toolbar-item-on{background:#0000001c}.nxe-toolbar-item-icon{width:16px;height:16px;flex:none;display:inline-flex;align-items:center;justify-content:center}.nxe-toolbar-item-icon svg{width:16px;height:16px;fill:currentColor}.nxe-toolbar-item-separator{height:1px;margin:3px 0;background:#c8c8c8}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
908
1216
|
}
|
|
909
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
1217
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeViewerToolbarComponent, decorators: [{
|
|
910
1218
|
type: Component,
|
|
911
|
-
args: [{ selector: '
|
|
912
|
-
class: '
|
|
1219
|
+
args: [{ selector: 'nxe-viewer-toolbar', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
1220
|
+
class: 'nxe-viewer-toolbar',
|
|
913
1221
|
role: 'toolbar',
|
|
914
1222
|
'[style.background-color]': 'backgroundColor()',
|
|
915
1223
|
'[style.border]': 'border()',
|
|
916
1224
|
'[style.color]': 'fontColor()',
|
|
917
1225
|
'[style.font-family]': 'fontFamily()',
|
|
918
1226
|
'[style.justify-content]': 'justifyContent()',
|
|
919
|
-
'[class.
|
|
1227
|
+
'[class.nxe-busy]': 'busy()',
|
|
920
1228
|
}, template: `
|
|
1229
|
+
@if (showSave()) {
|
|
1230
|
+
<div class="nxe-toolbar-menu">
|
|
1231
|
+
<button
|
|
1232
|
+
type="button"
|
|
1233
|
+
class="nxe-toolbar-button nxe-toolbar-captioned"
|
|
1234
|
+
[attr.aria-label]="strings().SaveToolTip"
|
|
1235
|
+
[attr.aria-expanded]="openMenu() === 'save'"
|
|
1236
|
+
(click)="toggleMenu('save')"
|
|
1237
|
+
>
|
|
1238
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
1239
|
+
<path
|
|
1240
|
+
d="M2 2h9.5L14 4.5V14H2zm2.5 0v4.5h6V2zm-1 7.5h9V14h-9zM8.5 3h1.5v2.5H8.5z"
|
|
1241
|
+
/>
|
|
1242
|
+
</svg>
|
|
1243
|
+
<span class="nxe-toolbar-caption">{{ strings().Save }}</span>
|
|
1244
|
+
<svg class="nxe-toolbar-arrow" viewBox="0 0 8 8" aria-hidden="true">
|
|
1245
|
+
<path d="M1 3h6L4 6.2z" />
|
|
1246
|
+
</svg>
|
|
1247
|
+
</button>
|
|
1248
|
+
@if (openMenu() === 'save') {
|
|
1249
|
+
<div class="nxe-toolbar-popup" role="menu">
|
|
1250
|
+
@for (format of saveFormats(); track format) {
|
|
1251
|
+
<button
|
|
1252
|
+
type="button"
|
|
1253
|
+
class="nxe-toolbar-item"
|
|
1254
|
+
role="menuitem"
|
|
1255
|
+
(click)="save(format)"
|
|
1256
|
+
>
|
|
1257
|
+
<span class="nxe-toolbar-item-icon">
|
|
1258
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
1259
|
+
<path d="M3 1h7l3 3v11H3zm6.6 1.2v2.2h2.2z" />
|
|
1260
|
+
</svg>
|
|
1261
|
+
</span>
|
|
1262
|
+
{{ format }}
|
|
1263
|
+
</button>
|
|
1264
|
+
}
|
|
1265
|
+
</div>
|
|
1266
|
+
}
|
|
1267
|
+
</div>
|
|
1268
|
+
<span class="nxe-toolbar-separator"></span>
|
|
1269
|
+
}
|
|
921
1270
|
@if (showFirstPage()) {
|
|
922
1271
|
<button
|
|
923
1272
|
type="button"
|
|
924
|
-
class="
|
|
1273
|
+
class="nxe-toolbar-button"
|
|
925
1274
|
[disabled]="atStart()"
|
|
926
1275
|
[attr.aria-label]="strings().FirstPage"
|
|
927
1276
|
(click)="goTo(0)"
|
|
@@ -934,7 +1283,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
934
1283
|
@if (showPreviousPage()) {
|
|
935
1284
|
<button
|
|
936
1285
|
type="button"
|
|
937
|
-
class="
|
|
1286
|
+
class="nxe-toolbar-button"
|
|
938
1287
|
[disabled]="atStart()"
|
|
939
1288
|
[attr.aria-label]="strings().PrevPage"
|
|
940
1289
|
(click)="goTo(pageIndex() - 1)"
|
|
@@ -943,25 +1292,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
943
1292
|
</button>
|
|
944
1293
|
}
|
|
945
1294
|
@if (showCurrentPage()) {
|
|
946
|
-
<span class="
|
|
947
|
-
<span class="
|
|
1295
|
+
<span class="nxe-page-control">
|
|
1296
|
+
<span class="nxe-page-label">{{ strings().Page }}</span>
|
|
948
1297
|
<input
|
|
949
1298
|
type="text"
|
|
950
1299
|
inputmode="numeric"
|
|
951
|
-
class="
|
|
1300
|
+
class="nxe-page-box"
|
|
952
1301
|
[style.width.px]="pageBoxWidth"
|
|
953
1302
|
[disabled]="pageControlDisabled()"
|
|
954
1303
|
[value]="pageIndex() + 1"
|
|
955
1304
|
[attr.aria-label]="strings().Page"
|
|
956
1305
|
(keydown.enter)="enterPage($event)"
|
|
957
1306
|
/>
|
|
958
|
-
<span class="
|
|
1307
|
+
<span class="nxe-page-label">{{ strings().PageOf }} {{ pageCount() }}</span>
|
|
959
1308
|
</span>
|
|
960
1309
|
}
|
|
961
1310
|
@if (showNextPage()) {
|
|
962
1311
|
<button
|
|
963
1312
|
type="button"
|
|
964
|
-
class="
|
|
1313
|
+
class="nxe-toolbar-button"
|
|
965
1314
|
[disabled]="atEnd()"
|
|
966
1315
|
[attr.aria-label]="strings().NextPage"
|
|
967
1316
|
(click)="goTo(pageIndex() + 1)"
|
|
@@ -972,7 +1321,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
972
1321
|
@if (showLastPage()) {
|
|
973
1322
|
<button
|
|
974
1323
|
type="button"
|
|
975
|
-
class="
|
|
1324
|
+
class="nxe-toolbar-button"
|
|
976
1325
|
[disabled]="atEnd()"
|
|
977
1326
|
[attr.aria-label]="strings().LastPage"
|
|
978
1327
|
(click)="goTo(pageCount() - 1)"
|
|
@@ -982,9 +1331,118 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
982
1331
|
</svg>
|
|
983
1332
|
</button>
|
|
984
1333
|
}
|
|
985
|
-
|
|
986
|
-
|
|
1334
|
+
@if (showZoom() || showViewMode()) {
|
|
1335
|
+
<span class="nxe-toolbar-separator"></span>
|
|
1336
|
+
}
|
|
1337
|
+
@if (showZoom()) {
|
|
1338
|
+
<div class="nxe-toolbar-menu">
|
|
1339
|
+
<button
|
|
1340
|
+
type="button"
|
|
1341
|
+
class="nxe-toolbar-button nxe-toolbar-captioned"
|
|
1342
|
+
[attr.aria-label]="strings().ZoomToolTip"
|
|
1343
|
+
[attr.aria-expanded]="openMenu() === 'zoom'"
|
|
1344
|
+
(click)="toggleMenu('zoom')"
|
|
1345
|
+
>
|
|
1346
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
1347
|
+
<path
|
|
1348
|
+
d="M3 2h7l3 3v9H3zm6.6 1.2v2.2h2.2zM5 8h6v1.1H5zm0 2.4h6v1.1H5z"
|
|
1349
|
+
/>
|
|
1350
|
+
</svg>
|
|
1351
|
+
<span class="nxe-toolbar-caption">{{ zoomCaption() }}</span>
|
|
1352
|
+
<svg class="nxe-toolbar-arrow" viewBox="0 0 8 8" aria-hidden="true">
|
|
1353
|
+
<path d="M1 3h6L4 6.2z" />
|
|
1354
|
+
</svg>
|
|
1355
|
+
</button>
|
|
1356
|
+
@if (openMenu() === 'zoom') {
|
|
1357
|
+
<div class="nxe-toolbar-popup" role="menu">
|
|
1358
|
+
@for (percent of zoomPresets; track percent) {
|
|
1359
|
+
<button
|
|
1360
|
+
type="button"
|
|
1361
|
+
class="nxe-toolbar-item"
|
|
1362
|
+
role="menuitem"
|
|
1363
|
+
[class.nxe-toolbar-item-on]="isZoom(percent)"
|
|
1364
|
+
(click)="chooseZoom(percent)"
|
|
1365
|
+
>
|
|
1366
|
+
<span class="nxe-toolbar-item-icon"></span>{{ percent }}%
|
|
1367
|
+
</button>
|
|
1368
|
+
}
|
|
1369
|
+
<span class="nxe-toolbar-item-separator"></span>
|
|
1370
|
+
<button type="button" class="nxe-toolbar-item" role="menuitem" (click)="fit('page')">
|
|
1371
|
+
<span class="nxe-toolbar-item-icon">
|
|
1372
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 2h10v12H3zm1.2 1.2v9.6h7.6V3.2z" /></svg>
|
|
1373
|
+
</span>
|
|
1374
|
+
{{ strings().ZoomOnePage }}
|
|
1375
|
+
</button>
|
|
1376
|
+
<button type="button" class="nxe-toolbar-item" role="menuitem" (click)="fit('width')">
|
|
1377
|
+
<span class="nxe-toolbar-item-icon">
|
|
1378
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M1 6h14v4H1zm1.2 1.2v1.6h11.6V7.2z" /></svg>
|
|
1379
|
+
</span>
|
|
1380
|
+
{{ strings().ZoomPageWidth }}
|
|
1381
|
+
</button>
|
|
1382
|
+
</div>
|
|
1383
|
+
}
|
|
1384
|
+
</div>
|
|
1385
|
+
}
|
|
1386
|
+
@if (showViewMode()) {
|
|
1387
|
+
<div class="nxe-toolbar-menu">
|
|
1388
|
+
<button
|
|
1389
|
+
type="button"
|
|
1390
|
+
class="nxe-toolbar-button nxe-toolbar-captioned"
|
|
1391
|
+
[attr.aria-label]="strings().ViewModeToolTip"
|
|
1392
|
+
[attr.aria-expanded]="openMenu() === 'viewMode'"
|
|
1393
|
+
(click)="toggleMenu('viewMode')"
|
|
1394
|
+
>
|
|
1395
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 2h10v12H3zm1.2 1.2v9.6h7.6V3.2z" /></svg>
|
|
1396
|
+
<span class="nxe-toolbar-caption">{{ strings()[viewMode()] }}</span>
|
|
1397
|
+
<svg class="nxe-toolbar-arrow" viewBox="0 0 8 8" aria-hidden="true">
|
|
1398
|
+
<path d="M1 3h6L4 6.2z" />
|
|
1399
|
+
</svg>
|
|
1400
|
+
</button>
|
|
1401
|
+
@if (openMenu() === 'viewMode') {
|
|
1402
|
+
<div class="nxe-toolbar-popup" role="menu">
|
|
1403
|
+
@for (mode of viewModes; track mode) {
|
|
1404
|
+
<button
|
|
1405
|
+
type="button"
|
|
1406
|
+
class="nxe-toolbar-item"
|
|
1407
|
+
role="menuitem"
|
|
1408
|
+
[class.nxe-toolbar-item-on]="viewMode() === mode"
|
|
1409
|
+
(click)="chooseViewMode(mode)"
|
|
1410
|
+
>
|
|
1411
|
+
<span class="nxe-toolbar-item-icon">
|
|
1412
|
+
@switch (mode) {
|
|
1413
|
+
@case ('SinglePage') {
|
|
1414
|
+
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M3 2h10v12H3zm1.2 1.2v9.6h7.6V3.2z" /></svg>
|
|
1415
|
+
}
|
|
1416
|
+
@case ('Continuous') {
|
|
1417
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
1418
|
+
<path d="M3 1h10v6H3zm1.2 1.2v3.6h7.6V2.2zM3 9h10v6H3zm1.2 1.2v3.6h7.6v-3.6z" />
|
|
1419
|
+
</svg>
|
|
1420
|
+
}
|
|
1421
|
+
@case ('MultiplePages') {
|
|
1422
|
+
<svg viewBox="0 0 16 16" aria-hidden="true">
|
|
1423
|
+
<path d="M2 2h5v5H2zm7 0h5v5H9zM2 9h5v5H2zm7 0h5v5H9z" />
|
|
1424
|
+
</svg>
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
</span>
|
|
1428
|
+
{{ strings()[mode] }}
|
|
1429
|
+
</button>
|
|
1430
|
+
}
|
|
1431
|
+
</div>
|
|
1432
|
+
}
|
|
1433
|
+
</div>
|
|
1434
|
+
}
|
|
1435
|
+
`, styles: [":host{display:flex;align-items:center;gap:2px;box-sizing:border-box;padding:3px 6px;min-height:30px;position:relative;-webkit-user-select:none;user-select:none}:host(.nxe-busy):after{content:\"\";position:absolute;inset:0;cursor:default}.nxe-toolbar-button{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:0;border-radius:2px;background:transparent;color:inherit;font:inherit;cursor:pointer}.nxe-toolbar-button:hover:not(:disabled){background:#00000014}.nxe-toolbar-button:disabled{opacity:.5;cursor:default}.nxe-toolbar-button svg{width:16px;height:16px;fill:currentColor}.nxe-page-control{display:inline-flex;align-items:center;gap:4px;padding:0 4px;white-space:nowrap}.nxe-page-box{height:21px;box-sizing:border-box;text-align:center;font:inherit;color:inherit}.nxe-page-box:disabled{color:inherit;-webkit-text-fill-color:currentColor;opacity:1}.nxe-toolbar-separator{width:1px;height:18px;margin:0 4px;background:currentColor;opacity:.25}.nxe-toolbar-menu{position:relative;display:flex;align-items:center}.nxe-toolbar-captioned{width:auto;gap:4px;padding:0 6px}.nxe-toolbar-caption{font-size:12px;white-space:nowrap}.nxe-toolbar-arrow{width:8px;height:8px;fill:currentColor;flex:none}.nxe-toolbar-popup{position:absolute;top:100%;left:0;z-index:10;min-width:150px;margin-top:2px;padding:3px 0;background:#fff;color:#000;border:1px solid #c8c8c8;box-shadow:0 2px 8px #00000040;display:flex;flex-direction:column}.nxe-toolbar-item{display:flex;align-items:center;gap:8px;border:0;background:transparent;color:inherit;font:inherit;font-size:12px;text-align:left;white-space:nowrap;padding:5px 14px 5px 6px;cursor:pointer}.nxe-toolbar-item:hover{background:#00000012}.nxe-toolbar-item-on{background:#0000001c}.nxe-toolbar-item-icon{width:16px;height:16px;flex:none;display:inline-flex;align-items:center;justify-content:center}.nxe-toolbar-item-icon svg{width:16px;height:16px;fill:currentColor}.nxe-toolbar-item-separator{height:1px;margin:3px 0;background:#c8c8c8}\n"] }]
|
|
1436
|
+
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], pageIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageIndex", required: false }] }], pageCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageCount", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], strings: [{ type: i0.Input, args: [{ isSignal: true, alias: "strings", required: false }] }], viewMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewMode", required: false }] }], zoom: [{ type: i0.Input, args: [{ isSignal: true, alias: "zoom", required: false }] }], goToPage: [{ type: i0.Output, args: ["goToPage"] }], viewModeChange: [{ type: i0.Output, args: ["viewModeChange"] }], zoomChange: [{ type: i0.Output, args: ["zoomChange"] }], fitRequest: [{ type: i0.Output, args: ["fitRequest"] }], saveRequest: [{ type: i0.Output, args: ["saveRequest"] }] } });
|
|
987
1437
|
|
|
1438
|
+
/** Stimulsoft's scroll tracker waits this long after scrolling stops before it moves the page. */
|
|
1439
|
+
const SCROLL_SETTLE_MS = 300;
|
|
1440
|
+
/** And animates a navigation scroll over this long, in steps of about this many milliseconds. */
|
|
1441
|
+
const SCROLL_ANIMATION_MS = 350;
|
|
1442
|
+
const SCROLL_STEP_MS = 30;
|
|
1443
|
+
/** The viewer never measures itself smaller than this, nor closer than this to the window's bottom. */
|
|
1444
|
+
const MIN_PANEL_HEIGHT = 200;
|
|
1445
|
+
const VIEWER_BOTTOM_GAP = 16;
|
|
988
1446
|
/**
|
|
989
1447
|
* A report served by `Nextera.Reports.Angular.NetCore`, shown page by page.
|
|
990
1448
|
*
|
|
@@ -994,7 +1452,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
994
1452
|
*
|
|
995
1453
|
* Each viewer provides its own session, so several on a page never share a report or a token.
|
|
996
1454
|
*/
|
|
997
|
-
class
|
|
1455
|
+
class NxeReportViewerComponent {
|
|
998
1456
|
/** Each request's URL, with `{action}` in place of the host action it goes to. */
|
|
999
1457
|
requestUrl = input('', /* @ts-ignore */
|
|
1000
1458
|
...(ngDevMode ? [{ debugName: "requestUrl" }] : /* istanbul ignore next */ []));
|
|
@@ -1023,11 +1481,43 @@ class NxrReportViewerComponent {
|
|
|
1023
1481
|
loaded = output();
|
|
1024
1482
|
/** Emits every failure the viewer puts on show. */
|
|
1025
1483
|
error = output();
|
|
1026
|
-
session = inject(
|
|
1484
|
+
session = inject(NxeViewerSessionService);
|
|
1027
1485
|
/** Stimulsoft's `api` object: what a host calls to drive the viewer from outside. */
|
|
1028
1486
|
api = this.buildApi();
|
|
1029
1487
|
appearance = computed(() => this.session.options()?.appearance ?? null, /* @ts-ignore */
|
|
1030
1488
|
...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
|
|
1489
|
+
pagesBox = viewChild('pagesBox', /* @ts-ignore */
|
|
1490
|
+
...(ngDevMode ? [{ debugName: "pagesBox" }] : /* istanbul ignore next */ []));
|
|
1491
|
+
rootBox = viewChild('rootBox', /* @ts-ignore */
|
|
1492
|
+
...(ngDevMode ? [{ debugName: "rootBox" }] : /* istanbul ignore next */ []));
|
|
1493
|
+
/**
|
|
1494
|
+
* The mode the pages are shown in, and the zoom they are drawn at. Both start from the server's
|
|
1495
|
+
* options and are then the user's, until a new report's options arrive and reset them — which is
|
|
1496
|
+
* what `linkedSignal` says and an effect would only imply.
|
|
1497
|
+
*/
|
|
1498
|
+
viewMode = linkedSignal(() => this.session.options()?.toolbar.viewMode ?? 'SinglePage', /* @ts-ignore */
|
|
1499
|
+
...(ngDevMode ? [{ debugName: "viewMode" }] : /* istanbul ignore next */ []));
|
|
1500
|
+
zoom = linkedSignal(() => {
|
|
1501
|
+
const percent = this.session.options()?.toolbar.zoom ?? 100;
|
|
1502
|
+
return percent > 0 ? percent / 100 : 1;
|
|
1503
|
+
}, /* @ts-ignore */
|
|
1504
|
+
...(ngDevMode ? [{ debugName: "zoom" }] : /* istanbul ignore next */ []));
|
|
1505
|
+
/**
|
|
1506
|
+
* The pages on screen: one in `SinglePage`, the whole report in the modes that scroll through it.
|
|
1507
|
+
* A page carries its size before its HTML arrives, so the list holds its shape while the pages that
|
|
1508
|
+
* come into view are fetched.
|
|
1509
|
+
*/
|
|
1510
|
+
shownPages = computed(() => {
|
|
1511
|
+
if (this.viewMode() === 'SinglePage') {
|
|
1512
|
+
const page = this.session.shownPage();
|
|
1513
|
+
return page ? [page] : [];
|
|
1514
|
+
}
|
|
1515
|
+
return this.session.allPages();
|
|
1516
|
+
}, /* @ts-ignore */
|
|
1517
|
+
...(ngDevMode ? [{ debugName: "shownPages" }] : /* istanbul ignore next */ []));
|
|
1518
|
+
/** Set while a page-navigation scroll is running, so scroll tracking does not fight it. */
|
|
1519
|
+
scrollingToPage = false;
|
|
1520
|
+
scrollSettled = null;
|
|
1031
1521
|
toolbarVisible = computed(() => this.session.options()?.toolbar.visible !== false, /* @ts-ignore */
|
|
1032
1522
|
...(ngDevMode ? [{ debugName: "toolbarVisible" }] : /* istanbul ignore next */ []));
|
|
1033
1523
|
pageCss = computed(() => this.session.report()?.pageCss ?? '', /* @ts-ignore */
|
|
@@ -1058,7 +1548,22 @@ class NxrReportViewerComponent {
|
|
|
1058
1548
|
...(ngDevMode ? [{ debugName: "rootWidth" }] : /* istanbul ignore next */ []));
|
|
1059
1549
|
rootBackgroundColor = computed(() => this.backgroundColor() ?? 'White', /* @ts-ignore */
|
|
1060
1550
|
...(ngDevMode ? [{ debugName: "rootBackgroundColor" }] : /* istanbul ignore next */ []));
|
|
1551
|
+
/**
|
|
1552
|
+
* The height the viewer was measured to fill, once it is known.
|
|
1553
|
+
*
|
|
1554
|
+
* The report panel is what scrolls, as Stimulsoft's does — its panel measures 836 px against a
|
|
1555
|
+
* 16 513 px report while the document itself does not scroll at all. That needs the viewer to have a
|
|
1556
|
+
* definite height, and a host that asks for `100%` inside a container whose own height is automatic
|
|
1557
|
+
* does not give it one: the viewer would then grow to the whole report, 29 pages of it, and the
|
|
1558
|
+
* page would scroll instead of the panel. So when the height the host asked for cannot resolve, the
|
|
1559
|
+
* viewer measures the room between its top and the bottom of the window and takes that.
|
|
1560
|
+
*/
|
|
1561
|
+
measuredHeight = signal(null, /* @ts-ignore */
|
|
1562
|
+
...(ngDevMode ? [{ debugName: "measuredHeight" }] : /* istanbul ignore next */ []));
|
|
1061
1563
|
rootHeight = computed(() => {
|
|
1564
|
+
const measured = this.measuredHeight();
|
|
1565
|
+
if (measured !== null)
|
|
1566
|
+
return measured;
|
|
1062
1567
|
const given = this.height();
|
|
1063
1568
|
if (given !== null)
|
|
1064
1569
|
return given;
|
|
@@ -1066,9 +1571,12 @@ class NxrReportViewerComponent {
|
|
|
1066
1571
|
}, /* @ts-ignore */
|
|
1067
1572
|
...(ngDevMode ? [{ debugName: "rootHeight" }] : /* istanbul ignore next */ []));
|
|
1068
1573
|
constructor() {
|
|
1069
|
-
this.session.reportLoaded
|
|
1070
|
-
|
|
1071
|
-
|
|
1574
|
+
this.session.reportLoaded.pipe(takeUntilDestroyed()).subscribe(() => {
|
|
1575
|
+
// The modes that show the whole report start with every page laid out at its size and none of
|
|
1576
|
+
// them fetched, so the first screenful has to be asked for here; scrolling asks for the rest.
|
|
1577
|
+
this.afterPagesSettle();
|
|
1578
|
+
this.loaded.emit();
|
|
1579
|
+
});
|
|
1072
1580
|
this.session.failed
|
|
1073
1581
|
.pipe(takeUntilDestroyed())
|
|
1074
1582
|
.subscribe((failure) => this.error.emit(failure));
|
|
@@ -1081,6 +1589,142 @@ class NxrReportViewerComponent {
|
|
|
1081
1589
|
ngOnChanges() {
|
|
1082
1590
|
this.session.open(this.context(), this.action());
|
|
1083
1591
|
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Page navigation. In a mode that scrolls through the whole report the page is reached by scrolling
|
|
1594
|
+
* to it, over 350 ms, which is what Stimulsoft's navigation actions do; in `SinglePage` the page is
|
|
1595
|
+
* simply the one shown.
|
|
1596
|
+
*/
|
|
1597
|
+
navigateTo(index) {
|
|
1598
|
+
this.session.goToPage(index);
|
|
1599
|
+
if (this.viewMode() === 'SinglePage')
|
|
1600
|
+
return;
|
|
1601
|
+
this.scrollToPage(index);
|
|
1602
|
+
}
|
|
1603
|
+
/** The page number is kept and nothing scrolls, as Stimulsoft's view-mode actions leave it. */
|
|
1604
|
+
setViewMode(mode) {
|
|
1605
|
+
this.viewMode.set(mode);
|
|
1606
|
+
this.afterPagesSettle();
|
|
1607
|
+
}
|
|
1608
|
+
/** Nothing preserves the scroll offset across a zoom change, so the visible region shifts. */
|
|
1609
|
+
setZoom(zoom) {
|
|
1610
|
+
this.zoom.set(zoom);
|
|
1611
|
+
this.afterPagesSettle();
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Fit a whole page, or a page's width, in the panel. Stimulsoft computes this once from what is on
|
|
1615
|
+
* screen and stores the number as the zoom; no fit mode is remembered, so a later resize does not
|
|
1616
|
+
* re-fit. The 40 px it subtracts is the panel's padding and the page's margins.
|
|
1617
|
+
*/
|
|
1618
|
+
fitTo(what) {
|
|
1619
|
+
const box = this.pagesBox()?.nativeElement;
|
|
1620
|
+
const page = box?.querySelector('nxe-report-page');
|
|
1621
|
+
if (!box || !(page instanceof HTMLElement))
|
|
1622
|
+
return;
|
|
1623
|
+
const available = what === 'width' ? box.clientWidth - 40 : box.clientHeight - 40;
|
|
1624
|
+
const drawn = what === 'width' ? page.offsetWidth : page.offsetHeight;
|
|
1625
|
+
if (drawn <= 0 || available <= 0)
|
|
1626
|
+
return;
|
|
1627
|
+
this.zoom.set((available * this.zoom()) / drawn);
|
|
1628
|
+
this.afterPagesSettle();
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Scrolling asks for the pages that have come into view, and, once it has stopped for 300 ms, moves
|
|
1632
|
+
* the page the toolbar reports to the first one still on screen — Stimulsoft's scroll tracking.
|
|
1633
|
+
*/
|
|
1634
|
+
onScroll() {
|
|
1635
|
+
this.requestVisiblePages();
|
|
1636
|
+
if (this.scrollSettled !== null)
|
|
1637
|
+
clearTimeout(this.scrollSettled);
|
|
1638
|
+
this.scrollSettled = setTimeout(() => {
|
|
1639
|
+
this.scrollSettled = null;
|
|
1640
|
+
if (!this.scrollingToPage)
|
|
1641
|
+
this.trackCurrentPage();
|
|
1642
|
+
}, SCROLL_SETTLE_MS);
|
|
1643
|
+
}
|
|
1644
|
+
/** The room on screen changed, so the viewer measures itself against it again. */
|
|
1645
|
+
onWindowResize() {
|
|
1646
|
+
this.fitToAvailableRoom();
|
|
1647
|
+
}
|
|
1648
|
+
/** Lets the new layout reach the DOM, then fits the viewer to it and asks for what is on screen. */
|
|
1649
|
+
afterPagesSettle() {
|
|
1650
|
+
requestAnimationFrame(() => {
|
|
1651
|
+
this.fitToAvailableRoom();
|
|
1652
|
+
this.requestVisiblePages();
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Takes the room between the viewer's top and the bottom of the window when the height the host
|
|
1657
|
+
* asked for did not resolve, so the panel inside scrolls rather than the page. Measured before the
|
|
1658
|
+
* height is applied, and again only when the window changes, so it never chases its own answer.
|
|
1659
|
+
*/
|
|
1660
|
+
fitToAvailableRoom() {
|
|
1661
|
+
const root = this.rootBox()?.nativeElement;
|
|
1662
|
+
if (!root || !this.appearance()?.scrollbarsMode)
|
|
1663
|
+
return;
|
|
1664
|
+
const rect = root.getBoundingClientRect();
|
|
1665
|
+
const room = Math.max(MIN_PANEL_HEIGHT, Math.round(window.innerHeight - rect.top - VIEWER_BOTTOM_GAP));
|
|
1666
|
+
// A height that resolved is left alone; one that grew past the room on screen is taken over.
|
|
1667
|
+
if (this.measuredHeight() === null && rect.height <= room + 1)
|
|
1668
|
+
return;
|
|
1669
|
+
this.measuredHeight.set(`${room}px`);
|
|
1670
|
+
}
|
|
1671
|
+
/** Every page whose box meets the panel, plus one either side, so scrolling stays ahead of the eye. */
|
|
1672
|
+
requestVisiblePages() {
|
|
1673
|
+
if (this.viewMode() === 'SinglePage')
|
|
1674
|
+
return;
|
|
1675
|
+
const box = this.pagesBox()?.nativeElement;
|
|
1676
|
+
if (!box)
|
|
1677
|
+
return;
|
|
1678
|
+
const pages = box.querySelectorAll('nxe-report-page');
|
|
1679
|
+
const top = box.scrollTop - box.clientHeight;
|
|
1680
|
+
const bottom = box.scrollTop + box.clientHeight * 2;
|
|
1681
|
+
pages.forEach((element, index) => {
|
|
1682
|
+
if (!(element instanceof HTMLElement))
|
|
1683
|
+
return;
|
|
1684
|
+
const pageTop = element.offsetTop - box.offsetTop;
|
|
1685
|
+
if (pageTop + element.offsetHeight >= top && pageTop <= bottom)
|
|
1686
|
+
this.session.requestPage(index);
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
/** The first page whose bottom is still below the panel's scroll offset. */
|
|
1690
|
+
trackCurrentPage() {
|
|
1691
|
+
const box = this.pagesBox()?.nativeElement;
|
|
1692
|
+
if (!box)
|
|
1693
|
+
return;
|
|
1694
|
+
const pages = box.querySelectorAll('nxe-report-page');
|
|
1695
|
+
for (let index = 0; index < pages.length; index++) {
|
|
1696
|
+
const element = pages[index];
|
|
1697
|
+
if (!(element instanceof HTMLElement))
|
|
1698
|
+
continue;
|
|
1699
|
+
const pageTop = element.offsetTop - box.offsetTop;
|
|
1700
|
+
if (pageTop + element.offsetHeight > box.scrollTop) {
|
|
1701
|
+
this.session.markCurrentPage(index);
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
/** Stimulsoft animates the panel to the page's top over 350 ms, in steps of about 30 ms. */
|
|
1707
|
+
scrollToPage(index) {
|
|
1708
|
+
const box = this.pagesBox()?.nativeElement;
|
|
1709
|
+
const element = box?.querySelectorAll('nxe-report-page')[index];
|
|
1710
|
+
if (!box || !(element instanceof HTMLElement))
|
|
1711
|
+
return;
|
|
1712
|
+
const from = box.scrollTop;
|
|
1713
|
+
const to = element.offsetTop - box.offsetTop;
|
|
1714
|
+
const started = performance.now();
|
|
1715
|
+
this.scrollingToPage = true;
|
|
1716
|
+
const step = () => {
|
|
1717
|
+
const done = Math.min(1, (performance.now() - started) / SCROLL_ANIMATION_MS);
|
|
1718
|
+
box.scrollTop = from + (to - from) * done;
|
|
1719
|
+
if (done < 1) {
|
|
1720
|
+
setTimeout(step, SCROLL_STEP_MS);
|
|
1721
|
+
return;
|
|
1722
|
+
}
|
|
1723
|
+
this.scrollingToPage = false;
|
|
1724
|
+
this.requestVisiblePages();
|
|
1725
|
+
};
|
|
1726
|
+
step();
|
|
1727
|
+
}
|
|
1084
1728
|
/**
|
|
1085
1729
|
* The getters read the session's signals, so a host that asks between renders gets what is on
|
|
1086
1730
|
* screen now. Built from a captured session rather than `this`, because `this` inside an object
|
|
@@ -1108,104 +1752,126 @@ class NxrReportViewerComponent {
|
|
|
1108
1752
|
requestUrl: this.requestUrl(),
|
|
1109
1753
|
properties: this.properties(),
|
|
1110
1754
|
postParametersFunction: this.postParametersFunction(),
|
|
1111
|
-
strings:
|
|
1755
|
+
strings: NXE_ENGLISH,
|
|
1112
1756
|
};
|
|
1113
1757
|
}
|
|
1114
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
1115
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type:
|
|
1758
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeReportViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1759
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.5", type: NxeReportViewerComponent, isStandalone: true, selector: "nxe-report-viewer", inputs: { requestUrl: { classPropertyName: "requestUrl", publicName: "requestUrl", isSignal: true, isRequired: false, transformFunction: null }, action: { classPropertyName: "action", publicName: "action", isSignal: true, isRequired: false, transformFunction: null }, properties: { classPropertyName: "properties", publicName: "properties", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null }, style: { classPropertyName: "style", publicName: "style", isSignal: true, isRequired: false, transformFunction: null }, postParametersFunction: { classPropertyName: "postParametersFunction", publicName: "postParametersFunction", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { loaded: "loaded", error: "error" }, host: { listeners: { "window:resize": "onWindowResize()" } }, providers: [NxeViewerClientService, NxeViewerSessionService], viewQueries: [{ propertyName: "pagesBox", first: true, predicate: ["pagesBox"], descendants: true, isSignal: true }, { propertyName: "rootBox", first: true, predicate: ["rootBox"], descendants: true, isSignal: true }], usesOnChanges: true, ngImport: i0, template: `
|
|
1116
1760
|
<div
|
|
1117
|
-
|
|
1761
|
+
#rootBox
|
|
1762
|
+
class="nxe-viewer-root"
|
|
1118
1763
|
[style]="style()"
|
|
1119
1764
|
[style.background-color]="rootBackgroundColor()"
|
|
1120
1765
|
[style.width]="rootWidth()"
|
|
1121
1766
|
[style.height]="rootHeight()"
|
|
1122
1767
|
>
|
|
1123
1768
|
@if (toolbarVisible()) {
|
|
1124
|
-
<
|
|
1769
|
+
<nxe-viewer-toolbar
|
|
1125
1770
|
[options]="session.options()"
|
|
1126
1771
|
[pageIndex]="session.pageIndex()"
|
|
1127
1772
|
[pageCount]="session.pageCount()"
|
|
1128
1773
|
[busy]="session.busy()"
|
|
1129
|
-
|
|
1774
|
+
[viewMode]="viewMode()"
|
|
1775
|
+
[zoom]="zoom()"
|
|
1776
|
+
(goToPage)="navigateTo($event)"
|
|
1777
|
+
(viewModeChange)="setViewMode($event)"
|
|
1778
|
+
(zoomChange)="setZoom($event)"
|
|
1779
|
+
(fitRequest)="fitTo($event)"
|
|
1780
|
+
(saveRequest)="session.exportTo($event)"
|
|
1130
1781
|
/>
|
|
1131
1782
|
}
|
|
1132
1783
|
|
|
1133
1784
|
<div
|
|
1134
|
-
|
|
1785
|
+
#pagesBox
|
|
1786
|
+
class="nxe-viewer-pages"
|
|
1787
|
+
[class.nxe-viewer-continuous]="viewMode() === 'Continuous'"
|
|
1135
1788
|
[style.text-align]="pageAlignment()"
|
|
1136
1789
|
[style.overflow]="pagesOverflow()"
|
|
1790
|
+
(scroll)="onScroll()"
|
|
1137
1791
|
>
|
|
1138
|
-
@
|
|
1139
|
-
<
|
|
1140
|
-
class="
|
|
1141
|
-
[class.
|
|
1792
|
+
@for (page of shownPages(); track page.index) {
|
|
1793
|
+
<nxe-report-page
|
|
1794
|
+
class="nxe-viewer-page"
|
|
1795
|
+
[class.nxe-viewer-page-shadow]="showPageShadow()"
|
|
1142
1796
|
[style.border]="pageBorder()"
|
|
1143
1797
|
[page]="page.size"
|
|
1144
1798
|
[html]="page.html"
|
|
1145
1799
|
[css]="pageCss()"
|
|
1800
|
+
[zoom]="zoom()"
|
|
1146
1801
|
/>
|
|
1147
1802
|
}
|
|
1148
1803
|
</div>
|
|
1149
1804
|
|
|
1150
1805
|
@if (session.failure(); as failure) {
|
|
1151
|
-
<div class="
|
|
1152
|
-
<span class="
|
|
1153
|
-
<button type="button" class="
|
|
1806
|
+
<div class="nxe-viewer-failure" role="alert">
|
|
1807
|
+
<span class="nxe-viewer-failure-text">{{ failure.message }}</span>
|
|
1808
|
+
<button type="button" class="nxe-viewer-failure-close" (click)="session.dismissFailure()">
|
|
1154
1809
|
×
|
|
1155
1810
|
</button>
|
|
1156
1811
|
</div>
|
|
1157
1812
|
}
|
|
1158
1813
|
</div>
|
|
1159
|
-
`, isInline: true, styles: [":host{display:inline-block;position:relative}.
|
|
1814
|
+
`, isInline: true, styles: [":host{display:inline-block;position:relative}.nxe-viewer-root{display:flex;flex-direction:column;box-sizing:border-box;position:relative}.nxe-viewer-pages{flex:1 1 auto;min-height:0;box-sizing:border-box}.nxe-viewer-page{display:inline-block;vertical-align:top;margin:10px}.nxe-viewer-continuous .nxe-viewer-page{display:block;margin:10px auto}.nxe-viewer-page-shadow{box-shadow:2px 2px 6px #0000004d}.nxe-viewer-failure{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;gap:8px;padding:8px 12px;background:#fdecea;color:#611a15;font-size:13px}.nxe-viewer-failure-text{flex:1 1 auto}.nxe-viewer-failure-close{border:0;background:transparent;color:inherit;font-size:18px;line-height:1;cursor:pointer}\n"], dependencies: [{ kind: "component", type: NxeReportPageComponent, selector: "nxe-report-page", inputs: ["page", "html", "css", "zoom"] }, { kind: "component", type: NxeViewerToolbarComponent, selector: "nxe-viewer-toolbar", inputs: ["options", "pageIndex", "pageCount", "busy", "strings", "viewMode", "zoom"], outputs: ["goToPage", "viewModeChange", "zoomChange", "fitRequest", "saveRequest"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1160
1815
|
}
|
|
1161
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type:
|
|
1816
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImport: i0, type: NxeReportViewerComponent, decorators: [{
|
|
1162
1817
|
type: Component,
|
|
1163
|
-
args: [{ selector: '
|
|
1818
|
+
args: [{ selector: 'nxe-report-viewer', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NxeReportPageComponent, NxeViewerToolbarComponent], providers: [NxeViewerClientService, NxeViewerSessionService], host: { '(window:resize)': 'onWindowResize()' }, template: `
|
|
1164
1819
|
<div
|
|
1165
|
-
|
|
1820
|
+
#rootBox
|
|
1821
|
+
class="nxe-viewer-root"
|
|
1166
1822
|
[style]="style()"
|
|
1167
1823
|
[style.background-color]="rootBackgroundColor()"
|
|
1168
1824
|
[style.width]="rootWidth()"
|
|
1169
1825
|
[style.height]="rootHeight()"
|
|
1170
1826
|
>
|
|
1171
1827
|
@if (toolbarVisible()) {
|
|
1172
|
-
<
|
|
1828
|
+
<nxe-viewer-toolbar
|
|
1173
1829
|
[options]="session.options()"
|
|
1174
1830
|
[pageIndex]="session.pageIndex()"
|
|
1175
1831
|
[pageCount]="session.pageCount()"
|
|
1176
1832
|
[busy]="session.busy()"
|
|
1177
|
-
|
|
1833
|
+
[viewMode]="viewMode()"
|
|
1834
|
+
[zoom]="zoom()"
|
|
1835
|
+
(goToPage)="navigateTo($event)"
|
|
1836
|
+
(viewModeChange)="setViewMode($event)"
|
|
1837
|
+
(zoomChange)="setZoom($event)"
|
|
1838
|
+
(fitRequest)="fitTo($event)"
|
|
1839
|
+
(saveRequest)="session.exportTo($event)"
|
|
1178
1840
|
/>
|
|
1179
1841
|
}
|
|
1180
1842
|
|
|
1181
1843
|
<div
|
|
1182
|
-
|
|
1844
|
+
#pagesBox
|
|
1845
|
+
class="nxe-viewer-pages"
|
|
1846
|
+
[class.nxe-viewer-continuous]="viewMode() === 'Continuous'"
|
|
1183
1847
|
[style.text-align]="pageAlignment()"
|
|
1184
1848
|
[style.overflow]="pagesOverflow()"
|
|
1849
|
+
(scroll)="onScroll()"
|
|
1185
1850
|
>
|
|
1186
|
-
@
|
|
1187
|
-
<
|
|
1188
|
-
class="
|
|
1189
|
-
[class.
|
|
1851
|
+
@for (page of shownPages(); track page.index) {
|
|
1852
|
+
<nxe-report-page
|
|
1853
|
+
class="nxe-viewer-page"
|
|
1854
|
+
[class.nxe-viewer-page-shadow]="showPageShadow()"
|
|
1190
1855
|
[style.border]="pageBorder()"
|
|
1191
1856
|
[page]="page.size"
|
|
1192
1857
|
[html]="page.html"
|
|
1193
1858
|
[css]="pageCss()"
|
|
1859
|
+
[zoom]="zoom()"
|
|
1194
1860
|
/>
|
|
1195
1861
|
}
|
|
1196
1862
|
</div>
|
|
1197
1863
|
|
|
1198
1864
|
@if (session.failure(); as failure) {
|
|
1199
|
-
<div class="
|
|
1200
|
-
<span class="
|
|
1201
|
-
<button type="button" class="
|
|
1865
|
+
<div class="nxe-viewer-failure" role="alert">
|
|
1866
|
+
<span class="nxe-viewer-failure-text">{{ failure.message }}</span>
|
|
1867
|
+
<button type="button" class="nxe-viewer-failure-close" (click)="session.dismissFailure()">
|
|
1202
1868
|
×
|
|
1203
1869
|
</button>
|
|
1204
1870
|
</div>
|
|
1205
1871
|
}
|
|
1206
1872
|
</div>
|
|
1207
|
-
`, styles: [":host{display:inline-block;position:relative}.
|
|
1208
|
-
}], ctorParameters: () => [], propDecorators: { requestUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "requestUrl", required: false }] }], action: [{ type: i0.Input, args: [{ isSignal: true, alias: "action", required: false }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], style: [{ type: i0.Input, args: [{ isSignal: true, alias: "style", required: false }] }], postParametersFunction: [{ type: i0.Input, args: [{ isSignal: true, alias: "postParametersFunction", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], error: [{ type: i0.Output, args: ["error"] }] } });
|
|
1873
|
+
`, styles: [":host{display:inline-block;position:relative}.nxe-viewer-root{display:flex;flex-direction:column;box-sizing:border-box;position:relative}.nxe-viewer-pages{flex:1 1 auto;min-height:0;box-sizing:border-box}.nxe-viewer-page{display:inline-block;vertical-align:top;margin:10px}.nxe-viewer-continuous .nxe-viewer-page{display:block;margin:10px auto}.nxe-viewer-page-shadow{box-shadow:2px 2px 6px #0000004d}.nxe-viewer-failure{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:center;gap:8px;padding:8px 12px;background:#fdecea;color:#611a15;font-size:13px}.nxe-viewer-failure-text{flex:1 1 auto}.nxe-viewer-failure-close{border:0;background:transparent;color:inherit;font-size:18px;line-height:1;cursor:pointer}\n"] }]
|
|
1874
|
+
}], ctorParameters: () => [], propDecorators: { requestUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "requestUrl", required: false }] }], action: [{ type: i0.Input, args: [{ isSignal: true, alias: "action", required: false }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], style: [{ type: i0.Input, args: [{ isSignal: true, alias: "style", required: false }] }], postParametersFunction: [{ type: i0.Input, args: [{ isSignal: true, alias: "postParametersFunction", required: false }] }], loaded: [{ type: i0.Output, args: ["loaded"] }], error: [{ type: i0.Output, args: ["error"] }], pagesBox: [{ type: i0.ViewChild, args: ['pagesBox', { isSignal: true }] }], rootBox: [{ type: i0.ViewChild, args: ['rootBox', { isSignal: true }] }] } });
|
|
1209
1875
|
|
|
1210
1876
|
/*
|
|
1211
1877
|
* Public API Surface of @nexterajs/report-viewer
|
|
@@ -1215,5 +1881,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.5", ngImpor
|
|
|
1215
1881
|
* Generated bundle index. Do not edit.
|
|
1216
1882
|
*/
|
|
1217
1883
|
|
|
1218
|
-
export {
|
|
1884
|
+
export { NXE_VIEWER_PROTOCOL, NxeReportViewerComponent };
|
|
1219
1885
|
//# sourceMappingURL=nexterajs-report-viewer.mjs.map
|