@dereekb/dbx-web 13.33.0 → 13.35.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.
- package/eslint/package.json +4 -4
- package/fesm2022/dereekb-dbx-web.mjs +1859 -134
- package/fesm2022/dereekb-dbx-web.mjs.map +1 -1
- package/lib/extension/pdf/_pdf.scss +171 -1
- package/package.json +7 -7
- package/types/dereekb-dbx-web.d.ts +1146 -26
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Injectable, input, inject, effect, Directive, NgModule, ChangeDetectionStrategy, Component, output, computed, HostListener, forwardRef, model, InjectionToken, viewChild, ElementRef, makeEnvironmentProviders, untracked, ApplicationRef, Injector, EnvironmentInjector, createComponent, DestroyRef, signal, TemplateRef, SecurityContext, ViewContainerRef, booleanAttribute, Renderer2 } from '@angular/core';
|
|
3
|
-
import { asPromise, DASH_CHARACTER_PREFIX_INSTANCE, hashStringToNumber, cssTokenVar, useIterableOrValue, isDefinedAndNotFalse, spaceSeparatedCssClasses, cssClassesSet, objectHasNoKeys, getValueFromGetter, firstValue, filterUndefinedValues, separateValues, splitFront, asDecisionFunction, SLASH_PATH_FILE_TYPE_SEPARATOR, filterMaybeArrayValues, mapIterable, toReadableError, isDefaultReadableError, MS_IN_SECOND, mergeObjects, build, ServerErrorResponse, UnauthorizedServerErrorResponse, makeTimer, MS_IN_MINUTE, toggleTimerRunning, unixDateTimeSecondsNumberForNow, ModelRelationUtility, encodeModelKeyTypePair, safeCompareEquality, addMilliseconds, isPast, asArray, slashPathDetails, mimeTypeForFileExtension, slashPathDirectoryTree, nameToInitials, isMaybeNot, isNotFalse, modifier, combineMaps, addModifiers, removeModifiers, applyBestFit, findNext, maybeModifierMapToFunction, makeValuesGroupMap, compareWithMappedValuesFunction, invertMaybeBoolean, splitCommaSeparatedStringToSet, ZIP_FILE_MIME_TYPE, cachedGetter, sortByNumberFunction, JPEG_MIME_TYPES, JPEG_MIME_TYPE, PNG_MIME_TYPE, PDF_MIME_TYPE, sequentialIncrementingNumberStringModelIdFactory, PDF_HEADER, PDF_EOF_MARKER, PDF_ENCRYPT_MARKER } from '@dereekb/util';
|
|
3
|
+
import { asPromise, DASH_CHARACTER_PREFIX_INSTANCE, hashStringToNumber, cssTokenVar, useIterableOrValue, isDefinedAndNotFalse, spaceSeparatedCssClasses, cssClassesSet, objectHasNoKeys, getValueFromGetter, firstValue, filterUndefinedValues, separateValues, splitFront, asDecisionFunction, SLASH_PATH_FILE_TYPE_SEPARATOR, filterMaybeArrayValues, mapIterable, toReadableError, isDefaultReadableError, MS_IN_SECOND, mergeObjects, build, ServerErrorResponse, UnauthorizedServerErrorResponse, makeTimer, MS_IN_MINUTE, toggleTimerRunning, unixDateTimeSecondsNumberForNow, ModelRelationUtility, encodeModelKeyTypePair, safeCompareEquality, addMilliseconds, isPast, asArray, slashPathDetails, mimeTypeForFileExtension, slashPathDirectoryTree, nameToInitials, isMaybeNot, isNotFalse, modifier, combineMaps, addModifiers, removeModifiers, applyBestFit, findNext, maybeModifierMapToFunction, makeValuesGroupMap, compareWithMappedValuesFunction, invertMaybeBoolean, splitCommaSeparatedStringToSet, ZIP_FILE_MIME_TYPE, cachedGetter, sortByNumberFunction, JPEG_MIME_TYPES, JPEG_MIME_TYPE, PNG_MIME_TYPE, PDF_MIME_TYPE, JSON_MIME_TYPE, sequentialIncrementingNumberStringModelIdFactory, PDF_HEADER, PDF_EOF_MARKER, PDF_ENCRYPT_MARKER } from '@dereekb/util';
|
|
4
4
|
import * as i1$2 from '@dereekb/dbx-core';
|
|
5
|
-
import { completeOnDestroy, clean, AbstractTransitionWatcherDirective, DbxInjectionComponent, dbxActionWorkProgress, AbstractDbxButtonDirective, hasNonTrivialChildNodes, provideDbxButton, DbxCoreButtonModule, createInjectorForInjectionComponentConfig, initInjectionComponent, AbstractDbxActionValueGetterDirective, cleanSubscription, AbstractDbxActionHandlerDirective, FilterSourceDirective, provideActionStoreSource, isClickableFilterPreset, AbstractDbxAnchorDirective, expandClickableAnchorLinkTrees, DbxCoreFilterModule, DbxButton, DbxActionWorkable, provideDbxActionWorkable, DbxActionContextStoreSourceInstance, cleanSubscriptionWithLockSet, DBX_INJECTION_COMPONENT_DATA, checkNgContentWrapperHasContent, cleanLoadingContext, DbxActionSourceDirective, DbxActionSuccessHandlerDirective, DbxActionDirective, transformEmptyStringInputToUndefined, isIdleActionState, canTriggerAction, DbxCoreActionModule, DbxActionButtonDirective, onDbxAppAuth, SimpleStorageAccessorFactory, mergeStaticProviders, asSegueRef, AbstractTransitionDirective, DbxRouterService, AbstractIfDirective, isSegueRefActive, anchorTypeForAnchor } from '@dereekb/dbx-core';
|
|
5
|
+
import { completeOnDestroy, clean, AbstractTransitionWatcherDirective, DbxInjectionComponent, dbxActionWorkProgress, AbstractDbxButtonDirective, hasNonTrivialChildNodes, provideDbxButton, DbxCoreButtonModule, createInjectorForInjectionComponentConfig, initInjectionComponent, AbstractDbxActionValueGetterDirective, cleanSubscription, AbstractDbxActionHandlerDirective, FilterSourceDirective, provideActionStoreSource, isClickableFilterPreset, AbstractDbxAnchorDirective, expandClickableAnchorLinkTrees, DbxCoreFilterModule, DbxButton, DbxActionWorkable, provideDbxActionWorkable, DbxActionContextStoreSourceInstance, cleanSubscriptionWithLockSet, DBX_INJECTION_COMPONENT_DATA, checkNgContentWrapperHasContent, cleanLoadingContext, DbxActionSourceDirective, DbxActionSuccessHandlerDirective, DbxActionDirective, transformEmptyStringInputToUndefined, isIdleActionState, canTriggerAction, DbxCoreActionModule, DbxActionButtonDirective, onDbxAppAuth, SimpleStorageAccessorFactory, mergeStaticProviders, asSegueRef, AbstractTransitionDirective, DbxRouterService, AbstractIfDirective, isSegueRefActive, anchorTypeForAnchor, DbxActionDisabledDirective, DbxActionHandlerDirective } from '@dereekb/dbx-core';
|
|
6
6
|
import { NgPopoverRef, NgOverlayContainerService } from 'ng-overlay-container';
|
|
7
7
|
import { of, map, BehaviorSubject, distinctUntilChanged, shareReplay, delay, Subject, first, startWith, filter, throttleTime, skip, defaultIfEmpty, combineLatest, switchMap, delayWhen, timer, asyncScheduler, from, firstValueFrom, mergeMap, race, tap, catchError, distinct, merge, exhaustMap, NEVER } from 'rxjs';
|
|
8
8
|
import { LockSet, distinctUntilItemsHaveDifferentValues, filterMaybe, skipAllInitialMaybe, maybeValueFromObservableOrValue, switchMapMaybeLoadingContextStream, LoadingStateType, loadingStateType, successResult, valueFromFinishedLoadingState, loadingStateContext, initialize, asObservable, isNot, mapForEach, switchMapMaybe, listLoadingStateContext, isLoadingStateFinishedLoading, startWithBeginLoading, loadingStateFromObs, errorResult, beginLoading, distinctUntilHasDifferentValues } from '@dereekb/rxjs';
|
|
@@ -52,7 +52,7 @@ import * as i1$8 from '@angular/material/chips';
|
|
|
52
52
|
import { MatChipsModule } from '@angular/material/chips';
|
|
53
53
|
import * as i2$1 from '@angular/material/tooltip';
|
|
54
54
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
55
|
-
import { PDFDocument } from '@cantoo/pdf-lib';
|
|
55
|
+
import { PDFName, PDFString, PDFHexString, PDFDocument, degrees } from '@cantoo/pdf-lib';
|
|
56
56
|
import { moveItemInArray, CdkDrag, CdkDragHandle, CdkDragPlaceholder, CdkDropList } from '@angular/cdk/drag-drop';
|
|
57
57
|
|
|
58
58
|
/**
|
|
@@ -18151,6 +18151,22 @@ const DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING = 'focus';
|
|
|
18151
18151
|
* Error message used when an encrypted entry is projected to the `error` status under {@link DbxPdfMergeEncryptedHandling} `'error'` mode.
|
|
18152
18152
|
*/
|
|
18153
18153
|
const DBX_PDF_MERGE_ENCRYPTED_ERROR_MESSAGE = 'Password-protected PDFs cannot be merged.';
|
|
18154
|
+
/**
|
|
18155
|
+
* Default value for {@link DbxPdfMergeEditorConfig.pageEditing}. Off, so the editor's default behavior is unchanged.
|
|
18156
|
+
*/
|
|
18157
|
+
const DEFAULT_DBX_PDF_MERGE_PAGE_EDITING = false;
|
|
18158
|
+
/**
|
|
18159
|
+
* Default value for {@link DbxPdfMergeEditorConfig.sidecar}. Off, so merged output bytes are unchanged.
|
|
18160
|
+
*/
|
|
18161
|
+
const DEFAULT_DBX_PDF_MERGE_SIDECAR = false;
|
|
18162
|
+
/**
|
|
18163
|
+
* Default value for {@link DbxPdfMergeEditorConfig.restoreImportOnClear}. On, so an editor the app populated programmatically is reset to that document by Clear rather than left empty.
|
|
18164
|
+
*/
|
|
18165
|
+
const DEFAULT_DBX_PDF_MERGE_RESTORE_IMPORT_ON_CLEAR = true;
|
|
18166
|
+
/**
|
|
18167
|
+
* Message shown on an encrypted entry's row while page editing is enabled. Encrypted documents cannot be opened by `pdf-lib`, so their pages cannot be listed or edited — the entry stays a single opaque row.
|
|
18168
|
+
*/
|
|
18169
|
+
const DBX_PDF_MERGE_ENCRYPTED_NOT_EDITABLE_MESSAGE = 'Encrypted — pages cannot be edited.';
|
|
18154
18170
|
/**
|
|
18155
18171
|
* MIME types accepted by the PDF merge editor by default: PDF documents and PNG/JPEG images.
|
|
18156
18172
|
*/
|
|
@@ -18159,6 +18175,45 @@ const DEFAULT_PDF_MERGE_ACCEPT = [PDF_MIME_TYPE, PNG_MIME_TYPE, JPEG_MIME_TYPE];
|
|
|
18159
18175
|
* MIME type emitted by the merged result blob.
|
|
18160
18176
|
*/
|
|
18161
18177
|
const PDF_MERGE_RESULT_MIME_TYPE = PDF_MIME_TYPE;
|
|
18178
|
+
/**
|
|
18179
|
+
* Group key used for pages whose source entry has no slot id.
|
|
18180
|
+
*/
|
|
18181
|
+
const DEFAULT_PDF_MERGE_PAGE_GROUP_KEY = '_';
|
|
18182
|
+
/**
|
|
18183
|
+
* Returns the group key a page reorders within — its entry's slot id, or {@link DEFAULT_PDF_MERGE_PAGE_GROUP_KEY} when the entry is unslotted.
|
|
18184
|
+
*
|
|
18185
|
+
* @param slotId - Slot id of the owning entry, if any.
|
|
18186
|
+
* @returns The group key.
|
|
18187
|
+
* @__NO_SIDE_EFFECTS__
|
|
18188
|
+
*/
|
|
18189
|
+
function pdfMergePageGroupKeyForSlotId(slotId) {
|
|
18190
|
+
return slotId ?? DEFAULT_PDF_MERGE_PAGE_GROUP_KEY;
|
|
18191
|
+
}
|
|
18192
|
+
/**
|
|
18193
|
+
* Separator between the entry id and the page index inside a page id.
|
|
18194
|
+
*/
|
|
18195
|
+
const PDF_MERGE_PAGE_ID_SEPARATOR = ':';
|
|
18196
|
+
/**
|
|
18197
|
+
* Builds the stable identifier for a page within the editor.
|
|
18198
|
+
*
|
|
18199
|
+
* @param entryId - Id of the owning {@link PdfMergeEntry}.
|
|
18200
|
+
* @param sourceIndex - Zero-based page index within that entry's source document.
|
|
18201
|
+
* @returns The page id.
|
|
18202
|
+
* @__NO_SIDE_EFFECTS__
|
|
18203
|
+
*/
|
|
18204
|
+
function makePdfMergePageId(entryId, sourceIndex) {
|
|
18205
|
+
return `${entryId}${PDF_MERGE_PAGE_ID_SEPARATOR}${sourceIndex}`;
|
|
18206
|
+
}
|
|
18207
|
+
/**
|
|
18208
|
+
* Returns the id of the entry a page id belongs to. Used to prune page state when an entry leaves the list.
|
|
18209
|
+
*
|
|
18210
|
+
* @param pageId - Page id produced by {@link makePdfMergePageId}.
|
|
18211
|
+
* @returns The owning entry's id.
|
|
18212
|
+
* @__NO_SIDE_EFFECTS__
|
|
18213
|
+
*/
|
|
18214
|
+
function entryIdForPdfMergePageId(pageId) {
|
|
18215
|
+
return pageId.slice(0, pageId.indexOf(PDF_MERGE_PAGE_ID_SEPARATOR));
|
|
18216
|
+
}
|
|
18162
18217
|
/**
|
|
18163
18218
|
* Injection token for a workspace-wide default {@link DbxPdfMergeEditorConfig}. Use {@link provideDbxPdfMergeEditorConfig} to register a value.
|
|
18164
18219
|
*/
|
|
@@ -18186,6 +18241,233 @@ function provideDbxPdfMergeEditorPreserveEntriesOnSlotDestroy(preserve = true) {
|
|
|
18186
18241
|
return { provide: DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY, useValue: preserve };
|
|
18187
18242
|
}
|
|
18188
18243
|
|
|
18244
|
+
/**
|
|
18245
|
+
* Name of the JSON manifest attached to a merged PDF that records which pages came from which slot.
|
|
18246
|
+
*/
|
|
18247
|
+
const PDF_MERGE_SIDECAR_FILE_NAME = 'dbx-pdf-merge.json';
|
|
18248
|
+
/**
|
|
18249
|
+
* Key set on each output page's dictionary holding that page's {@link PdfMergeSidecarPage.tag}.
|
|
18250
|
+
*
|
|
18251
|
+
* Custom page-dictionary keys travel with the page: they survive `copyPages` (including subsetting and reordering), a save/load round trip, and further edits. That is what makes tags — rather than the manifest's recorded indices — the authoritative record of which document a page belongs to.
|
|
18252
|
+
*/
|
|
18253
|
+
const PDF_MERGE_PAGE_TAG_KEY = 'DbxPageTag';
|
|
18254
|
+
/**
|
|
18255
|
+
* Schema version of the emitted {@link PdfMergeSidecar}.
|
|
18256
|
+
*/
|
|
18257
|
+
const PDF_MERGE_SIDECAR_VERSION = 1;
|
|
18258
|
+
/**
|
|
18259
|
+
* Description recorded on the attached manifest.
|
|
18260
|
+
*/
|
|
18261
|
+
const PDF_MERGE_SIDECAR_DESCRIPTION = 'dbx-pdf-merge page manifest';
|
|
18262
|
+
/**
|
|
18263
|
+
* Builds the tag written onto an output page.
|
|
18264
|
+
*
|
|
18265
|
+
* @param slotId - Slot the page's source entry belongs to, or `null` when unslotted.
|
|
18266
|
+
* @param ordinal - Zero-based position of the page within its own slot's pages.
|
|
18267
|
+
* @returns A tag unique within the output document.
|
|
18268
|
+
* @__NO_SIDE_EFFECTS__
|
|
18269
|
+
*/
|
|
18270
|
+
function makePdfMergeSidecarPageTag(slotId, ordinal) {
|
|
18271
|
+
return `${slotId ?? '_'}-${ordinal}`;
|
|
18272
|
+
}
|
|
18273
|
+
/**
|
|
18274
|
+
* Writes a page's tag onto its page dictionary so the association survives later reordering.
|
|
18275
|
+
*
|
|
18276
|
+
* @param page - Page in the output document.
|
|
18277
|
+
* @param tag - Tag to record.
|
|
18278
|
+
*/
|
|
18279
|
+
function writePdfMergePageTag(page, tag) {
|
|
18280
|
+
page.node.set(PDFName.of(PDF_MERGE_PAGE_TAG_KEY), PDFString.of(tag));
|
|
18281
|
+
}
|
|
18282
|
+
/**
|
|
18283
|
+
* Reads the tag previously written onto a page, if any.
|
|
18284
|
+
*
|
|
18285
|
+
* Uses `get` plus an instance check rather than a typed `lookup`, because `PDFDict.lookup(key, PDFString)` throws when the key is absent — which is the normal case for any PDF that did not come out of this editor.
|
|
18286
|
+
*
|
|
18287
|
+
* @param page - Page to inspect.
|
|
18288
|
+
* @returns The tag, or `null` when the page carries none.
|
|
18289
|
+
*/
|
|
18290
|
+
function readPdfMergePageTag(page) {
|
|
18291
|
+
const value = page.node.get(PDFName.of(PDF_MERGE_PAGE_TAG_KEY));
|
|
18292
|
+
let tag;
|
|
18293
|
+
if (value instanceof PDFString || value instanceof PDFHexString) {
|
|
18294
|
+
tag = value.decodeText();
|
|
18295
|
+
}
|
|
18296
|
+
else {
|
|
18297
|
+
tag = null;
|
|
18298
|
+
}
|
|
18299
|
+
return tag;
|
|
18300
|
+
}
|
|
18301
|
+
/**
|
|
18302
|
+
* Attaches a manifest to the output document as an embedded JSON file.
|
|
18303
|
+
*
|
|
18304
|
+
* @param document - Output document being saved.
|
|
18305
|
+
* @param sidecar - Manifest to embed.
|
|
18306
|
+
*/
|
|
18307
|
+
async function attachPdfMergeSidecar(document, sidecar) {
|
|
18308
|
+
// Re-wrap with this module's Uint8Array: pdf-lib validates its input with `instanceof`, and a
|
|
18309
|
+
// TextEncoder supplied by another realm (Node's, under jsdom) produces an array that fails that
|
|
18310
|
+
// check even though it is a genuine Uint8Array.
|
|
18311
|
+
const bytes = new Uint8Array(new TextEncoder().encode(JSON.stringify(sidecar)));
|
|
18312
|
+
await document.attach(bytes, PDF_MERGE_SIDECAR_FILE_NAME, {
|
|
18313
|
+
mimeType: JSON_MIME_TYPE,
|
|
18314
|
+
description: PDF_MERGE_SIDECAR_DESCRIPTION
|
|
18315
|
+
});
|
|
18316
|
+
}
|
|
18317
|
+
/**
|
|
18318
|
+
* Builds a {@link PdfMergeSidecar} from the pages written to an output document.
|
|
18319
|
+
*
|
|
18320
|
+
* @param pages - Page records in output order.
|
|
18321
|
+
* @returns The manifest, with one document per distinct slot in first-appearance order.
|
|
18322
|
+
* @__NO_SIDE_EFFECTS__
|
|
18323
|
+
*/
|
|
18324
|
+
function makePdfMergeSidecar(pages) {
|
|
18325
|
+
const tagsBySlot = new Map();
|
|
18326
|
+
pages.forEach((page) => {
|
|
18327
|
+
const existing = tagsBySlot.get(page.slotId);
|
|
18328
|
+
if (existing == null) {
|
|
18329
|
+
tagsBySlot.set(page.slotId, [page.tag]);
|
|
18330
|
+
}
|
|
18331
|
+
else {
|
|
18332
|
+
existing.push(page.tag);
|
|
18333
|
+
}
|
|
18334
|
+
});
|
|
18335
|
+
const documents = Array.from(tagsBySlot.entries()).map(([slotId, pageTags]) => ({ slotId, pageTags }));
|
|
18336
|
+
return {
|
|
18337
|
+
version: PDF_MERGE_SIDECAR_VERSION,
|
|
18338
|
+
createdAt: new Date().toISOString(),
|
|
18339
|
+
documents,
|
|
18340
|
+
pages
|
|
18341
|
+
};
|
|
18342
|
+
}
|
|
18343
|
+
async function bytesForSidecarReadInput(input) {
|
|
18344
|
+
return input instanceof Blob ? input.arrayBuffer() : input;
|
|
18345
|
+
}
|
|
18346
|
+
function parseSidecarBytes(data) {
|
|
18347
|
+
let sidecar;
|
|
18348
|
+
try {
|
|
18349
|
+
const parsed = JSON.parse(new TextDecoder().decode(data));
|
|
18350
|
+
sidecar = Array.isArray(parsed?.documents) && Array.isArray(parsed?.pages) ? parsed : null;
|
|
18351
|
+
}
|
|
18352
|
+
catch {
|
|
18353
|
+
sidecar = null;
|
|
18354
|
+
}
|
|
18355
|
+
return sidecar;
|
|
18356
|
+
}
|
|
18357
|
+
/**
|
|
18358
|
+
* Reads the manifest embedded in a merged PDF and resolves each recorded page to its current position in that file.
|
|
18359
|
+
*
|
|
18360
|
+
* Resolution prefers each page's own tag, which follows the page through reordering. The manifest's {@link PdfMergeSidecarPage.outputIndex} is used only as a fallback for pages whose tag has been stripped, and any index that no longer exists is reported through {@link PdfMergeSidecarReadResult.missingTags} instead.
|
|
18361
|
+
*
|
|
18362
|
+
* @param input - The merged PDF's bytes.
|
|
18363
|
+
* @returns The manifest and its resolved documents, or `null` when the file carries no manifest, is unreadable, or is encrypted.
|
|
18364
|
+
*/
|
|
18365
|
+
async function readPdfMergeSidecar(input) {
|
|
18366
|
+
let result;
|
|
18367
|
+
try {
|
|
18368
|
+
const bytes = await bytesForSidecarReadInput(input);
|
|
18369
|
+
const document = await PDFDocument.load(bytes);
|
|
18370
|
+
result = resolvePdfMergeSidecar(document);
|
|
18371
|
+
}
|
|
18372
|
+
catch {
|
|
18373
|
+
result = null;
|
|
18374
|
+
}
|
|
18375
|
+
return result;
|
|
18376
|
+
}
|
|
18377
|
+
/**
|
|
18378
|
+
* Resolves the manifest of an already-loaded document. Split out from {@link readPdfMergeSidecar} so {@link splitPdfMergeSidecarDocuments} can reuse the resolution without parsing the same bytes twice.
|
|
18379
|
+
*
|
|
18380
|
+
* @param document - Loaded merged document.
|
|
18381
|
+
* @returns The resolved manifest, or `null` when the document carries none.
|
|
18382
|
+
*/
|
|
18383
|
+
function resolvePdfMergeSidecar(document) {
|
|
18384
|
+
let result;
|
|
18385
|
+
{
|
|
18386
|
+
const attachment = document.getAttachments().find((x) => x.name === PDF_MERGE_SIDECAR_FILE_NAME);
|
|
18387
|
+
const sidecar = attachment == null ? null : parseSidecarBytes(attachment.data);
|
|
18388
|
+
if (sidecar == null) {
|
|
18389
|
+
result = null;
|
|
18390
|
+
}
|
|
18391
|
+
else {
|
|
18392
|
+
const pageCount = document.getPageCount();
|
|
18393
|
+
const indexByTag = new Map();
|
|
18394
|
+
let untaggedPageCount = 0;
|
|
18395
|
+
document.getPages().forEach((page, index) => {
|
|
18396
|
+
const tag = readPdfMergePageTag(page);
|
|
18397
|
+
if (tag == null) {
|
|
18398
|
+
untaggedPageCount += 1;
|
|
18399
|
+
}
|
|
18400
|
+
else {
|
|
18401
|
+
indexByTag.set(tag, index);
|
|
18402
|
+
}
|
|
18403
|
+
});
|
|
18404
|
+
const recordByTag = new Map(sidecar.pages.map((page) => [page.tag, page]));
|
|
18405
|
+
const missingTags = [];
|
|
18406
|
+
const documents = sidecar.documents.map((sidecarDocument) => {
|
|
18407
|
+
const pages = [];
|
|
18408
|
+
sidecarDocument.pageTags.forEach((tag) => {
|
|
18409
|
+
const record = recordByTag.get(tag);
|
|
18410
|
+
const taggedIndex = indexByTag.get(tag);
|
|
18411
|
+
if (record == null) {
|
|
18412
|
+
missingTags.push(tag);
|
|
18413
|
+
}
|
|
18414
|
+
else if (taggedIndex != null) {
|
|
18415
|
+
pages.push({ ...record, pageIndex: taggedIndex, resolvedByTag: true });
|
|
18416
|
+
}
|
|
18417
|
+
else if (record.outputIndex >= 0 && record.outputIndex < pageCount) {
|
|
18418
|
+
pages.push({ ...record, pageIndex: record.outputIndex, resolvedByTag: false });
|
|
18419
|
+
}
|
|
18420
|
+
else {
|
|
18421
|
+
missingTags.push(tag);
|
|
18422
|
+
}
|
|
18423
|
+
});
|
|
18424
|
+
return { slotId: sidecarDocument.slotId, pages };
|
|
18425
|
+
});
|
|
18426
|
+
result = { sidecar, documents, missingTags, untaggedPageCount };
|
|
18427
|
+
}
|
|
18428
|
+
}
|
|
18429
|
+
return result;
|
|
18430
|
+
}
|
|
18431
|
+
/**
|
|
18432
|
+
* Splits a previously-merged PDF back into one file per document, using the embedded manifest to decide which pages belong to which slot.
|
|
18433
|
+
*
|
|
18434
|
+
* This is the inverse of a merge with `sidecar: true`, and is what lets a completed document be re-imported into the editor with its slots repopulated instead of arriving as one opaque file. Page order and any rotation baked in at merge time are preserved, and each document's pages are resolved by tag so a document that was reordered after export still splits correctly.
|
|
18435
|
+
*
|
|
18436
|
+
* @param input - Bytes of a merged PDF.
|
|
18437
|
+
* @returns One file per document plus the manifest, or `null` when the input carries no manifest, is unreadable, or is encrypted.
|
|
18438
|
+
*/
|
|
18439
|
+
async function splitPdfMergeSidecarDocuments(input) {
|
|
18440
|
+
let result;
|
|
18441
|
+
try {
|
|
18442
|
+
const bytes = await bytesForSidecarReadInput(input);
|
|
18443
|
+
const source = await PDFDocument.load(bytes);
|
|
18444
|
+
const read = resolvePdfMergeSidecar(source);
|
|
18445
|
+
if (read == null) {
|
|
18446
|
+
result = null;
|
|
18447
|
+
}
|
|
18448
|
+
else {
|
|
18449
|
+
const documents = [];
|
|
18450
|
+
for (const document of read.documents) {
|
|
18451
|
+
if (document.pages.length > 0) {
|
|
18452
|
+
const target = await PDFDocument.create();
|
|
18453
|
+
const copied = await target.copyPages(source, document.pages.map((page) => page.pageIndex));
|
|
18454
|
+
copied.forEach((page) => target.addPage(page));
|
|
18455
|
+
const outputBytes = await target.save();
|
|
18456
|
+
const name = document.pages[0].sourceName || `${document.slotId ?? 'document'}.pdf`;
|
|
18457
|
+
const file = new File([outputBytes], name, { type: PDF_MIME_TYPE });
|
|
18458
|
+
documents.push({ slotId: document.slotId, file, pageCount: document.pages.length });
|
|
18459
|
+
}
|
|
18460
|
+
}
|
|
18461
|
+
result = { sidecar: read.sidecar, documents, missingTags: read.missingTags, untaggedPageCount: read.untaggedPageCount };
|
|
18462
|
+
}
|
|
18463
|
+
}
|
|
18464
|
+
catch {
|
|
18465
|
+
result = null;
|
|
18466
|
+
}
|
|
18467
|
+
return result;
|
|
18468
|
+
}
|
|
18469
|
+
|
|
18470
|
+
const FULL_ROTATION_DEGREES = 360;
|
|
18189
18471
|
const TEXT_DECODER = new TextDecoder('latin1');
|
|
18190
18472
|
const FORMAT_KILOBYTE = 1024;
|
|
18191
18473
|
const FORMAT_MEGABYTE = FORMAT_KILOBYTE * 1024;
|
|
@@ -18209,6 +18491,33 @@ function formatPdfMergeEntrySize(size) {
|
|
|
18209
18491
|
}
|
|
18210
18492
|
return result;
|
|
18211
18493
|
}
|
|
18494
|
+
const DEFAULT_BLOB_FILE_NAMES = {
|
|
18495
|
+
[PDF_MIME_TYPE]: 'document.pdf',
|
|
18496
|
+
[PNG_MIME_TYPE]: 'image.png',
|
|
18497
|
+
[JPEG_MIME_TYPE]: 'image.jpg'
|
|
18498
|
+
};
|
|
18499
|
+
const DEFAULT_BLOB_FILE_NAME = DEFAULT_BLOB_FILE_NAMES[PDF_MIME_TYPE];
|
|
18500
|
+
/**
|
|
18501
|
+
* Normalizes a {@link Blob} into a {@link File} so it can become a {@link PdfMergeEntry}, which needs a name to display.
|
|
18502
|
+
*
|
|
18503
|
+
* A {@link File} passes through untouched unless an explicit `fileName` overrides it. A bare {@link Blob} is named from `fileName`, else from its MIME type (`document.pdf` / `image.png` / `image.jpg`), else `document.pdf` — the name only has to be plausible and correctly suffixed, since {@link classifyPdfMergeFile} falls back to the extension when a blob carries no type.
|
|
18504
|
+
*
|
|
18505
|
+
* @param input - Blob or File to normalize.
|
|
18506
|
+
* @param fileName - Optional name, overriding a File's own name and naming a bare Blob.
|
|
18507
|
+
* @returns A File carrying the resolved name.
|
|
18508
|
+
*/
|
|
18509
|
+
function asPdfMergeFile(input, fileName) {
|
|
18510
|
+
const mimeType = (input.type ?? '').toLowerCase();
|
|
18511
|
+
let file;
|
|
18512
|
+
if (input instanceof File && fileName == null) {
|
|
18513
|
+
file = input;
|
|
18514
|
+
}
|
|
18515
|
+
else {
|
|
18516
|
+
const name = fileName ?? DEFAULT_BLOB_FILE_NAMES[mimeType] ?? DEFAULT_BLOB_FILE_NAME;
|
|
18517
|
+
file = new File([input], name, { type: input.type || mimeTypeForFileExtension(slashPathDetails(name.toLowerCase()).typedFileExtension) || PDF_MIME_TYPE });
|
|
18518
|
+
}
|
|
18519
|
+
return file;
|
|
18520
|
+
}
|
|
18212
18521
|
/**
|
|
18213
18522
|
* Returns the {@link PdfMergeEntryKind} for a file based on its MIME type, with a small fallback to file-extension matching when the browser provided no MIME type.
|
|
18214
18523
|
*
|
|
@@ -18363,30 +18672,305 @@ async function validatePdfMergeEntry(entry) {
|
|
|
18363
18672
|
}
|
|
18364
18673
|
return result;
|
|
18365
18674
|
}
|
|
18366
|
-
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
|
|
18370
|
-
|
|
18675
|
+
/**
|
|
18676
|
+
* Normalizes an arbitrary degree value into the `[0, 360)` range.
|
|
18677
|
+
*
|
|
18678
|
+
* @param angle - Angle in degrees, possibly negative or over a full turn.
|
|
18679
|
+
* @returns The equivalent angle in `[0, 360)`.
|
|
18680
|
+
* @__NO_SIDE_EFFECTS__
|
|
18681
|
+
*/
|
|
18682
|
+
function normalizeRotationDegrees(angle) {
|
|
18683
|
+
return ((angle % FULL_ROTATION_DEGREES) + FULL_ROTATION_DEGREES) % FULL_ROTATION_DEGREES;
|
|
18684
|
+
}
|
|
18685
|
+
/**
|
|
18686
|
+
* Reads per-page metadata from an entry's source document so the editor can list its pages individually.
|
|
18687
|
+
*
|
|
18688
|
+
* Only called while page editing is enabled — the normal upload path never parses the document, it only scans for the PDF header and markers (see {@link validatePdfMergeEntry}).
|
|
18689
|
+
*
|
|
18690
|
+
* @param entry - Entry to inspect.
|
|
18691
|
+
* @returns One {@link PdfMergePageMeta} per source page, or `null` when the entry is encrypted or cannot be parsed. Image entries report a single synthetic page.
|
|
18692
|
+
*/
|
|
18693
|
+
async function readPdfMergeEntryPageMetas(entry) {
|
|
18694
|
+
let metas;
|
|
18695
|
+
if (entry.encrypted) {
|
|
18696
|
+
// pdf-lib cannot open an encrypted document at all, so its pages can never be listed or edited.
|
|
18697
|
+
metas = null;
|
|
18698
|
+
}
|
|
18699
|
+
else if (entry.kind === 'image') {
|
|
18700
|
+
const dimensions = entry.original.dimensions;
|
|
18701
|
+
metas = [{ sourceIndex: 0, width: dimensions?.width ?? 0, height: dimensions?.height ?? 0, sourceRotation: 0 }];
|
|
18702
|
+
}
|
|
18703
|
+
else {
|
|
18704
|
+
try {
|
|
18705
|
+
const bytes = await entry.file.arrayBuffer();
|
|
18706
|
+
const source = await PDFDocument.load(bytes);
|
|
18707
|
+
metas = source.getPages().map((page, sourceIndex) => {
|
|
18708
|
+
const { width, height } = page.getSize();
|
|
18709
|
+
return { sourceIndex, width, height, sourceRotation: normalizeRotationDegrees(page.getRotation().angle) };
|
|
18710
|
+
});
|
|
18711
|
+
}
|
|
18712
|
+
catch {
|
|
18713
|
+
metas = null;
|
|
18714
|
+
}
|
|
18715
|
+
}
|
|
18716
|
+
return metas;
|
|
18717
|
+
}
|
|
18718
|
+
/**
|
|
18719
|
+
* Reconstructs editor entries from a PDF previously exported with an embedded manifest.
|
|
18720
|
+
*
|
|
18721
|
+
* Each document recorded in the manifest becomes one entry carrying its original slot id, so dropping the result into the store repopulates the same slots the file was built from. This is the re-import half of the sidecar: without it a completed document can only come back as a single opaque file.
|
|
18722
|
+
*
|
|
18723
|
+
* @param input - Bytes of a merged PDF, typically the file the user just chose.
|
|
18724
|
+
* @param config - Optional entry-building config (id factory override, and the sidecar-less fallback).
|
|
18725
|
+
* @returns The reconstructed entries and manifest, or a reason when the file cannot be imported.
|
|
18726
|
+
*/
|
|
18727
|
+
async function buildPdfMergeEntriesFromSidecar(input, config) {
|
|
18728
|
+
const split = await splitPdfMergeSidecarDocuments(input);
|
|
18729
|
+
let result;
|
|
18730
|
+
if (split == null) {
|
|
18731
|
+
// A readable PDF without a manifest and an unreadable one are distinguished so the UI can
|
|
18732
|
+
// explain the difference — "not exported from here" reads very differently from "corrupt".
|
|
18733
|
+
const readable = await isReadablePdf(input);
|
|
18734
|
+
if (!readable) {
|
|
18735
|
+
result = { error: 'unreadable' };
|
|
18736
|
+
}
|
|
18737
|
+
else if (config?.allowWithoutSidecar) {
|
|
18738
|
+
// No manifest to attribute pages with, so the whole file becomes one unslotted entry. An
|
|
18739
|
+
// `unreadable` file never reaches here — the fallback widens which readable PDFs are
|
|
18740
|
+
// accepted, it does not accept broken ones.
|
|
18741
|
+
const entry = buildPdfMergeEntrySync(asPdfMergeFile(input, config?.fileName), { ...config, slotId: null });
|
|
18742
|
+
result = entry == null ? { error: 'unreadable' } : { entries: [entry], sidecar: null, fromSidecar: false, slotIds: [null], missingTags: [], untaggedPageCount: 0 };
|
|
18743
|
+
}
|
|
18744
|
+
else {
|
|
18745
|
+
result = { error: 'no_sidecar' };
|
|
18746
|
+
}
|
|
18747
|
+
}
|
|
18748
|
+
else if (split.documents.length === 0) {
|
|
18749
|
+
result = { error: 'no_documents' };
|
|
18750
|
+
}
|
|
18751
|
+
else {
|
|
18752
|
+
const entries = split.documents.map((document) => buildPdfMergeEntrySync(document.file, { ...config, slotId: document.slotId })).filter((entry) => entry != null);
|
|
18753
|
+
result = {
|
|
18754
|
+
entries,
|
|
18755
|
+
sidecar: split.sidecar,
|
|
18756
|
+
fromSidecar: true,
|
|
18757
|
+
slotIds: split.documents.map((document) => document.slotId),
|
|
18758
|
+
missingTags: split.missingTags,
|
|
18759
|
+
untaggedPageCount: split.untaggedPageCount
|
|
18760
|
+
};
|
|
18761
|
+
}
|
|
18762
|
+
return result;
|
|
18763
|
+
}
|
|
18764
|
+
async function isReadablePdf(input) {
|
|
18765
|
+
let readable;
|
|
18766
|
+
try {
|
|
18767
|
+
await PDFDocument.load(await input.arrayBuffer());
|
|
18768
|
+
readable = true;
|
|
18769
|
+
}
|
|
18770
|
+
catch {
|
|
18771
|
+
readable = false;
|
|
18772
|
+
}
|
|
18773
|
+
return readable;
|
|
18774
|
+
}
|
|
18775
|
+
function applyPdfMergePageGroupOrder(natural, orderedIds) {
|
|
18776
|
+
let ordered;
|
|
18777
|
+
if (orderedIds == null || orderedIds.length === 0) {
|
|
18778
|
+
ordered = [...natural];
|
|
18779
|
+
}
|
|
18780
|
+
else {
|
|
18781
|
+
const byId = new Map(natural.map((page) => [page.id, page]));
|
|
18782
|
+
const taken = new Set();
|
|
18783
|
+
const result = [];
|
|
18784
|
+
// Stored ids that still resolve keep their stored order; anything else is ignored.
|
|
18785
|
+
orderedIds.forEach((id) => {
|
|
18786
|
+
const page = byId.get(id);
|
|
18787
|
+
if (page != null && !taken.has(id)) {
|
|
18788
|
+
result.push(page);
|
|
18789
|
+
taken.add(id);
|
|
18790
|
+
}
|
|
18791
|
+
});
|
|
18792
|
+
// Pages the stored order does not mention are new since it was written — append them in natural order.
|
|
18793
|
+
natural.forEach((page) => {
|
|
18794
|
+
if (!taken.has(page.id)) {
|
|
18795
|
+
result.push(page);
|
|
18796
|
+
}
|
|
18797
|
+
});
|
|
18798
|
+
ordered = result;
|
|
18799
|
+
}
|
|
18800
|
+
return ordered;
|
|
18801
|
+
}
|
|
18802
|
+
/**
|
|
18803
|
+
* Builds the editor's page plan from the current entries plus the user's sparse edits.
|
|
18804
|
+
*
|
|
18805
|
+
* The result is self-healing: stored ordering ids that no longer resolve are dropped, pages the stored ordering does not mention are appended in natural order, and overrides for departed pages are ignored. That means entries can be added and removed without any page-level bookkeeping.
|
|
18806
|
+
*
|
|
18807
|
+
* Entries whose metadata is `null` contribute no pages — they are encrypted (which `pdf-lib` cannot open) or unparseable. Callers should surface those separately rather than letting them disappear silently.
|
|
18808
|
+
*
|
|
18809
|
+
* @param input - Entries, hydrated metadata, and the stored edits.
|
|
18810
|
+
* @returns The ordered page plan across every group.
|
|
18811
|
+
* @__NO_SIDE_EFFECTS__
|
|
18812
|
+
*/
|
|
18813
|
+
function buildPdfMergePagePlan(input) {
|
|
18814
|
+
const { entries, pageMetas, pageOverrides, pageOrder } = input;
|
|
18815
|
+
const eligible = entries.filter((entry) => entry.status === 'ready' && !entry.ignored && pageMetas[entry.id] != null);
|
|
18816
|
+
const groups = makeValuesGroupMap(eligible, (entry) => pdfMergePageGroupKeyForSlotId(entry.slotId));
|
|
18817
|
+
const plan = [];
|
|
18818
|
+
groups.forEach((groupEntries, groupKey) => {
|
|
18819
|
+
const natural = [];
|
|
18820
|
+
groupEntries.forEach((entry) => {
|
|
18821
|
+
const metas = pageMetas[entry.id];
|
|
18822
|
+
metas.forEach((meta) => {
|
|
18823
|
+
const id = makePdfMergePageId(entry.id, meta.sourceIndex);
|
|
18824
|
+
const override = pageOverrides[id];
|
|
18825
|
+
natural.push({
|
|
18826
|
+
id,
|
|
18827
|
+
entryId: entry.id,
|
|
18828
|
+
slotId: entry.slotId ?? null,
|
|
18829
|
+
groupKey: groupKey,
|
|
18830
|
+
sourceName: entry.name,
|
|
18831
|
+
kind: entry.kind,
|
|
18832
|
+
sourceIndex: meta.sourceIndex,
|
|
18833
|
+
sourcePageCount: metas.length,
|
|
18834
|
+
meta,
|
|
18835
|
+
rotation: override?.rotation ?? 0,
|
|
18836
|
+
removed: override?.removed ?? false
|
|
18837
|
+
});
|
|
18838
|
+
});
|
|
18839
|
+
});
|
|
18840
|
+
applyPdfMergePageGroupOrder(natural, pageOrder[groupKey]).forEach((page) => plan.push(page));
|
|
18841
|
+
});
|
|
18842
|
+
return plan;
|
|
18843
|
+
}
|
|
18844
|
+
/**
|
|
18845
|
+
* Returns a loader that parses each entry's source document at most once per merge. A page plan routinely pulls several non-adjacent pages from the same file, so without this the same bytes would be parsed repeatedly.
|
|
18846
|
+
*
|
|
18847
|
+
* @returns Loader that resolves an entry to its parsed source document.
|
|
18848
|
+
*/
|
|
18849
|
+
function makePdfSourceDocumentLoader() {
|
|
18850
|
+
const cache = new Map();
|
|
18851
|
+
return (entry) => {
|
|
18852
|
+
let loaded = cache.get(entry.id);
|
|
18853
|
+
if (loaded == null) {
|
|
18854
|
+
loaded = entry.file.arrayBuffer().then((bytes) => PDFDocument.load(bytes));
|
|
18855
|
+
cache.set(entry.id, loaded);
|
|
18856
|
+
}
|
|
18857
|
+
return loaded;
|
|
18858
|
+
};
|
|
18371
18859
|
}
|
|
18372
|
-
async function appendImagePage(target, entry) {
|
|
18860
|
+
async function appendImagePage(target, entry, rotation) {
|
|
18373
18861
|
const bytes = await entry.file.arrayBuffer();
|
|
18374
18862
|
const isPng = entry.mimeType === PNG_MIME_TYPE || entry.name.toLowerCase().endsWith('.png');
|
|
18375
18863
|
const image = isPng ? await target.embedPng(bytes) : await target.embedJpg(bytes);
|
|
18376
18864
|
const page = target.addPage([image.width, image.height]);
|
|
18377
18865
|
page.drawImage(image, { x: 0, y: 0, width: image.width, height: image.height });
|
|
18866
|
+
if (rotation !== 0) {
|
|
18867
|
+
page.setRotation(degrees(rotation));
|
|
18868
|
+
}
|
|
18869
|
+
return { entry, sourceIndex: 0, rotation };
|
|
18870
|
+
}
|
|
18871
|
+
/**
|
|
18872
|
+
* Appends every page of every entry, in entry order — the behavior used when no page plan is supplied.
|
|
18873
|
+
*
|
|
18874
|
+
* @param input - Target document, entries, and source loader.
|
|
18875
|
+
* @returns One record per appended page, aligned with the target's pages.
|
|
18876
|
+
*/
|
|
18877
|
+
async function appendAllEntryPages(input) {
|
|
18878
|
+
const { target, entries, loadSource } = input;
|
|
18879
|
+
const records = [];
|
|
18880
|
+
for (const entry of entries) {
|
|
18881
|
+
if (entry.kind === 'pdf') {
|
|
18882
|
+
const source = await loadSource(entry);
|
|
18883
|
+
const indices = source.getPageIndices();
|
|
18884
|
+
const copied = await target.copyPages(source, indices);
|
|
18885
|
+
copied.forEach((page, index) => {
|
|
18886
|
+
target.addPage(page);
|
|
18887
|
+
records.push({ entry, sourceIndex: indices[index], rotation: normalizeRotationDegrees(page.getRotation().angle) });
|
|
18888
|
+
});
|
|
18889
|
+
}
|
|
18890
|
+
else {
|
|
18891
|
+
records.push(await appendImagePage(target, entry, 0));
|
|
18892
|
+
}
|
|
18893
|
+
}
|
|
18894
|
+
return records;
|
|
18895
|
+
}
|
|
18896
|
+
/**
|
|
18897
|
+
* Appends pages according to an explicit plan — honoring subsetting, reordering, and per-page rotation.
|
|
18898
|
+
*
|
|
18899
|
+
* Consecutive pages from the same entry are batched into a single `copyPages` call. `copyPages` returns pages in the order of the indices it is given, which is what lets subsetting and reordering happen in one operation.
|
|
18900
|
+
*
|
|
18901
|
+
* @param input - Target document, entries, page plan, and source loader.
|
|
18902
|
+
* @returns One record per appended page, aligned with the target's pages.
|
|
18903
|
+
*/
|
|
18904
|
+
async function appendPlannedPages(input) {
|
|
18905
|
+
const { target, entries, plan, loadSource } = input;
|
|
18906
|
+
const entriesById = new Map(entries.map((entry) => [entry.id, entry]));
|
|
18907
|
+
const visible = plan.filter((page) => !page.removed && entriesById.has(page.entryId));
|
|
18908
|
+
const records = [];
|
|
18909
|
+
let index = 0;
|
|
18910
|
+
while (index < visible.length) {
|
|
18911
|
+
const first = visible[index];
|
|
18912
|
+
const entry = entriesById.get(first.entryId);
|
|
18913
|
+
let end = index + 1;
|
|
18914
|
+
while (end < visible.length && visible[end].entryId === first.entryId) {
|
|
18915
|
+
end += 1;
|
|
18916
|
+
}
|
|
18917
|
+
const run = visible.slice(index, end);
|
|
18918
|
+
if (entry.kind === 'image') {
|
|
18919
|
+
for (const planned of run) {
|
|
18920
|
+
records.push(await appendImagePage(target, entry, normalizeRotationDegrees(planned.rotation)));
|
|
18921
|
+
}
|
|
18922
|
+
}
|
|
18923
|
+
else {
|
|
18924
|
+
const source = await loadSource(entry);
|
|
18925
|
+
const copied = await target.copyPages(source, run.map((planned) => planned.sourceIndex));
|
|
18926
|
+
copied.forEach((page, runIndex) => {
|
|
18927
|
+
const planned = run[runIndex];
|
|
18928
|
+
// Compose rather than replace: a scan already stored at 90° that the user rotates by 90° must land at 180°.
|
|
18929
|
+
const rotation = normalizeRotationDegrees(planned.meta.sourceRotation + planned.rotation);
|
|
18930
|
+
target.addPage(page);
|
|
18931
|
+
page.setRotation(degrees(rotation));
|
|
18932
|
+
records.push({ entry, sourceIndex: planned.sourceIndex, rotation });
|
|
18933
|
+
});
|
|
18934
|
+
}
|
|
18935
|
+
index = end;
|
|
18936
|
+
}
|
|
18937
|
+
return records;
|
|
18938
|
+
}
|
|
18939
|
+
/**
|
|
18940
|
+
* Tags each output page and attaches the manifest describing which pages belong to which slot.
|
|
18941
|
+
*
|
|
18942
|
+
* @param target - Document being assembled, whose pages align with `records`.
|
|
18943
|
+
* @param records - One record per appended page, in output order.
|
|
18944
|
+
*/
|
|
18945
|
+
async function writePdfMergeSidecarForRecords(target, records) {
|
|
18946
|
+
const ordinalBySlotKey = new Map();
|
|
18947
|
+
const outputPages = target.getPages();
|
|
18948
|
+
const sidecarPages = records.map((record, outputIndex) => {
|
|
18949
|
+
// Normalize to null so the value survives JSON.stringify, which drops undefined.
|
|
18950
|
+
const slotId = record.entry.slotId ?? null;
|
|
18951
|
+
const slotKey = slotId ?? '';
|
|
18952
|
+
const ordinal = ordinalBySlotKey.get(slotKey) ?? 0;
|
|
18953
|
+
const tag = makePdfMergeSidecarPageTag(slotId, ordinal);
|
|
18954
|
+
ordinalBySlotKey.set(slotKey, ordinal + 1);
|
|
18955
|
+
writePdfMergePageTag(outputPages[outputIndex], tag);
|
|
18956
|
+
return { tag, slotId, sourceName: record.entry.name, sourceIndex: record.sourceIndex, rotation: record.rotation, outputIndex };
|
|
18957
|
+
});
|
|
18958
|
+
await attachPdfMergeSidecar(target, makePdfMergeSidecar(sidecarPages));
|
|
18378
18959
|
}
|
|
18379
18960
|
/**
|
|
18380
18961
|
* Merges every `ready` entry in the provided array order into a single PDF and returns it as a `Blob`. PDF entries contribute their full set of pages in order; image entries contribute one page sized to the image.
|
|
18381
18962
|
*
|
|
18382
|
-
*
|
|
18963
|
+
* Supplying {@link MergePdfMergeEntriesOptions.pages} replaces that default with an explicit page plan, so pages can be subsetted, reordered, and rotated. Supplying {@link MergePdfMergeEntriesOptions.sidecar} additionally embeds a manifest of which pages came from which slot.
|
|
18383
18964
|
*
|
|
18384
|
-
*
|
|
18965
|
+
* Special case: when the only `ready` entry is a single encrypted PDF, returns a passthrough `Blob` of its original bytes — `pdf-lib` cannot read encrypted PDFs even with `ignoreEncryption: true`, and downstream upload flows still need a usable blob. Neither the page plan nor the sidecar applies on that path, since the document cannot be opened.
|
|
18966
|
+
*
|
|
18967
|
+
* Throws if no `ready` entries are provided, if multiple encrypted entries are passed in (the editor only routes here under focus mode where it has narrowed to one), or if the page plan leaves no pages to emit.
|
|
18385
18968
|
*
|
|
18386
18969
|
* @param entries - Ordered entries to merge.
|
|
18970
|
+
* @param options - Optional page plan and sidecar switch.
|
|
18387
18971
|
* @returns A Blob with `application/pdf` MIME type.
|
|
18388
18972
|
*/
|
|
18389
|
-
async function mergePdfMergeEntries(entries) {
|
|
18973
|
+
async function mergePdfMergeEntries(entries, options) {
|
|
18390
18974
|
const ready = entries.filter((entry) => entry.status === 'ready');
|
|
18391
18975
|
let result;
|
|
18392
18976
|
if (ready.length === 0) {
|
|
@@ -18401,13 +18985,14 @@ async function mergePdfMergeEntries(entries) {
|
|
|
18401
18985
|
}
|
|
18402
18986
|
else {
|
|
18403
18987
|
const target = await PDFDocument.create();
|
|
18404
|
-
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
|
|
18408
|
-
|
|
18409
|
-
|
|
18410
|
-
|
|
18988
|
+
const loadSource = makePdfSourceDocumentLoader();
|
|
18989
|
+
const plan = options?.pages;
|
|
18990
|
+
const records = plan == null ? await appendAllEntryPages({ target, entries: ready, loadSource }) : await appendPlannedPages({ target, entries: ready, plan, loadSource });
|
|
18991
|
+
if (records.length === 0) {
|
|
18992
|
+
throw new Error('No pages to merge.');
|
|
18993
|
+
}
|
|
18994
|
+
if (options?.sidecar) {
|
|
18995
|
+
await writePdfMergeSidecarForRecords(target, records);
|
|
18411
18996
|
}
|
|
18412
18997
|
const bytes = await target.save();
|
|
18413
18998
|
result = new Blob([bytes], { type: PDF_MERGE_RESULT_MIME_TYPE });
|
|
@@ -18416,11 +19001,39 @@ async function mergePdfMergeEntries(entries) {
|
|
|
18416
19001
|
}
|
|
18417
19002
|
|
|
18418
19003
|
/**
|
|
18419
|
-
* Initial state used by {@link DbxPdfMergeEditorStore} — no entries.
|
|
19004
|
+
* Initial state used by {@link DbxPdfMergeEditorStore} — no entries and no page edits.
|
|
18420
19005
|
*/
|
|
18421
19006
|
const DBX_PDF_MERGE_EDITOR_INITIAL_STATE = {
|
|
18422
|
-
rawEntries: []
|
|
19007
|
+
rawEntries: [],
|
|
19008
|
+
pageOverrides: {},
|
|
19009
|
+
pageOrder: {}
|
|
18423
19010
|
};
|
|
19011
|
+
/**
|
|
19012
|
+
* Drops page overrides and stored orderings that belong to entries no longer in the list.
|
|
19013
|
+
*
|
|
19014
|
+
* The derived plan already ignores stale ids, so this is hygiene rather than correctness — it keeps the two records from growing without bound as the user adds and removes files.
|
|
19015
|
+
*
|
|
19016
|
+
* @param state - Current state.
|
|
19017
|
+
* @param liveEntryIds - Ids of the entries that remain.
|
|
19018
|
+
* @returns The pruned page-state slice.
|
|
19019
|
+
* @__NO_SIDE_EFFECTS__
|
|
19020
|
+
*/
|
|
19021
|
+
function prunePdfMergePageState(state, liveEntryIds) {
|
|
19022
|
+
const pageOverrides = {};
|
|
19023
|
+
const pageOrder = {};
|
|
19024
|
+
Object.entries(state.pageOverrides).forEach(([pageId, override]) => {
|
|
19025
|
+
if (liveEntryIds.has(entryIdForPdfMergePageId(pageId))) {
|
|
19026
|
+
pageOverrides[pageId] = override;
|
|
19027
|
+
}
|
|
19028
|
+
});
|
|
19029
|
+
Object.entries(state.pageOrder).forEach(([groupKey, pageIds]) => {
|
|
19030
|
+
const kept = pageIds.filter((pageId) => liveEntryIds.has(entryIdForPdfMergePageId(pageId)));
|
|
19031
|
+
if (kept.length > 0) {
|
|
19032
|
+
pageOrder[groupKey] = kept;
|
|
19033
|
+
}
|
|
19034
|
+
});
|
|
19035
|
+
return { pageOverrides, pageOrder };
|
|
19036
|
+
}
|
|
18424
19037
|
/**
|
|
18425
19038
|
* Component-scoped {@link ComponentStore} that owns the list of files staged for merging in the PDF merge editor. Each {@link PdfMergeEntry} carries its own validation promise from the moment it is built; {@link entries$} composes those promises into a live stream — emitting the entry first in `validating` state and then again as each promise resolves to `ready` or `error`. {@link mergeOutput$} emits the merged PDF blob once every entry has finished validating, at least one is `ready`, and the registered validator delegate (if any) reports `true`.
|
|
18426
19039
|
*/
|
|
@@ -18429,6 +19042,22 @@ class DbxPdfMergeEditorStore extends ComponentStore {
|
|
|
18429
19042
|
_outputSizeLimit$ = new BehaviorSubject(undefined);
|
|
18430
19043
|
_imageCompression$ = new BehaviorSubject(undefined);
|
|
18431
19044
|
_encryptedHandling$ = new BehaviorSubject(undefined);
|
|
19045
|
+
_pageEditing$ = new BehaviorSubject(undefined);
|
|
19046
|
+
_sidecar$ = new BehaviorSubject(undefined);
|
|
19047
|
+
/**
|
|
19048
|
+
* Per-entry page metadata, parsed at most once per entry. Keyed by entry id; a resolved `null` means the entry could not be expanded. Only populated while page editing is enabled.
|
|
19049
|
+
*/
|
|
19050
|
+
_pageMetaCache = new Map();
|
|
19051
|
+
/**
|
|
19052
|
+
* Mount count per registered slot id. See {@link registerSlotId} for why this is a count rather than a set.
|
|
19053
|
+
*/
|
|
19054
|
+
_slotIdCounts = new Map();
|
|
19055
|
+
_registeredSlotIds$ = new BehaviorSubject([]);
|
|
19056
|
+
_importState$ = new BehaviorSubject(undefined);
|
|
19057
|
+
/**
|
|
19058
|
+
* Input of the most recent successful `programmatic` import, replayed by {@link clearEntries} to reset the editor to the document the app supplied. `null` until one succeeds; a user's pick never overwrites it.
|
|
19059
|
+
*/
|
|
19060
|
+
_restorableImport;
|
|
18432
19061
|
constructor() {
|
|
18433
19062
|
super(DBX_PDF_MERGE_EDITOR_INITIAL_STATE);
|
|
18434
19063
|
}
|
|
@@ -18496,28 +19125,123 @@ class DbxPdfMergeEditorStore extends ComponentStore {
|
|
|
18496
19125
|
*/
|
|
18497
19126
|
encryptedEntries$ = this.entries$.pipe(map((entries) => entries.filter((entry) => entry.encrypted && entry.status === 'ready')), shareReplay(1));
|
|
18498
19127
|
hasReadyEntries$ = this.displayEntries$.pipe(map((entries) => entries.some((entry) => entry.status === 'ready' && !entry.ignored)), distinctUntilChanged(), shareReplay(1));
|
|
19128
|
+
// MARK: Page editing
|
|
18499
19129
|
/**
|
|
18500
|
-
*
|
|
19130
|
+
* Raw {@link DbxPdfMergeEditorConfig.pageEditing} value pushed onto the store, before defaulting. Consumed by the editor as the middle tier of its resolution chain (own `[config]` input → store → {@link DBX_PDF_MERGE_EDITOR_CONFIG} token), so a store-level default does not shadow the token.
|
|
18501
19131
|
*/
|
|
18502
|
-
|
|
19132
|
+
pageEditingSetting$ = this._pageEditing$.asObservable();
|
|
18503
19133
|
/**
|
|
18504
|
-
*
|
|
19134
|
+
* Whether page editing is active, defaulted to {@link DEFAULT_DBX_PDF_MERGE_PAGE_EDITING}. Every page-related stream is inert while this is `false`.
|
|
18505
19135
|
*/
|
|
18506
|
-
|
|
19136
|
+
pageEditing$ = this.pageEditingSetting$.pipe(map((pageEditing) => pageEditing ?? DEFAULT_DBX_PDF_MERGE_PAGE_EDITING), distinctUntilChanged(), shareReplay(1));
|
|
18507
19137
|
/**
|
|
18508
|
-
*
|
|
19138
|
+
* Raw {@link DbxPdfMergeEditorConfig.sidecar} value pushed onto the store, before defaulting.
|
|
18509
19139
|
*/
|
|
18510
|
-
|
|
18511
|
-
|
|
18512
|
-
|
|
19140
|
+
sidecarSetting$ = this._sidecar$.asObservable();
|
|
19141
|
+
/**
|
|
19142
|
+
* Whether the merged output should carry an embedded manifest, defaulted to {@link DEFAULT_DBX_PDF_MERGE_SIDECAR}. Independent of {@link pageEditing$}.
|
|
19143
|
+
*/
|
|
19144
|
+
sidecar$ = this.sidecarSetting$.pipe(map((sidecar) => sidecar ?? DEFAULT_DBX_PDF_MERGE_SIDECAR), distinctUntilChanged(), shareReplay(1));
|
|
19145
|
+
/**
|
|
19146
|
+
* Hydrated page metadata keyed by entry id.
|
|
19147
|
+
*
|
|
19148
|
+
* Short-circuits to an empty record while page editing is off, so the default configuration never parses a source document — the upload path's cheap header scan stays the only inspection performed. Each entry is parsed at most once thanks to {@link _pageMetaCache}, and metadata for departed entries is pruned on each pass.
|
|
19149
|
+
*/
|
|
19150
|
+
pageMetas$ = combineLatest([this.entries$, this.pageEditing$]).pipe(switchMap(([entries, pageEditing]) => {
|
|
19151
|
+
const ready = pageEditing ? entries.filter((entry) => entry.status === 'ready') : [];
|
|
18513
19152
|
let next$;
|
|
18514
|
-
if (
|
|
18515
|
-
next$ = of(
|
|
19153
|
+
if (ready.length === 0) {
|
|
19154
|
+
next$ = of({});
|
|
18516
19155
|
}
|
|
18517
19156
|
else {
|
|
18518
|
-
|
|
18519
|
-
|
|
18520
|
-
|
|
19157
|
+
const readyIds = new Set(ready.map((entry) => entry.id));
|
|
19158
|
+
Array.from(this._pageMetaCache.keys()).forEach((entryId) => {
|
|
19159
|
+
if (!readyIds.has(entryId)) {
|
|
19160
|
+
this._pageMetaCache.delete(entryId);
|
|
19161
|
+
}
|
|
19162
|
+
});
|
|
19163
|
+
const loaded = ready.map(async (entry) => {
|
|
19164
|
+
let metas = this._pageMetaCache.get(entry.id);
|
|
19165
|
+
if (metas == null) {
|
|
19166
|
+
metas = readPdfMergeEntryPageMetas(entry);
|
|
19167
|
+
this._pageMetaCache.set(entry.id, metas);
|
|
19168
|
+
}
|
|
19169
|
+
return [entry.id, await metas];
|
|
19170
|
+
});
|
|
19171
|
+
next$ = from(Promise.all(loaded)).pipe(map((pairs) => Object.fromEntries(pairs)));
|
|
19172
|
+
}
|
|
19173
|
+
return next$;
|
|
19174
|
+
}), shareReplay(1));
|
|
19175
|
+
/**
|
|
19176
|
+
* The editor's ordered page plan, or `null` while page editing is disabled. Passing `null` through to the merge is what makes the default path emit every page of every entry exactly as it did before page editing existed.
|
|
19177
|
+
*/
|
|
19178
|
+
pages$ = combineLatest([this.displayEntries$, this.pageEditing$, this.pageMetas$, this.select((state) => state.pageOverrides), this.select((state) => state.pageOrder)]).pipe(map(([entries, pageEditing, pageMetas, pageOverrides, pageOrder]) => {
|
|
19179
|
+
let pages;
|
|
19180
|
+
if (pageEditing) {
|
|
19181
|
+
pages = buildPdfMergePagePlan({ entries, pageMetas, pageOverrides, pageOrder });
|
|
19182
|
+
}
|
|
19183
|
+
else {
|
|
19184
|
+
pages = null;
|
|
19185
|
+
}
|
|
19186
|
+
return pages;
|
|
19187
|
+
}), shareReplay(1));
|
|
19188
|
+
/**
|
|
19189
|
+
* The page plan split into the groups the UI renders, one per slot plus one for unslotted entries. Empty while page editing is disabled.
|
|
19190
|
+
*/
|
|
19191
|
+
pageGroups$ = this.pages$.pipe(map((pages) => {
|
|
19192
|
+
const grouped = makeValuesGroupMap(pages ?? [], (page) => page.groupKey);
|
|
19193
|
+
const groups = [];
|
|
19194
|
+
grouped.forEach((groupPages, groupKey) => {
|
|
19195
|
+
groups.push({ groupKey: groupKey, slotId: groupPages[0]?.slotId ?? null, pages: groupPages });
|
|
19196
|
+
});
|
|
19197
|
+
return groups;
|
|
19198
|
+
}), shareReplay(1));
|
|
19199
|
+
/**
|
|
19200
|
+
* Entries that are ready to merge but whose pages could not be listed — encrypted documents (which `pdf-lib` cannot open) and anything unparseable. Surfaced so the UI can explain why those rows are not expandable instead of letting them silently vanish from the page list. Always empty while page editing is disabled.
|
|
19201
|
+
*/
|
|
19202
|
+
unexpandableEntries$ = combineLatest([this.displayEntries$, this.pageEditing$, this.pageMetas$]).pipe(map(([entries, pageEditing, pageMetas]) => (pageEditing ? entries.filter((entry) => entry.status === 'ready' && !entry.ignored && pageMetas[entry.id] === null) : [])), shareReplay(1));
|
|
19203
|
+
/**
|
|
19204
|
+
* Number of pages that will reach the merged output — the plan minus anything marked for removal. Zero while page editing is disabled, where the count is not meaningful.
|
|
19205
|
+
*/
|
|
19206
|
+
mergeablePageCount$ = this.pages$.pipe(map((pages) => (pages ?? []).filter((page) => !page.removed).length), distinctUntilChanged(), shareReplay(1));
|
|
19207
|
+
/**
|
|
19208
|
+
* Whether at least one page survives the user's edits. Emits `true` while page editing is disabled so it never gates the default path.
|
|
19209
|
+
*/
|
|
19210
|
+
hasMergeablePages$ = this.pages$.pipe(map((pages) => pages == null || pages.some((page) => !page.removed)), distinctUntilChanged(), shareReplay(1));
|
|
19211
|
+
/**
|
|
19212
|
+
* Returns the pages belonging to one group, for a slot rendering its own pages inline.
|
|
19213
|
+
*
|
|
19214
|
+
* @param slotId - Slot identifier, or `null` for the unslotted group.
|
|
19215
|
+
* @returns Observable of that group's ordered pages.
|
|
19216
|
+
*/
|
|
19217
|
+
pagesForSlotId$(slotId) {
|
|
19218
|
+
const groupKey = pdfMergePageGroupKeyForSlotId(slotId);
|
|
19219
|
+
return this.pages$.pipe(map((pages) => (pages ?? []).filter((page) => page.groupKey === groupKey)), shareReplay(1));
|
|
19220
|
+
}
|
|
19221
|
+
/**
|
|
19222
|
+
* Emits `true` while any entry's validation promise has not yet resolved (i.e. one or more entries are still in `validating` status). Reads from {@link entries$} (not {@link displayEntries$}) so validation gating ignores the `ignored`/`error` projection done by encryption handling.
|
|
19223
|
+
*/
|
|
19224
|
+
isValidating$ = this.entries$.pipe(map((entries) => entries.some((entry) => entry.status === 'validating')), distinctUntilChanged(), shareReplay(1));
|
|
19225
|
+
/**
|
|
19226
|
+
* Emits the boolean output of the registered {@link DbxPdfMergeEditorValidator} delegate, or a constant `true` when no delegate is registered. Composed with {@link sizeLimitValid$} into {@link isValid$} to gate {@link currentMergeOutput$}.
|
|
19227
|
+
*/
|
|
19228
|
+
validatorValid$ = this._validator$.pipe(switchMap((validator) => (validator ? validator(this.entries$) : of(true))), distinctUntilChanged(), shareReplay(1));
|
|
19229
|
+
/**
|
|
19230
|
+
* Internal pre-validity merge stream produced without consulting {@link isValid$}. Drives both {@link outputSize$} and the eventual {@link currentMergeOutput$} so size-based gating can observe the would-be blob without creating a cycle. Consumes {@link displayEntries$} so the merge respects the active {@link DbxPdfMergeEncryptedHandling} (encrypted-focused entries pass through, ignored entries are dropped, `error` mode demotions are honored).
|
|
19231
|
+
*/
|
|
19232
|
+
_candidateMergeOutput$ = combineLatest([this.displayEntries$, this.isValidating$, this.validatorValid$, this.pages$, this.sidecar$]).pipe(switchMap(([entries, isValidating, validatorValid, pages, sidecar]) => {
|
|
19233
|
+
const mergeable = entries.filter((entry) => !entry.ignored);
|
|
19234
|
+
const hasReady = mergeable.some((entry) => entry.status === 'ready');
|
|
19235
|
+
// A `null` plan means page editing is off, in which case the merge takes its original every-page path.
|
|
19236
|
+
const hasPages = pages == null || pages.some((page) => !page.removed);
|
|
19237
|
+
let next$;
|
|
19238
|
+
if (isValidating || !hasReady || !validatorValid || !hasPages) {
|
|
19239
|
+
next$ = of(undefined);
|
|
19240
|
+
}
|
|
19241
|
+
else {
|
|
19242
|
+
next$ = from(mergePdfMergeEntries(mergeable, { pages, sidecar })).pipe(catchError(() => of(undefined)));
|
|
19243
|
+
}
|
|
19244
|
+
return next$;
|
|
18521
19245
|
}), shareReplay(1));
|
|
18522
19246
|
/**
|
|
18523
19247
|
* Emits the byte size of the most recent candidate merge output, or `undefined` while there is none.
|
|
@@ -18537,9 +19261,11 @@ class DbxPdfMergeEditorStore extends ComponentStore {
|
|
|
18537
19261
|
return valid;
|
|
18538
19262
|
}), distinctUntilChanged(), shareReplay(1));
|
|
18539
19263
|
/**
|
|
18540
|
-
* Emits `true` while
|
|
19264
|
+
* Emits `true` while the registered {@link DbxPdfMergeEditorValidator} delegate (if any), the optional output-size limit, and the page plan are all satisfied. {@link currentMergeOutput$} gates merge emissions on this stream.
|
|
19265
|
+
*
|
|
19266
|
+
* The page-plan term is `true` whenever page editing is disabled, so this stream is unchanged on the default path. With page editing on it blocks the merge once the user has marked every page for deletion.
|
|
18541
19267
|
*/
|
|
18542
|
-
isValid$ = combineLatest([this.validatorValid$, this.sizeLimitValid$]).pipe(map(([validatorValid, sizeLimitValid]) => validatorValid && sizeLimitValid), distinctUntilChanged(), shareReplay(1));
|
|
19268
|
+
isValid$ = combineLatest([this.validatorValid$, this.sizeLimitValid$, this.hasMergeablePages$]).pipe(map(([validatorValid, sizeLimitValid, hasMergeablePages]) => validatorValid && sizeLimitValid && hasMergeablePages), distinctUntilChanged(), shareReplay(1));
|
|
18543
19269
|
/**
|
|
18544
19270
|
* Emits the merged PDF blob whenever every entry has finished validating (see {@link isValidating$}), at least one is `ready`, and {@link isValid$} reports `true`. Emits `undefined` while validation is in flight, when the list is empty, when the delegate or size limit reports invalid, or when the most recent merge failed.
|
|
18545
19271
|
*/
|
|
@@ -18558,6 +19284,176 @@ class DbxPdfMergeEditorStore extends ComponentStore {
|
|
|
18558
19284
|
entriesForSlotId$(slotId) {
|
|
18559
19285
|
return this.displayEntries$.pipe(map((entries) => entries.filter((entry) => entry.slotId === slotId)), shareReplay(1));
|
|
18560
19286
|
}
|
|
19287
|
+
// MARK: Slot registry
|
|
19288
|
+
/**
|
|
19289
|
+
* Slot ids currently mounted against this store, i.e. the sections this editor declares.
|
|
19290
|
+
*
|
|
19291
|
+
* This is what makes `<dbx-pdf-merge-import>` a zero-configuration drop-in: it derives the set of sections a re-imported file is allowed to name (and whether it renders at all) from the slots actually on screen, rather than from a hand-maintained list duplicating them.
|
|
19292
|
+
*
|
|
19293
|
+
* Emitted **sorted ascending**, which is deterministic but is NOT declaration order. Sorting is what lets the guard below collapse slot churn: a set cycling through `@if` (`['a','b']` → `['b']` → `['b','a']`) ends up equal to the array it started with, where registration order would emit a spuriously different array for an identical set.
|
|
19294
|
+
*
|
|
19295
|
+
* Registered by {@link DbxPdfMergeEditorFileUploadComponent} from its own lifecycle. Kept here rather than on the optional {@link DbxPdfMergeEditorFileUploadValidatorDirective} because it must be available whether or not a validator is present — and because a separately-provided registry could be provided at a different level than the store, letting slots register with one instance while a reader sees another.
|
|
19296
|
+
*/
|
|
19297
|
+
registeredSlotIds$ = this._registeredSlotIds$.pipe(distinctUntilChanged((a, b) => a.length === b.length && a.every((slotId, i) => slotId === b[i])), shareReplay(1));
|
|
19298
|
+
/**
|
|
19299
|
+
* Registers a slot id as mounted, adding it to {@link registeredSlotIds$}.
|
|
19300
|
+
*
|
|
19301
|
+
* Reference-counted rather than a set: the same `slotId` can legitimately be mounted twice at once — the PDF merge upload dialog hosts its slots against an ancestor store while inline slots may still exist under {@link DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY} — and one unmount must not deregister a still-mounted id.
|
|
19302
|
+
*
|
|
19303
|
+
* @param slotId - Slot identifier being mounted.
|
|
19304
|
+
*/
|
|
19305
|
+
registerSlotId(slotId) {
|
|
19306
|
+
this._slotIdCounts.set(slotId, (this._slotIdCounts.get(slotId) ?? 0) + 1);
|
|
19307
|
+
this._emitRegisteredSlotIds();
|
|
19308
|
+
}
|
|
19309
|
+
/**
|
|
19310
|
+
* Deregisters one mount of a slot id, removing it from {@link registeredSlotIds$} once the last mount is gone. Unknown ids are ignored.
|
|
19311
|
+
*
|
|
19312
|
+
* Callers must pair this with their own {@link registerSlotId} exactly once. The store frequently outlives its slots (a dialog-hosted slot is destroyed on every close while the page-level store lives on), so a missed call leaves the editor permanently claiming a section that is not on screen.
|
|
19313
|
+
*
|
|
19314
|
+
* @param slotId - Slot identifier being unmounted.
|
|
19315
|
+
*/
|
|
19316
|
+
unregisterSlotId(slotId) {
|
|
19317
|
+
const count = this._slotIdCounts.get(slotId);
|
|
19318
|
+
if (count != null) {
|
|
19319
|
+
if (count > 1) {
|
|
19320
|
+
this._slotIdCounts.set(slotId, count - 1);
|
|
19321
|
+
}
|
|
19322
|
+
else {
|
|
19323
|
+
this._slotIdCounts.delete(slotId);
|
|
19324
|
+
}
|
|
19325
|
+
this._emitRegisteredSlotIds();
|
|
19326
|
+
}
|
|
19327
|
+
}
|
|
19328
|
+
_emitRegisteredSlotIds() {
|
|
19329
|
+
const next = Array.from(this._slotIdCounts.keys()).sort();
|
|
19330
|
+
const current = this._registeredSlotIds$.value;
|
|
19331
|
+
if (next.length !== current.length || next.some((slotId, i) => slotId !== current[i])) {
|
|
19332
|
+
this._registeredSlotIds$.next(next);
|
|
19333
|
+
}
|
|
19334
|
+
}
|
|
19335
|
+
// MARK: Import
|
|
19336
|
+
/**
|
|
19337
|
+
* Lifecycle of the most recent {@link importMergedPdf} call, or `undefined` before the first. Lets a consumer that triggered a programmatic import observe its outcome — the picker component is not the only entry point.
|
|
19338
|
+
*/
|
|
19339
|
+
importState$ = this._importState$.asObservable();
|
|
19340
|
+
/**
|
|
19341
|
+
* The most recent successful import, or `null` while none has succeeded.
|
|
19342
|
+
*/
|
|
19343
|
+
importResult$ = this.importState$.pipe(map((state) => (state?.status === 'imported' ? state.result : null)), distinctUntilChanged(), shareReplay(1));
|
|
19344
|
+
/**
|
|
19345
|
+
* Why the most recent import failed, or `null` when it did not.
|
|
19346
|
+
*/
|
|
19347
|
+
importError$ = this.importState$.pipe(map((state) => (state?.status === 'failed' ? state.error : null)), distinctUntilChanged(), shareReplay(1));
|
|
19348
|
+
/**
|
|
19349
|
+
* Emits `true` while an import is in flight.
|
|
19350
|
+
*/
|
|
19351
|
+
isImporting$ = this.importState$.pipe(map((state) => state?.status === 'importing'), distinctUntilChanged(), shareReplay(1));
|
|
19352
|
+
/**
|
|
19353
|
+
* Whether a programmatic baseline exists for {@link clearEntries} to restore. Lets a consumer word its own confirmation honestly — "reset to the imported document" rather than "remove everything".
|
|
19354
|
+
*
|
|
19355
|
+
* @returns `true` once a programmatic import has succeeded.
|
|
19356
|
+
*/
|
|
19357
|
+
hasRestorableImport() {
|
|
19358
|
+
return this._restorableImport != null;
|
|
19359
|
+
}
|
|
19360
|
+
/**
|
|
19361
|
+
* Imports a previously-exported merged PDF, replacing every entry currently in the store with the per-slot documents recorded in its manifest.
|
|
19362
|
+
*
|
|
19363
|
+
* This is the programmatic equivalent of what {@link DbxPdfMergeImportComponent} does with a picked file, and that component delegates here — so a stored document loaded by an app and a file chosen by a user go through one implementation with one set of error semantics. Progress and outcome are published on {@link importState$} as well as returned.
|
|
19364
|
+
*
|
|
19365
|
+
* @param input - The source blob plus the optional slot check and sidecar-less fallback.
|
|
19366
|
+
* @returns The terminal state of this import.
|
|
19367
|
+
*/
|
|
19368
|
+
async importMergedPdf(input) {
|
|
19369
|
+
const { source, expectedSlotIds, allowWithoutSidecar, fileName } = input;
|
|
19370
|
+
const origin = input.origin ?? 'programmatic';
|
|
19371
|
+
const config = { allowWithoutSidecar, fileName };
|
|
19372
|
+
this._importState$.next({ status: 'importing', origin });
|
|
19373
|
+
const outcome = await buildPdfMergeEntriesFromSidecar(source, config);
|
|
19374
|
+
let state;
|
|
19375
|
+
if ('error' in outcome) {
|
|
19376
|
+
state = { status: 'failed', origin, error: outcome.error };
|
|
19377
|
+
}
|
|
19378
|
+
else {
|
|
19379
|
+
// An unsectioned document counts as unexpected too: in a slots-only editor it would render
|
|
19380
|
+
// nowhere, so rejecting it beats importing pages the user can never see again.
|
|
19381
|
+
const unexpected = expectedSlotIds == null ? [] : outcome.slotIds.filter((slotId) => slotId == null || !expectedSlotIds.includes(slotId));
|
|
19382
|
+
if (unexpected.length > 0) {
|
|
19383
|
+
state = { status: 'failed', origin, error: 'unexpected_slots', unexpectedSlotIds: unexpected };
|
|
19384
|
+
}
|
|
19385
|
+
else {
|
|
19386
|
+
const missingSlotIds = expectedSlotIds == null ? [] : expectedSlotIds.filter((slotId) => !outcome.slotIds.includes(slotId));
|
|
19387
|
+
this.replaceEntries(outcome.entries);
|
|
19388
|
+
state = { status: 'imported', origin, result: { ...outcome, missingSlotIds } };
|
|
19389
|
+
// A programmatic import is the app telling the editor what it should be showing, so it
|
|
19390
|
+
// becomes the restore point. A user's pick deliberately does NOT overwrite it: the person
|
|
19391
|
+
// replaced what is on screen, not what the app configured.
|
|
19392
|
+
if (origin === 'programmatic') {
|
|
19393
|
+
this._restorableImport = input;
|
|
19394
|
+
}
|
|
19395
|
+
}
|
|
19396
|
+
}
|
|
19397
|
+
this._importState$.next(state);
|
|
19398
|
+
return state;
|
|
19399
|
+
}
|
|
19400
|
+
/**
|
|
19401
|
+
* Discards the current import lifecycle, so consumers rendering its notices (the picker's "Imported N section(s)" success line, its missing-section warning, its error) fall back to showing nothing.
|
|
19402
|
+
*
|
|
19403
|
+
* Separate from the entry list — this clears the *record* of an import, not its contents.
|
|
19404
|
+
*/
|
|
19405
|
+
clearImportState() {
|
|
19406
|
+
this._importState$.next(undefined);
|
|
19407
|
+
}
|
|
19408
|
+
/**
|
|
19409
|
+
* Empties the editor, or resets it to its programmatic baseline when one exists.
|
|
19410
|
+
*
|
|
19411
|
+
* This is the "Clear" affordance's operation, and it deliberately is not a plain {@link clearAll}. When the surrounding app supplied the document (a programmatic {@link importMergedPdf}, or `[source]` on {@link DbxPdfMergeEditorStoreDirective}), emptying the editor would strand it in a state the app never asked for and has no obvious way to notice. Instead the original import is re-run, landing the view exactly where it was when the document first arrived — page edits, section removals, and any user-picked file on top of it all discarded.
|
|
19412
|
+
*
|
|
19413
|
+
* With no baseline — the ordinary case, including an editor whose only import came from {@link DbxPdfMergeImportComponent} — the entries and the import state both go, so no stale success/warning notice outlives the content it described.
|
|
19414
|
+
*
|
|
19415
|
+
* @param input - Optional override for the restore behavior.
|
|
19416
|
+
* @returns Whether the baseline was restored, plus the re-import's terminal state when one ran.
|
|
19417
|
+
*/
|
|
19418
|
+
async clearEntries(input) {
|
|
19419
|
+
const restorable = this._restorableImport;
|
|
19420
|
+
const shouldRestore = (input?.restoreImport ?? true) && restorable != null;
|
|
19421
|
+
let result;
|
|
19422
|
+
if (shouldRestore) {
|
|
19423
|
+
const importState = await this.importMergedPdf(restorable);
|
|
19424
|
+
// A baseline that no longer imports (unreadable retained blob) must not leave the old
|
|
19425
|
+
// entries sitting there — the user asked for a clear and got neither.
|
|
19426
|
+
if (importState.status === 'imported') {
|
|
19427
|
+
result = { restored: true, importState };
|
|
19428
|
+
}
|
|
19429
|
+
else {
|
|
19430
|
+
this.clearAll();
|
|
19431
|
+
result = { restored: false, importState };
|
|
19432
|
+
}
|
|
19433
|
+
}
|
|
19434
|
+
else {
|
|
19435
|
+
this.clearAll();
|
|
19436
|
+
this.clearImportState();
|
|
19437
|
+
result = { restored: false };
|
|
19438
|
+
}
|
|
19439
|
+
return result;
|
|
19440
|
+
}
|
|
19441
|
+
/**
|
|
19442
|
+
* Adds a single document to a slot, without any sidecar involvement — the programmatic equivalent of a user dropping one file onto a slot uploader. Existing entries are left alone.
|
|
19443
|
+
*
|
|
19444
|
+
* Unlike {@link importMergedPdf} this neither reads nor requires a manifest; it is the plain "put this file in that slot" primitive, for e.g. an admin appending one document to an existing set. The entry can be added before its slot mounts — {@link entriesForSlotId$} picks it up when the slot renders.
|
|
19445
|
+
*
|
|
19446
|
+
* @param input - The blob or file, its target slot, and an optional name for a bare blob.
|
|
19447
|
+
* @returns The entry that was added, or `null` when the file is not a supported PDF/PNG/JPEG (in which case nothing was added).
|
|
19448
|
+
*/
|
|
19449
|
+
async addFileToSlot(input) {
|
|
19450
|
+
const { file, slotId, fileName, imageCompression } = input;
|
|
19451
|
+
const entry = await buildPdfMergeEntry(asPdfMergeFile(file, fileName), { slotId, imageCompression: imageCompression ?? this._imageCompression$.value });
|
|
19452
|
+
if (entry != null) {
|
|
19453
|
+
this.addFiles({ entries: [entry] });
|
|
19454
|
+
}
|
|
19455
|
+
return entry;
|
|
19456
|
+
}
|
|
18561
19457
|
// MARK: Validator
|
|
18562
19458
|
/**
|
|
18563
19459
|
* Registers a {@link DbxPdfMergeEditorValidator} delegate that gates merge emissions. Only one delegate is active at a time — calling this replaces any previously registered delegate.
|
|
@@ -18597,6 +19493,22 @@ class DbxPdfMergeEditorStore extends ComponentStore {
|
|
|
18597
19493
|
setEncryptedHandling(handling) {
|
|
18598
19494
|
this._encryptedHandling$.next(handling ?? undefined);
|
|
18599
19495
|
}
|
|
19496
|
+
/**
|
|
19497
|
+
* Enables or disables page editing, exposed via {@link pageEditing$}. Turning it on expands each ready entry into its individual pages; turning it off returns the editor to its file-granular behavior while preserving any page edits in state, so toggling back on restores them.
|
|
19498
|
+
*
|
|
19499
|
+
* @param pageEditing - Whether page editing is active, or a falsy value to clear and fall back to {@link DEFAULT_DBX_PDF_MERGE_PAGE_EDITING}.
|
|
19500
|
+
*/
|
|
19501
|
+
setPageEditing(pageEditing) {
|
|
19502
|
+
this._pageEditing$.next(pageEditing ?? undefined);
|
|
19503
|
+
}
|
|
19504
|
+
/**
|
|
19505
|
+
* Enables or disables the embedded manifest, exposed via {@link sidecar$}.
|
|
19506
|
+
*
|
|
19507
|
+
* @param sidecar - Whether to embed the manifest, or a falsy value to clear and fall back to {@link DEFAULT_DBX_PDF_MERGE_SIDECAR}.
|
|
19508
|
+
*/
|
|
19509
|
+
setSidecar(sidecar) {
|
|
19510
|
+
this._sidecar$.next(sidecar ?? undefined);
|
|
19511
|
+
}
|
|
18600
19512
|
// MARK: Updaters
|
|
18601
19513
|
/**
|
|
18602
19514
|
* Appends entries (already constructed) or builds them from raw files and appends them to state. Each entry's validation promise starts when the entry is built; {@link entries$} reflects each result as it resolves. When `input` is an object with `files` and `slotId`, the resulting entries are tagged with that slot id. When `input` is `{ entries }`, the entries are appended as-is — use this shape for entries that went through async client-side compression upstream.
|
|
@@ -18619,11 +19531,17 @@ class DbxPdfMergeEditorStore extends ComponentStore {
|
|
|
18619
19531
|
}
|
|
18620
19532
|
return newEntries.length > 0 ? { ...state, rawEntries: [...state.rawEntries, ...newEntries] } : state;
|
|
18621
19533
|
});
|
|
18622
|
-
removeEntry = this.updater((state, id) =>
|
|
19534
|
+
removeEntry = this.updater((state, id) => {
|
|
19535
|
+
const rawEntries = state.rawEntries.filter((entry) => entry.id !== id);
|
|
19536
|
+
return { ...state, rawEntries, ...prunePdfMergePageState(state, new Set(rawEntries.map((entry) => entry.id))) };
|
|
19537
|
+
});
|
|
18623
19538
|
/**
|
|
18624
19539
|
* Removes every entry whose `slotId` matches the given id. Used by {@link DbxPdfMergeEditorFileUploadComponent} on destroy so a slot's entries leave with it.
|
|
18625
19540
|
*/
|
|
18626
|
-
removeEntriesBySlotId = this.updater((state, slotId) =>
|
|
19541
|
+
removeEntriesBySlotId = this.updater((state, slotId) => {
|
|
19542
|
+
const rawEntries = state.rawEntries.filter((entry) => entry.slotId !== slotId);
|
|
19543
|
+
return { ...state, rawEntries, ...prunePdfMergePageState(state, new Set(rawEntries.map((entry) => entry.id))) };
|
|
19544
|
+
});
|
|
18627
19545
|
moveEntry = this.updater((state, move) => {
|
|
18628
19546
|
let nextState;
|
|
18629
19547
|
if (move.previousIndex === move.currentIndex) {
|
|
@@ -18663,7 +19581,49 @@ class DbxPdfMergeEditorStore extends ComponentStore {
|
|
|
18663
19581
|
}
|
|
18664
19582
|
return nextState;
|
|
18665
19583
|
});
|
|
18666
|
-
clearAll = this.updater((state) => ({ ...state, rawEntries: [] }));
|
|
19584
|
+
clearAll = this.updater((state) => ({ ...state, rawEntries: [], pageOverrides: {}, pageOrder: {} }));
|
|
19585
|
+
/**
|
|
19586
|
+
* Replaces the entire entry list, discarding any page edits.
|
|
19587
|
+
*
|
|
19588
|
+
* Used by the sidecar import path, which reconstructs a fresh set of slot-tagged entries from a previously-exported file. Done as a single state change so subscribers never observe an intermediate empty list.
|
|
19589
|
+
*/
|
|
19590
|
+
replaceEntries = this.updater((state, entries) => ({ ...state, rawEntries: [...entries], pageOverrides: {}, pageOrder: {} }));
|
|
19591
|
+
// MARK: Page updaters
|
|
19592
|
+
/**
|
|
19593
|
+
* Reorders a page inside its own group. Groups never exchange pages — the page list renders one CDK drop list per group, so a cross-group drag is impossible to express.
|
|
19594
|
+
*
|
|
19595
|
+
* The move rewrites the id array the caller rendered rather than recomputing it from state, keeping the stored order aligned with what the user saw.
|
|
19596
|
+
*/
|
|
19597
|
+
movePageWithinGroup = this.updater((state, move) => {
|
|
19598
|
+
let nextState;
|
|
19599
|
+
if (move.previousIndex === move.currentIndex || move.previousIndex < 0 || move.currentIndex < 0 || move.previousIndex >= move.pageIds.length || move.currentIndex >= move.pageIds.length) {
|
|
19600
|
+
nextState = state;
|
|
19601
|
+
}
|
|
19602
|
+
else {
|
|
19603
|
+
const pageIds = [...move.pageIds];
|
|
19604
|
+
moveItemInArray(pageIds, move.previousIndex, move.currentIndex);
|
|
19605
|
+
nextState = { ...state, pageOrder: { ...state.pageOrder, [move.groupKey]: pageIds } };
|
|
19606
|
+
}
|
|
19607
|
+
return nextState;
|
|
19608
|
+
});
|
|
19609
|
+
/**
|
|
19610
|
+
* Sets a page's rotation. The value is composed onto the page's own source rotation at merge time, so this is the rotation the user applied rather than the page's absolute orientation.
|
|
19611
|
+
*/
|
|
19612
|
+
setPageRotation = this.updater((state, change) => {
|
|
19613
|
+
const current = state.pageOverrides[change.pageId];
|
|
19614
|
+
return { ...state, pageOverrides: { ...state.pageOverrides, [change.pageId]: { rotation: change.rotation, removed: current?.removed ?? false } } };
|
|
19615
|
+
});
|
|
19616
|
+
/**
|
|
19617
|
+
* Marks a page for deletion or restores it. Marked pages stay in the list so the choice can be undone, but are excluded from the merged output.
|
|
19618
|
+
*/
|
|
19619
|
+
setPageRemoved = this.updater((state, change) => {
|
|
19620
|
+
const current = state.pageOverrides[change.pageId];
|
|
19621
|
+
return { ...state, pageOverrides: { ...state.pageOverrides, [change.pageId]: { rotation: current?.rotation ?? 0, removed: change.removed } } };
|
|
19622
|
+
});
|
|
19623
|
+
/**
|
|
19624
|
+
* Discards every page edit, returning the plan to natural order with no rotations or deletions. Leaves the entries themselves untouched.
|
|
19625
|
+
*/
|
|
19626
|
+
clearPageEdits = this.updater((state) => ({ ...state, pageOverrides: {}, pageOrder: {} }));
|
|
18667
19627
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
18668
19628
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorStore });
|
|
18669
19629
|
}
|
|
@@ -18688,6 +19648,19 @@ class DbxPdfMergeEditorStoreDirective {
|
|
|
18688
19648
|
_injectedConfig = inject(DBX_PDF_MERGE_EDITOR_CONFIG, { optional: true });
|
|
18689
19649
|
config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
|
|
18690
19650
|
outputSizeLimit = input(...(ngDevMode ? [undefined, { debugName: "outputSizeLimit" }] : /* istanbul ignore next */ []));
|
|
19651
|
+
/**
|
|
19652
|
+
* A previously-exported merged PDF to load into the store, for an app that fetches a stored document itself and wants the editor to open already populated — no picker involved.
|
|
19653
|
+
*
|
|
19654
|
+
* Imported once per distinct blob instance: re-emitting the same object does nothing, so a template re-render or an unrelated input change cannot re-import. Emitting a *different* blob imports again and replaces the entries, even if the bytes are identical.
|
|
19655
|
+
*
|
|
19656
|
+
* Watch {@link DbxPdfMergeEditorStore.importState$} for the outcome — a failure surfaces there rather than throwing.
|
|
19657
|
+
*/
|
|
19658
|
+
source = input(...(ngDevMode ? [undefined, { debugName: "source" }] : /* istanbul ignore next */ []));
|
|
19659
|
+
sourceConfig = input(...(ngDevMode ? [undefined, { debugName: "sourceConfig" }] : /* istanbul ignore next */ []));
|
|
19660
|
+
/**
|
|
19661
|
+
* The blob instance most recently handed to the store, so the same one is never imported twice.
|
|
19662
|
+
*/
|
|
19663
|
+
_importedSource;
|
|
18691
19664
|
effectiveErrorBytesSignal = computed(() => {
|
|
18692
19665
|
const direct = this.outputSizeLimit();
|
|
18693
19666
|
if (direct != null) {
|
|
@@ -18719,9 +19692,30 @@ class DbxPdfMergeEditorStoreDirective {
|
|
|
18719
19692
|
effect(() => {
|
|
18720
19693
|
this.store.setEncryptedHandling(this.config()?.encryptedHandling);
|
|
18721
19694
|
});
|
|
19695
|
+
// Same channel for pageEditing, so a dialog-hosted editor opens straight into page
|
|
19696
|
+
// editing mode when the host directive asked for it.
|
|
19697
|
+
effect(() => {
|
|
19698
|
+
this.store.setPageEditing(this.config()?.pageEditing);
|
|
19699
|
+
});
|
|
19700
|
+
// Same channel for the embedded manifest. Gated separately from pageEditing — a plain
|
|
19701
|
+
// slot merge benefits from recording which pages came from which slot too.
|
|
19702
|
+
effect(() => {
|
|
19703
|
+
this.store.setSidecar(this.config()?.sidecar);
|
|
19704
|
+
});
|
|
19705
|
+
// Loading a source is not idempotent the way the setters above are — it REPLACES the store's
|
|
19706
|
+
// entries — so this tracks only `source` and guards on instance identity. `sourceConfig` is
|
|
19707
|
+
// read untracked so changing it never re-imports; it is read at the moment a new blob arrives.
|
|
19708
|
+
effect(() => {
|
|
19709
|
+
const source = this.source();
|
|
19710
|
+
if (source != null && source !== this._importedSource) {
|
|
19711
|
+
this._importedSource = source;
|
|
19712
|
+
const sourceConfig = untracked(() => this.sourceConfig());
|
|
19713
|
+
void this.store.importMergedPdf({ source, expectedSlotIds: sourceConfig?.expectedSlotIds, allowWithoutSidecar: sourceConfig?.allowWithoutSidecar, fileName: sourceConfig?.fileName });
|
|
19714
|
+
}
|
|
19715
|
+
});
|
|
18722
19716
|
}
|
|
18723
19717
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorStoreDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
18724
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.11", type: DbxPdfMergeEditorStoreDirective, isStandalone: true, selector: "[dbxPdfMergeEditorStore]", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, outputSizeLimit: { classPropertyName: "outputSizeLimit", publicName: "outputSizeLimit", isSignal: true, isRequired: false, transformFunction: null } }, providers: [DbxPdfMergeEditorStore], exportAs: ["dbxPdfMergeEditorStore"], ngImport: i0 });
|
|
19718
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.11", type: DbxPdfMergeEditorStoreDirective, isStandalone: true, selector: "[dbxPdfMergeEditorStore]", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, outputSizeLimit: { classPropertyName: "outputSizeLimit", publicName: "outputSizeLimit", isSignal: true, isRequired: false, transformFunction: null }, source: { classPropertyName: "source", publicName: "source", isSignal: true, isRequired: false, transformFunction: null }, sourceConfig: { classPropertyName: "sourceConfig", publicName: "sourceConfig", isSignal: true, isRequired: false, transformFunction: null } }, providers: [DbxPdfMergeEditorStore], exportAs: ["dbxPdfMergeEditorStore"], ngImport: i0 });
|
|
18725
19719
|
}
|
|
18726
19720
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorStoreDirective, decorators: [{
|
|
18727
19721
|
type: Directive,
|
|
@@ -18731,10 +19725,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
18731
19725
|
providers: [DbxPdfMergeEditorStore],
|
|
18732
19726
|
exportAs: 'dbxPdfMergeEditorStore'
|
|
18733
19727
|
}]
|
|
18734
|
-
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], outputSizeLimit: [{ type: i0.Input, args: [{ isSignal: true, alias: "outputSizeLimit", required: false }] }] } });
|
|
19728
|
+
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], outputSizeLimit: [{ type: i0.Input, args: [{ isSignal: true, alias: "outputSizeLimit", required: false }] }], source: [{ type: i0.Input, args: [{ isSignal: true, alias: "source", required: false }] }], sourceConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "sourceConfig", required: false }] }] } });
|
|
18735
19729
|
|
|
18736
|
-
const PDF_ICON = 'picture_as_pdf';
|
|
18737
|
-
const IMAGE_ICON = 'image';
|
|
19730
|
+
const PDF_ICON$1 = 'picture_as_pdf';
|
|
19731
|
+
const IMAGE_ICON$1 = 'image';
|
|
18738
19732
|
const ERROR_ICON = 'error';
|
|
18739
19733
|
/**
|
|
18740
19734
|
* Single row inside the {@link DbxPdfMergeListComponent}: shows the file's icon, name, formatted size, status, drag handle, and a remove button. The component's template is itself a `cdkDrag` element so each row can be reordered inside the parent's `cdkDropList`.
|
|
@@ -18751,10 +19745,10 @@ class DbxPdfMergeEntryComponent {
|
|
|
18751
19745
|
icon = ERROR_ICON;
|
|
18752
19746
|
}
|
|
18753
19747
|
else if (entry.kind === 'pdf') {
|
|
18754
|
-
icon = PDF_ICON;
|
|
19748
|
+
icon = PDF_ICON$1;
|
|
18755
19749
|
}
|
|
18756
19750
|
else {
|
|
18757
|
-
icon = IMAGE_ICON;
|
|
19751
|
+
icon = IMAGE_ICON$1;
|
|
18758
19752
|
}
|
|
18759
19753
|
return icon;
|
|
18760
19754
|
}, ...(ngDevMode ? [{ debugName: "iconSignal" }] : /* istanbul ignore next */ []));
|
|
@@ -18829,116 +19823,385 @@ class DbxPdfMergeEntryComponent {
|
|
|
18829
19823
|
}
|
|
18830
19824
|
</div>
|
|
18831
19825
|
</div>
|
|
18832
|
-
@if (isValidatingSignal()) {
|
|
18833
|
-
<mat-progress-spinner mode="indeterminate" diameter="20"></mat-progress-spinner>
|
|
18834
|
-
}
|
|
18835
|
-
<button mat-icon-button type="button" class="dbx-pdf-merge-entry-remove" (click)="onRemove()" aria-label="Remove file">
|
|
18836
|
-
<mat-icon>close</mat-icon>
|
|
19826
|
+
@if (isValidatingSignal()) {
|
|
19827
|
+
<mat-progress-spinner mode="indeterminate" diameter="20"></mat-progress-spinner>
|
|
19828
|
+
}
|
|
19829
|
+
<button mat-icon-button type="button" class="dbx-pdf-merge-entry-remove" (click)="onRemove()" aria-label="Remove file">
|
|
19830
|
+
<mat-icon>close</mat-icon>
|
|
19831
|
+
</button>
|
|
19832
|
+
</div>
|
|
19833
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatProgressSpinnerModule }, { kind: "component", type: i3.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "mode", "value", "diameter", "strokeWidth"], exportAs: ["matProgressSpinner"] }, { kind: "directive", type: DbxChipDirective, selector: "dbx-chip", inputs: ["small", "block", "color", "display", "tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
19834
|
+
}
|
|
19835
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEntryComponent, decorators: [{
|
|
19836
|
+
type: Component,
|
|
19837
|
+
args: [{
|
|
19838
|
+
selector: 'dbx-pdf-merge-entry',
|
|
19839
|
+
template: `
|
|
19840
|
+
<div class="dbx-pdf-merge-entry-row" cdkDrag cdkDragLockAxis="y">
|
|
19841
|
+
<ng-template cdkDragPlaceholder>
|
|
19842
|
+
<div class="dbx-pdf-merge-entry-placeholder"></div>
|
|
19843
|
+
</ng-template>
|
|
19844
|
+
<button mat-icon-button type="button" cdkDragHandle class="dbx-pdf-merge-entry-handle" aria-label="Drag to reorder">
|
|
19845
|
+
<mat-icon>drag_indicator</mat-icon>
|
|
19846
|
+
</button>
|
|
19847
|
+
<mat-icon class="dbx-pdf-merge-entry-icon" [class.dbx-warn]="isErrorSignal()">{{ iconSignal() }}</mat-icon>
|
|
19848
|
+
<div class="dbx-pdf-merge-entry-info dbx-flex-fill-0">
|
|
19849
|
+
<div class="dbx-pdf-merge-entry-name dbx-text-truncate" [title]="entry().name">{{ entry().name }}</div>
|
|
19850
|
+
<div class="dbx-pdf-merge-entry-meta dbx-hint dbx-small">
|
|
19851
|
+
<span>{{ sizeSignal() }}</span>
|
|
19852
|
+
@if (isEncryptedSignal()) {
|
|
19853
|
+
<dbx-chip class="dbx-pdf-merge-entry-encrypted-chip" color="warn" [small]="true">
|
|
19854
|
+
<mat-icon class="dbx-pdf-merge-entry-encrypted-icon">lock</mat-icon>
|
|
19855
|
+
<span>Encrypted</span>
|
|
19856
|
+
</dbx-chip>
|
|
19857
|
+
}
|
|
19858
|
+
@if (compressionLabelSignal(); as compressionLabel) {
|
|
19859
|
+
<span class="dbx-pdf-merge-entry-compression">{{ compressionLabel }}</span>
|
|
19860
|
+
}
|
|
19861
|
+
@if (statusLabelSignal(); as label) {
|
|
19862
|
+
<span class="dbx-pdf-merge-entry-status" [class.dbx-warn]="isErrorSignal()">{{ label }}</span>
|
|
19863
|
+
}
|
|
19864
|
+
</div>
|
|
19865
|
+
</div>
|
|
19866
|
+
@if (isValidatingSignal()) {
|
|
19867
|
+
<mat-progress-spinner mode="indeterminate" diameter="20"></mat-progress-spinner>
|
|
19868
|
+
}
|
|
19869
|
+
<button mat-icon-button type="button" class="dbx-pdf-merge-entry-remove" (click)="onRemove()" aria-label="Remove file">
|
|
19870
|
+
<mat-icon>close</mat-icon>
|
|
19871
|
+
</button>
|
|
19872
|
+
</div>
|
|
19873
|
+
`,
|
|
19874
|
+
host: {
|
|
19875
|
+
class: 'dbx-pdf-merge-entry d-block',
|
|
19876
|
+
'[class.dbx-pdf-merge-entry--error]': 'isErrorSignal()',
|
|
19877
|
+
'[class.dbx-pdf-merge-entry--validating]': 'isValidatingSignal()',
|
|
19878
|
+
'[class.dbx-pdf-merge-entry--ignored]': 'isIgnoredSignal()',
|
|
19879
|
+
'[class.dbx-pdf-merge-entry--encrypted]': 'isEncryptedSignal()'
|
|
19880
|
+
},
|
|
19881
|
+
imports: [CdkDrag, CdkDragHandle, CdkDragPlaceholder, MatIconModule, MatButtonModule, MatProgressSpinnerModule, DbxChipDirective],
|
|
19882
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
19883
|
+
standalone: true
|
|
19884
|
+
}]
|
|
19885
|
+
}], propDecorators: { entry: [{ type: i0.Input, args: [{ isSignal: true, alias: "entry", required: true }] }] } });
|
|
19886
|
+
|
|
19887
|
+
/**
|
|
19888
|
+
* Renders the staged entries inside a CDK drop list. Each row is a {@link DbxPdfMergeEntryComponent} that hosts its own `cdkDrag`, so reordering events bubble up here as `cdkDropListDropped` and forward to the store's `moveEntry` updater.
|
|
19889
|
+
*/
|
|
19890
|
+
class DbxPdfMergeListComponent {
|
|
19891
|
+
store = inject(DbxPdfMergeEditorStore);
|
|
19892
|
+
entries$ = this.store.displayEntries$;
|
|
19893
|
+
onDrop(event) {
|
|
19894
|
+
this.store.moveEntry({ previousIndex: event.previousIndex, currentIndex: event.currentIndex });
|
|
19895
|
+
}
|
|
19896
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
19897
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergeListComponent, isStandalone: true, selector: "dbx-pdf-merge-list", host: { classAttribute: "dbx-pdf-merge-list" }, ngImport: i0, template: `
|
|
19898
|
+
@if (entries$ | async; as entries) {
|
|
19899
|
+
@if (entries.length === 0) {
|
|
19900
|
+
<div class="dbx-list-empty-content dbx-hint dbx-p4">No files added yet.</div>
|
|
19901
|
+
} @else {
|
|
19902
|
+
<div class="dbx-pdf-merge-list-items" cdkDropList (cdkDropListDropped)="onDrop($event)">
|
|
19903
|
+
@for (entry of entries; track entry.id) {
|
|
19904
|
+
<dbx-pdf-merge-entry [entry]="entry"></dbx-pdf-merge-entry>
|
|
19905
|
+
}
|
|
19906
|
+
</div>
|
|
19907
|
+
}
|
|
19908
|
+
}
|
|
19909
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "component", type: DbxPdfMergeEntryComponent, selector: "dbx-pdf-merge-entry", inputs: ["entry"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
19910
|
+
}
|
|
19911
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeListComponent, decorators: [{
|
|
19912
|
+
type: Component,
|
|
19913
|
+
args: [{
|
|
19914
|
+
selector: 'dbx-pdf-merge-list',
|
|
19915
|
+
template: `
|
|
19916
|
+
@if (entries$ | async; as entries) {
|
|
19917
|
+
@if (entries.length === 0) {
|
|
19918
|
+
<div class="dbx-list-empty-content dbx-hint dbx-p4">No files added yet.</div>
|
|
19919
|
+
} @else {
|
|
19920
|
+
<div class="dbx-pdf-merge-list-items" cdkDropList (cdkDropListDropped)="onDrop($event)">
|
|
19921
|
+
@for (entry of entries; track entry.id) {
|
|
19922
|
+
<dbx-pdf-merge-entry [entry]="entry"></dbx-pdf-merge-entry>
|
|
19923
|
+
}
|
|
19924
|
+
</div>
|
|
19925
|
+
}
|
|
19926
|
+
}
|
|
19927
|
+
`,
|
|
19928
|
+
host: {
|
|
19929
|
+
class: 'dbx-pdf-merge-list'
|
|
19930
|
+
},
|
|
19931
|
+
imports: [AsyncPipe, CdkDropList, DbxPdfMergeEntryComponent],
|
|
19932
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
19933
|
+
standalone: true
|
|
19934
|
+
}]
|
|
19935
|
+
}] });
|
|
19936
|
+
|
|
19937
|
+
const PDF_ICON = 'picture_as_pdf';
|
|
19938
|
+
const IMAGE_ICON = 'image';
|
|
19939
|
+
/**
|
|
19940
|
+
* Degrees added by one press of the rotate control.
|
|
19941
|
+
*/
|
|
19942
|
+
const ROTATION_STEP_DEGREES = 90;
|
|
19943
|
+
/**
|
|
19944
|
+
* PDF points per inch. PDF page dimensions are expressed in points; inches read better on a scanned document.
|
|
19945
|
+
*/
|
|
19946
|
+
const POINTS_PER_INCH = 72;
|
|
19947
|
+
const ROTATION_VALUES = [0, 90, 180, 270];
|
|
19948
|
+
/**
|
|
19949
|
+
* Single page row inside a {@link DbxPdfMergePageListComponent} group: shows which source file and page it came from, its dimensions and rotation, and controls to rotate it, mark it for removal, and drag it to a new position.
|
|
19950
|
+
*
|
|
19951
|
+
* The template's root element is itself a `cdkDrag`, matching {@link DbxPdfMergeEntryComponent}, so rows reorder inside their group's drop list.
|
|
19952
|
+
*/
|
|
19953
|
+
class DbxPdfMergePageComponent {
|
|
19954
|
+
store = inject(DbxPdfMergeEditorStore);
|
|
19955
|
+
page = input.required(...(ngDevMode ? [{ debugName: "page" }] : /* istanbul ignore next */ []));
|
|
19956
|
+
removedSignal = computed(() => this.page().removed, ...(ngDevMode ? [{ debugName: "removedSignal" }] : /* istanbul ignore next */ []));
|
|
19957
|
+
iconSignal = computed(() => (this.page().kind === 'pdf' ? PDF_ICON : IMAGE_ICON), ...(ngDevMode ? [{ debugName: "iconSignal" }] : /* istanbul ignore next */ []));
|
|
19958
|
+
removeLabelSignal = computed(() => (this.removedSignal() ? 'Restore page' : 'Mark page for removal'), ...(ngDevMode ? [{ debugName: "removeLabelSignal" }] : /* istanbul ignore next */ []));
|
|
19959
|
+
positionLabelSignal = computed(() => {
|
|
19960
|
+
const page = this.page();
|
|
19961
|
+
let label;
|
|
19962
|
+
if (page.kind === 'image') {
|
|
19963
|
+
label = 'Image';
|
|
19964
|
+
}
|
|
19965
|
+
else {
|
|
19966
|
+
label = `Page ${page.sourceIndex + 1} of ${page.sourcePageCount}`;
|
|
19967
|
+
}
|
|
19968
|
+
return label;
|
|
19969
|
+
}, ...(ngDevMode ? [{ debugName: "positionLabelSignal" }] : /* istanbul ignore next */ []));
|
|
19970
|
+
dimensionsLabelSignal = computed(() => {
|
|
19971
|
+
const { kind, meta } = this.page();
|
|
19972
|
+
let label;
|
|
19973
|
+
if (meta.width <= 0 || meta.height <= 0) {
|
|
19974
|
+
label = null;
|
|
19975
|
+
}
|
|
19976
|
+
else if (kind === 'image') {
|
|
19977
|
+
label = `${Math.round(meta.width)} × ${Math.round(meta.height)} px`;
|
|
19978
|
+
}
|
|
19979
|
+
else {
|
|
19980
|
+
label = `${(meta.width / POINTS_PER_INCH).toFixed(1)} × ${(meta.height / POINTS_PER_INCH).toFixed(1)} in`;
|
|
19981
|
+
}
|
|
19982
|
+
return label;
|
|
19983
|
+
}, ...(ngDevMode ? [{ debugName: "dimensionsLabelSignal" }] : /* istanbul ignore next */ []));
|
|
19984
|
+
rotationLabelSignal = computed(() => {
|
|
19985
|
+
const rotation = this.page().rotation;
|
|
19986
|
+
return rotation === 0 ? null : `${rotation}°`;
|
|
19987
|
+
}, ...(ngDevMode ? [{ debugName: "rotationLabelSignal" }] : /* istanbul ignore next */ []));
|
|
19988
|
+
onRotate() {
|
|
19989
|
+
const page = this.page();
|
|
19990
|
+
const next = ROTATION_VALUES[((page.rotation + ROTATION_STEP_DEGREES) / ROTATION_STEP_DEGREES) % ROTATION_VALUES.length];
|
|
19991
|
+
this.store.setPageRotation({ pageId: page.id, rotation: next });
|
|
19992
|
+
}
|
|
19993
|
+
onToggleRemoved() {
|
|
19994
|
+
const page = this.page();
|
|
19995
|
+
this.store.setPageRemoved({ pageId: page.id, removed: !page.removed });
|
|
19996
|
+
}
|
|
19997
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergePageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
19998
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergePageComponent, isStandalone: true, selector: "dbx-pdf-merge-page", inputs: { page: { classPropertyName: "page", publicName: "page", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "class.dbx-pdf-merge-page--removed": "removedSignal()" }, classAttribute: "dbx-pdf-merge-page d-block" }, ngImport: i0, template: `
|
|
19999
|
+
<div class="dbx-pdf-merge-page-row" cdkDrag cdkDragLockAxis="y">
|
|
20000
|
+
<ng-template cdkDragPlaceholder>
|
|
20001
|
+
<div class="dbx-pdf-merge-page-placeholder"></div>
|
|
20002
|
+
</ng-template>
|
|
20003
|
+
<button mat-icon-button type="button" cdkDragHandle class="dbx-pdf-merge-page-handle" aria-label="Drag to reorder page">
|
|
20004
|
+
<mat-icon>drag_indicator</mat-icon>
|
|
20005
|
+
</button>
|
|
20006
|
+
<mat-icon class="dbx-pdf-merge-page-icon">{{ iconSignal() }}</mat-icon>
|
|
20007
|
+
<div class="dbx-pdf-merge-page-info dbx-flex-fill-0">
|
|
20008
|
+
<div class="dbx-pdf-merge-page-name dbx-text-truncate" [title]="page().sourceName">{{ page().sourceName }}</div>
|
|
20009
|
+
<div class="dbx-pdf-merge-page-meta dbx-hint dbx-small">
|
|
20010
|
+
<span class="dbx-pdf-merge-page-position">{{ positionLabelSignal() }}</span>
|
|
20011
|
+
@if (dimensionsLabelSignal(); as dimensions) {
|
|
20012
|
+
<span class="dbx-pdf-merge-page-dimensions">{{ dimensions }}</span>
|
|
20013
|
+
}
|
|
20014
|
+
@if (rotationLabelSignal(); as rotation) {
|
|
20015
|
+
<dbx-chip class="dbx-pdf-merge-page-rotation-chip" [small]="true">
|
|
20016
|
+
<mat-icon class="dbx-pdf-merge-page-rotation-icon">rotate_right</mat-icon>
|
|
20017
|
+
<span>{{ rotation }}</span>
|
|
20018
|
+
</dbx-chip>
|
|
20019
|
+
}
|
|
20020
|
+
@if (removedSignal()) {
|
|
20021
|
+
<span class="dbx-pdf-merge-page-removed-label dbx-warn">Will be removed</span>
|
|
20022
|
+
}
|
|
20023
|
+
</div>
|
|
20024
|
+
</div>
|
|
20025
|
+
<button mat-icon-button type="button" class="dbx-pdf-merge-page-rotate" [disabled]="removedSignal()" (click)="onRotate()" aria-label="Rotate page 90 degrees">
|
|
20026
|
+
<mat-icon>rotate_right</mat-icon>
|
|
20027
|
+
</button>
|
|
20028
|
+
<button mat-icon-button type="button" class="dbx-pdf-merge-page-remove" (click)="onToggleRemoved()" [attr.aria-label]="removeLabelSignal()">
|
|
20029
|
+
<mat-icon>{{ removedSignal() ? 'undo' : 'close' }}</mat-icon>
|
|
18837
20030
|
</button>
|
|
18838
20031
|
</div>
|
|
18839
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "
|
|
20032
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: DbxChipDirective, selector: "dbx-chip", inputs: ["small", "block", "color", "display", "tone"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
18840
20033
|
}
|
|
18841
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type:
|
|
20034
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergePageComponent, decorators: [{
|
|
18842
20035
|
type: Component,
|
|
18843
20036
|
args: [{
|
|
18844
|
-
selector: 'dbx-pdf-merge-
|
|
20037
|
+
selector: 'dbx-pdf-merge-page',
|
|
18845
20038
|
template: `
|
|
18846
|
-
<div class="dbx-pdf-merge-
|
|
20039
|
+
<div class="dbx-pdf-merge-page-row" cdkDrag cdkDragLockAxis="y">
|
|
18847
20040
|
<ng-template cdkDragPlaceholder>
|
|
18848
|
-
<div class="dbx-pdf-merge-
|
|
20041
|
+
<div class="dbx-pdf-merge-page-placeholder"></div>
|
|
18849
20042
|
</ng-template>
|
|
18850
|
-
<button mat-icon-button type="button" cdkDragHandle class="dbx-pdf-merge-
|
|
20043
|
+
<button mat-icon-button type="button" cdkDragHandle class="dbx-pdf-merge-page-handle" aria-label="Drag to reorder page">
|
|
18851
20044
|
<mat-icon>drag_indicator</mat-icon>
|
|
18852
20045
|
</button>
|
|
18853
|
-
<mat-icon class="dbx-pdf-merge-
|
|
18854
|
-
<div class="dbx-pdf-merge-
|
|
18855
|
-
<div class="dbx-pdf-merge-
|
|
18856
|
-
<div class="dbx-pdf-merge-
|
|
18857
|
-
<span>{{
|
|
18858
|
-
@if (
|
|
18859
|
-
<
|
|
18860
|
-
<mat-icon class="dbx-pdf-merge-entry-encrypted-icon">lock</mat-icon>
|
|
18861
|
-
<span>Encrypted</span>
|
|
18862
|
-
</dbx-chip>
|
|
20046
|
+
<mat-icon class="dbx-pdf-merge-page-icon">{{ iconSignal() }}</mat-icon>
|
|
20047
|
+
<div class="dbx-pdf-merge-page-info dbx-flex-fill-0">
|
|
20048
|
+
<div class="dbx-pdf-merge-page-name dbx-text-truncate" [title]="page().sourceName">{{ page().sourceName }}</div>
|
|
20049
|
+
<div class="dbx-pdf-merge-page-meta dbx-hint dbx-small">
|
|
20050
|
+
<span class="dbx-pdf-merge-page-position">{{ positionLabelSignal() }}</span>
|
|
20051
|
+
@if (dimensionsLabelSignal(); as dimensions) {
|
|
20052
|
+
<span class="dbx-pdf-merge-page-dimensions">{{ dimensions }}</span>
|
|
18863
20053
|
}
|
|
18864
|
-
@if (
|
|
18865
|
-
<
|
|
20054
|
+
@if (rotationLabelSignal(); as rotation) {
|
|
20055
|
+
<dbx-chip class="dbx-pdf-merge-page-rotation-chip" [small]="true">
|
|
20056
|
+
<mat-icon class="dbx-pdf-merge-page-rotation-icon">rotate_right</mat-icon>
|
|
20057
|
+
<span>{{ rotation }}</span>
|
|
20058
|
+
</dbx-chip>
|
|
18866
20059
|
}
|
|
18867
|
-
@if (
|
|
18868
|
-
<span class="dbx-pdf-merge-
|
|
20060
|
+
@if (removedSignal()) {
|
|
20061
|
+
<span class="dbx-pdf-merge-page-removed-label dbx-warn">Will be removed</span>
|
|
18869
20062
|
}
|
|
18870
20063
|
</div>
|
|
18871
20064
|
</div>
|
|
18872
|
-
|
|
18873
|
-
<mat-
|
|
18874
|
-
|
|
18875
|
-
<button mat-icon-button type="button" class="dbx-pdf-merge-
|
|
18876
|
-
<mat-icon>close</mat-icon>
|
|
20065
|
+
<button mat-icon-button type="button" class="dbx-pdf-merge-page-rotate" [disabled]="removedSignal()" (click)="onRotate()" aria-label="Rotate page 90 degrees">
|
|
20066
|
+
<mat-icon>rotate_right</mat-icon>
|
|
20067
|
+
</button>
|
|
20068
|
+
<button mat-icon-button type="button" class="dbx-pdf-merge-page-remove" (click)="onToggleRemoved()" [attr.aria-label]="removeLabelSignal()">
|
|
20069
|
+
<mat-icon>{{ removedSignal() ? 'undo' : 'close' }}</mat-icon>
|
|
18877
20070
|
</button>
|
|
18878
20071
|
</div>
|
|
18879
20072
|
`,
|
|
18880
20073
|
host: {
|
|
18881
|
-
class: 'dbx-pdf-merge-
|
|
18882
|
-
'[class.dbx-pdf-merge-
|
|
18883
|
-
'[class.dbx-pdf-merge-entry--validating]': 'isValidatingSignal()',
|
|
18884
|
-
'[class.dbx-pdf-merge-entry--ignored]': 'isIgnoredSignal()',
|
|
18885
|
-
'[class.dbx-pdf-merge-entry--encrypted]': 'isEncryptedSignal()'
|
|
20074
|
+
class: 'dbx-pdf-merge-page d-block',
|
|
20075
|
+
'[class.dbx-pdf-merge-page--removed]': 'removedSignal()'
|
|
18886
20076
|
},
|
|
18887
|
-
imports: [CdkDrag, CdkDragHandle, CdkDragPlaceholder, MatIconModule, MatButtonModule,
|
|
20077
|
+
imports: [CdkDrag, CdkDragHandle, CdkDragPlaceholder, MatIconModule, MatButtonModule, DbxChipDirective],
|
|
18888
20078
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
18889
20079
|
standalone: true
|
|
18890
20080
|
}]
|
|
18891
|
-
}], propDecorators: {
|
|
20081
|
+
}], propDecorators: { page: [{ type: i0.Input, args: [{ isSignal: true, alias: "page", required: true }] }] } });
|
|
18892
20082
|
|
|
18893
20083
|
/**
|
|
18894
|
-
*
|
|
20084
|
+
* Message shown for an entry that is not encrypted but still could not be parsed into pages.
|
|
18895
20085
|
*/
|
|
18896
|
-
|
|
20086
|
+
const UNREADABLE_PAGES_MESSAGE = 'Pages could not be read.';
|
|
20087
|
+
/**
|
|
20088
|
+
* Renders the editor's page plan while page editing is enabled, one CDK drop list per group.
|
|
20089
|
+
*
|
|
20090
|
+
* Separate, unconnected drop lists are what confine reordering to a single slot: CDK will not let a drag cross into a list it is not connected to, so the rule needs no guard code. Bind {@link slotId} to render only that slot's group — used by {@link DbxPdfMergeEditorFileUploadComponent} to show its own pages inline. Leave it unbound to render every group.
|
|
20091
|
+
*
|
|
20092
|
+
* Entries that could not be expanded into pages (encrypted, or unparseable) are listed separately below the groups rather than silently omitted.
|
|
20093
|
+
*/
|
|
20094
|
+
class DbxPdfMergePageListComponent {
|
|
18897
20095
|
store = inject(DbxPdfMergeEditorStore);
|
|
18898
|
-
|
|
18899
|
-
|
|
18900
|
-
|
|
20096
|
+
/**
|
|
20097
|
+
* Renders only this slot's group when bound. Leave unbound to render every group.
|
|
20098
|
+
*/
|
|
20099
|
+
slotId = input(...(ngDevMode ? [undefined, { debugName: "slotId" }] : /* istanbul ignore next */ []));
|
|
20100
|
+
/**
|
|
20101
|
+
* Whether to show each group's slot name above its pages. Defaults to `true` when rendering every group and `false` when scoped to one slot, where the slot already has its own heading.
|
|
20102
|
+
*/
|
|
20103
|
+
showGroupLabels = input(...(ngDevMode ? [undefined, { debugName: "showGroupLabels" }] : /* istanbul ignore next */ []));
|
|
20104
|
+
_slotId$ = toObservable(this.slotId);
|
|
20105
|
+
groups$ = this._slotId$.pipe(switchMap((slotId) => {
|
|
20106
|
+
let groups$;
|
|
20107
|
+
if (slotId == null) {
|
|
20108
|
+
groups$ = this.store.pageGroups$;
|
|
20109
|
+
}
|
|
20110
|
+
else {
|
|
20111
|
+
const groupKey = pdfMergePageGroupKeyForSlotId(slotId);
|
|
20112
|
+
groups$ = this.store.pagesForSlotId$(slotId).pipe(map((pages) => (pages.length === 0 ? [] : [{ groupKey, slotId, pages }])));
|
|
20113
|
+
}
|
|
20114
|
+
return groups$;
|
|
20115
|
+
}), shareReplay(1));
|
|
20116
|
+
unexpandable$ = combineLatest([this.store.unexpandableEntries$, this._slotId$]).pipe(map(([entries, slotId]) => (slotId == null ? entries : entries.filter((entry) => entry.slotId === slotId))), shareReplay(1));
|
|
20117
|
+
groupsSignal = toSignal(this.groups$, { initialValue: [] });
|
|
20118
|
+
unexpandableSignal = toSignal(this.unexpandable$, { initialValue: [] });
|
|
20119
|
+
showGroupLabelsSignal = computed(() => {
|
|
20120
|
+
const slotId = this.slotId();
|
|
20121
|
+
return this.showGroupLabels() ?? slotId == null;
|
|
20122
|
+
}, ...(ngDevMode ? [{ debugName: "showGroupLabelsSignal" }] : /* istanbul ignore next */ []));
|
|
20123
|
+
labelForGroup(group) {
|
|
20124
|
+
return this.showGroupLabelsSignal() ? group.slotId : null;
|
|
20125
|
+
}
|
|
20126
|
+
messageForUnexpandable(entry) {
|
|
20127
|
+
return entry.encrypted ? DBX_PDF_MERGE_ENCRYPTED_NOT_EDITABLE_MESSAGE : UNREADABLE_PAGES_MESSAGE;
|
|
20128
|
+
}
|
|
20129
|
+
onDrop(group, event) {
|
|
20130
|
+
this.store.movePageWithinGroup({
|
|
20131
|
+
groupKey: group.groupKey,
|
|
20132
|
+
pageIds: group.pages.map((page) => page.id),
|
|
20133
|
+
previousIndex: event.previousIndex,
|
|
20134
|
+
currentIndex: event.currentIndex
|
|
20135
|
+
});
|
|
18901
20136
|
}
|
|
18902
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type:
|
|
18903
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type:
|
|
18904
|
-
@if (
|
|
18905
|
-
@if (
|
|
18906
|
-
<div class="dbx-list-empty-content dbx-hint dbx-p4">No
|
|
20137
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergePageListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
20138
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergePageListComponent, isStandalone: true, selector: "dbx-pdf-merge-page-list", inputs: { slotId: { classPropertyName: "slotId", publicName: "slotId", isSignal: true, isRequired: false, transformFunction: null }, showGroupLabels: { classPropertyName: "showGroupLabels", publicName: "showGroupLabels", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "dbx-pdf-merge-page-list d-block" }, ngImport: i0, template: `
|
|
20139
|
+
@if (groupsSignal(); as groups) {
|
|
20140
|
+
@if (groups.length === 0 && unexpandableSignal().length === 0) {
|
|
20141
|
+
<div class="dbx-list-empty-content dbx-hint dbx-p4">No pages yet.</div>
|
|
18907
20142
|
} @else {
|
|
18908
|
-
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
20143
|
+
@for (group of groups; track group.groupKey) {
|
|
20144
|
+
<div class="dbx-pdf-merge-page-group">
|
|
20145
|
+
@if (labelForGroup(group); as label) {
|
|
20146
|
+
<div class="dbx-pdf-merge-page-group-label dbx-hint dbx-small">{{ label }}</div>
|
|
20147
|
+
}
|
|
20148
|
+
<div class="dbx-pdf-merge-page-group-items" cdkDropList (cdkDropListDropped)="onDrop(group, $event)">
|
|
20149
|
+
@for (page of group.pages; track page.id) {
|
|
20150
|
+
<dbx-pdf-merge-page [page]="page"></dbx-pdf-merge-page>
|
|
20151
|
+
}
|
|
20152
|
+
</div>
|
|
20153
|
+
</div>
|
|
20154
|
+
}
|
|
18913
20155
|
}
|
|
18914
20156
|
}
|
|
18915
|
-
|
|
20157
|
+
@for (entry of unexpandableSignal(); track entry.id) {
|
|
20158
|
+
<div class="dbx-pdf-merge-page-unexpandable">
|
|
20159
|
+
<mat-icon class="dbx-pdf-merge-page-unexpandable-icon">lock</mat-icon>
|
|
20160
|
+
<span class="dbx-pdf-merge-page-unexpandable-name dbx-text-truncate" [title]="entry.name">{{ entry.name }}</span>
|
|
20161
|
+
<span class="dbx-hint dbx-small">{{ messageForUnexpandable(entry) }}</span>
|
|
20162
|
+
</div>
|
|
20163
|
+
}
|
|
20164
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: DbxPdfMergePageComponent, selector: "dbx-pdf-merge-page", inputs: ["page"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
18916
20165
|
}
|
|
18917
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type:
|
|
20166
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergePageListComponent, decorators: [{
|
|
18918
20167
|
type: Component,
|
|
18919
20168
|
args: [{
|
|
18920
|
-
selector: 'dbx-pdf-merge-list',
|
|
20169
|
+
selector: 'dbx-pdf-merge-page-list',
|
|
18921
20170
|
template: `
|
|
18922
|
-
@if (
|
|
18923
|
-
@if (
|
|
18924
|
-
<div class="dbx-list-empty-content dbx-hint dbx-p4">No
|
|
20171
|
+
@if (groupsSignal(); as groups) {
|
|
20172
|
+
@if (groups.length === 0 && unexpandableSignal().length === 0) {
|
|
20173
|
+
<div class="dbx-list-empty-content dbx-hint dbx-p4">No pages yet.</div>
|
|
18925
20174
|
} @else {
|
|
18926
|
-
|
|
18927
|
-
|
|
18928
|
-
|
|
18929
|
-
|
|
18930
|
-
|
|
20175
|
+
@for (group of groups; track group.groupKey) {
|
|
20176
|
+
<div class="dbx-pdf-merge-page-group">
|
|
20177
|
+
@if (labelForGroup(group); as label) {
|
|
20178
|
+
<div class="dbx-pdf-merge-page-group-label dbx-hint dbx-small">{{ label }}</div>
|
|
20179
|
+
}
|
|
20180
|
+
<div class="dbx-pdf-merge-page-group-items" cdkDropList (cdkDropListDropped)="onDrop(group, $event)">
|
|
20181
|
+
@for (page of group.pages; track page.id) {
|
|
20182
|
+
<dbx-pdf-merge-page [page]="page"></dbx-pdf-merge-page>
|
|
20183
|
+
}
|
|
20184
|
+
</div>
|
|
20185
|
+
</div>
|
|
20186
|
+
}
|
|
18931
20187
|
}
|
|
18932
20188
|
}
|
|
20189
|
+
@for (entry of unexpandableSignal(); track entry.id) {
|
|
20190
|
+
<div class="dbx-pdf-merge-page-unexpandable">
|
|
20191
|
+
<mat-icon class="dbx-pdf-merge-page-unexpandable-icon">lock</mat-icon>
|
|
20192
|
+
<span class="dbx-pdf-merge-page-unexpandable-name dbx-text-truncate" [title]="entry.name">{{ entry.name }}</span>
|
|
20193
|
+
<span class="dbx-hint dbx-small">{{ messageForUnexpandable(entry) }}</span>
|
|
20194
|
+
</div>
|
|
20195
|
+
}
|
|
18933
20196
|
`,
|
|
18934
20197
|
host: {
|
|
18935
|
-
class: 'dbx-pdf-merge-list'
|
|
20198
|
+
class: 'dbx-pdf-merge-page-list d-block'
|
|
18936
20199
|
},
|
|
18937
|
-
imports: [
|
|
20200
|
+
imports: [CdkDropList, MatIconModule, DbxPdfMergePageComponent],
|
|
18938
20201
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
18939
20202
|
standalone: true
|
|
18940
20203
|
}]
|
|
18941
|
-
}] });
|
|
20204
|
+
}], propDecorators: { slotId: [{ type: i0.Input, args: [{ isSignal: true, alias: "slotId", required: false }] }], showGroupLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showGroupLabels", required: false }] }] } });
|
|
18942
20205
|
|
|
18943
20206
|
/**
|
|
18944
20207
|
* Renders a PDF in a fixed-height embed container. Accepts either a URL or a `Blob` source.
|
|
@@ -19007,6 +20270,21 @@ const DEFAULT_DOWNLOAD_BUTTON = {
|
|
|
19007
20270
|
display: { icon: 'download', text: 'Download' },
|
|
19008
20271
|
style: { type: 'stroked' }
|
|
19009
20272
|
};
|
|
20273
|
+
const DEFAULT_CLEAR_CONFIRM = {
|
|
20274
|
+
title: 'Clear all files?',
|
|
20275
|
+
prompt: 'Every file added to this editor will be removed, along with any page edits made to them.',
|
|
20276
|
+
confirmText: 'Clear',
|
|
20277
|
+
cancelText: 'Cancel'
|
|
20278
|
+
};
|
|
20279
|
+
/**
|
|
20280
|
+
* Shown in place of {@link DEFAULT_CLEAR_CONFIRM} when the editor has a programmatic baseline, since Clear resets to that document rather than emptying.
|
|
20281
|
+
*/
|
|
20282
|
+
const DEFAULT_RESTORE_CONFIRM = {
|
|
20283
|
+
title: 'Reset to the imported document?',
|
|
20284
|
+
prompt: 'Every change made here — added files, page edits, removed sections — will be discarded and the imported document reloaded as it first arrived.',
|
|
20285
|
+
confirmText: 'Reset',
|
|
20286
|
+
cancelText: 'Cancel'
|
|
20287
|
+
};
|
|
19010
20288
|
class DbxPdfMergeEditorComponent {
|
|
19011
20289
|
store = inject(DbxPdfMergeEditorStore);
|
|
19012
20290
|
_matDialog = inject(MatDialog);
|
|
@@ -19026,6 +20304,8 @@ class DbxPdfMergeEditorComponent {
|
|
|
19026
20304
|
downloadButton = input(...(ngDevMode ? [undefined, { debugName: "downloadButton" }] : /* istanbul ignore next */ []));
|
|
19027
20305
|
showAddFiles = input(...(ngDevMode ? [undefined, { debugName: "showAddFiles" }] : /* istanbul ignore next */ []));
|
|
19028
20306
|
showFileList = input(...(ngDevMode ? [undefined, { debugName: "showFileList" }] : /* istanbul ignore next */ []));
|
|
20307
|
+
pageEditing = input(...(ngDevMode ? [undefined, { debugName: "pageEditing" }] : /* istanbul ignore next */ []));
|
|
20308
|
+
sidecar = input(...(ngDevMode ? [undefined, { debugName: "sidecar" }] : /* istanbul ignore next */ []));
|
|
19029
20309
|
/**
|
|
19030
20310
|
* Bundles every editor option into one object (see {@link DbxPdfMergeEditorConfig}). Individual inputs override the matching field here, which in turn overrides the workspace-wide {@link DBX_PDF_MERGE_EDITOR_CONFIG} token.
|
|
19031
20311
|
*/
|
|
@@ -19039,6 +20319,11 @@ class DbxPdfMergeEditorComponent {
|
|
|
19039
20319
|
* Store-level encryption handling default pushed by {@link DbxPdfMergeEditorStoreDirective}. Resolved between the editor's own `config` input and the workspace-wide token in {@link effectiveConfigSignal}.
|
|
19040
20320
|
*/
|
|
19041
20321
|
storeEncryptedHandlingSignal = toSignal(this.store.encryptedHandling$, { initialValue: undefined });
|
|
20322
|
+
/**
|
|
20323
|
+
* Store-level page-editing and sidecar defaults pushed by {@link DbxPdfMergeEditorStoreDirective}. These read the store's raw (undefaulted) settings so an unset store value still falls through to the workspace-wide token in {@link effectiveConfigSignal}.
|
|
20324
|
+
*/
|
|
20325
|
+
storePageEditingSignal = toSignal(this.store.pageEditingSetting$, { initialValue: undefined });
|
|
20326
|
+
storeSidecarSignal = toSignal(this.store.sidecarSetting$, { initialValue: undefined });
|
|
19042
20327
|
/**
|
|
19043
20328
|
* Merged config — the editor's own `config` input wins over the store-level default (for `imageCompression`), which in turn wins over the workspace-wide token. The individual inputs are resolved on top of this object in the per-field `*Signal` computeds below.
|
|
19044
20329
|
*/
|
|
@@ -19047,6 +20332,8 @@ class DbxPdfMergeEditorComponent {
|
|
|
19047
20332
|
const fromToken = this._injectedConfig;
|
|
19048
20333
|
const storeImageCompression = this.storeImageCompressionSignal();
|
|
19049
20334
|
const storeEncryptedHandling = this.storeEncryptedHandlingSignal();
|
|
20335
|
+
const storePageEditing = this.storePageEditingSignal();
|
|
20336
|
+
const storeSidecar = this.storeSidecarSignal();
|
|
19050
20337
|
return {
|
|
19051
20338
|
imageCompression: fromInput?.imageCompression ?? storeImageCompression ?? fromToken?.imageCompression ?? null,
|
|
19052
20339
|
outputSizeLimits: fromInput?.outputSizeLimits ?? fromToken?.outputSizeLimits ?? null,
|
|
@@ -19058,10 +20345,44 @@ class DbxPdfMergeEditorComponent {
|
|
|
19058
20345
|
downloadButton: fromInput?.downloadButton ?? fromToken?.downloadButton,
|
|
19059
20346
|
showAddFiles: fromInput?.showAddFiles ?? fromToken?.showAddFiles,
|
|
19060
20347
|
showFileList: fromInput?.showFileList ?? fromToken?.showFileList,
|
|
19061
|
-
encryptedHandling: fromInput?.encryptedHandling ?? storeEncryptedHandling ?? fromToken?.encryptedHandling ?? DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING
|
|
20348
|
+
encryptedHandling: fromInput?.encryptedHandling ?? storeEncryptedHandling ?? fromToken?.encryptedHandling ?? DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING,
|
|
20349
|
+
pageEditing: fromInput?.pageEditing ?? storePageEditing ?? fromToken?.pageEditing ?? DEFAULT_DBX_PDF_MERGE_PAGE_EDITING,
|
|
20350
|
+
sidecar: fromInput?.sidecar ?? storeSidecar ?? fromToken?.sidecar ?? DEFAULT_DBX_PDF_MERGE_SIDECAR,
|
|
20351
|
+
clearConfirm: fromInput?.clearConfirm ?? fromToken?.clearConfirm,
|
|
20352
|
+
restoreImportOnClear: fromInput?.restoreImportOnClear ?? fromToken?.restoreImportOnClear ?? DEFAULT_DBX_PDF_MERGE_RESTORE_IMPORT_ON_CLEAR
|
|
19062
20353
|
};
|
|
19063
20354
|
}, ...(ngDevMode ? [{ debugName: "effectiveConfigSignal" }] : /* istanbul ignore next */ []));
|
|
20355
|
+
restoreImportOnClearSignal = computed(() => this.effectiveConfigSignal().restoreImportOnClear ?? DEFAULT_DBX_PDF_MERGE_RESTORE_IMPORT_ON_CLEAR, ...(ngDevMode ? [{ debugName: "restoreImportOnClearSignal" }] : /* istanbul ignore next */ []));
|
|
20356
|
+
/**
|
|
20357
|
+
* Whether Clear will reset to a programmatic baseline instead of emptying. Only affects the confirmation's wording — {@link DbxPdfMergeEditorStore.clearEntries} makes the actual decision, and re-reads the store at the moment it runs.
|
|
20358
|
+
*/
|
|
20359
|
+
clearRestoresImportSignal = computed(() => {
|
|
20360
|
+
// Tracks the entry count so the wording is re-evaluated as the editor's contents change; the
|
|
20361
|
+
// store's restore point is a plain field rather than a stream (nothing else needs to observe it).
|
|
20362
|
+
this.entryCountSignal();
|
|
20363
|
+
return this.restoreImportOnClearSignal() && this.store.hasRestorableImport();
|
|
20364
|
+
}, ...(ngDevMode ? [{ debugName: "clearRestoresImportSignal" }] : /* istanbul ignore next */ []));
|
|
20365
|
+
/**
|
|
20366
|
+
* Config for the confirmation shown before the footer Clear button empties the editor. `clearConfirm` overrides any field of the default; `autoConfirm: true` restores the pre-confirmation one-click behavior.
|
|
20367
|
+
*
|
|
20368
|
+
* The default copy tells the truth about which of the two things Clear is about to do — reset to the app-supplied document, or remove everything.
|
|
20369
|
+
*/
|
|
20370
|
+
clearConfirmSignal = computed(() => {
|
|
20371
|
+
const base = this.clearRestoresImportSignal() ? DEFAULT_RESTORE_CONFIRM : DEFAULT_CLEAR_CONFIRM;
|
|
20372
|
+
return { ...base, ...this.effectiveConfigSignal().clearConfirm };
|
|
20373
|
+
}, ...(ngDevMode ? [{ debugName: "clearConfirmSignal" }] : /* istanbul ignore next */ []));
|
|
19064
20374
|
encryptedHandlingSignal = computed(() => this.effectiveConfigSignal().encryptedHandling ?? DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING, ...(ngDevMode ? [{ debugName: "encryptedHandlingSignal" }] : /* istanbul ignore next */ []));
|
|
20375
|
+
/**
|
|
20376
|
+
* Whether page editing is active. When `false` the editor renders its original file list and no page machinery is instantiated at all.
|
|
20377
|
+
*/
|
|
20378
|
+
pageEditingSignal = computed(() => {
|
|
20379
|
+
const config = this.effectiveConfigSignal();
|
|
20380
|
+
return this.pageEditing() ?? config.pageEditing ?? DEFAULT_DBX_PDF_MERGE_PAGE_EDITING;
|
|
20381
|
+
}, ...(ngDevMode ? [{ debugName: "pageEditingSignal" }] : /* istanbul ignore next */ []));
|
|
20382
|
+
sidecarSignal = computed(() => {
|
|
20383
|
+
const config = this.effectiveConfigSignal();
|
|
20384
|
+
return this.sidecar() ?? config.sidecar ?? DEFAULT_DBX_PDF_MERGE_SIDECAR;
|
|
20385
|
+
}, ...(ngDevMode ? [{ debugName: "sidecarSignal" }] : /* istanbul ignore next */ []));
|
|
19065
20386
|
imageCompressionConfigSignal = computed(() => this.effectiveConfigSignal().imageCompression, ...(ngDevMode ? [{ debugName: "imageCompressionConfigSignal" }] : /* istanbul ignore next */ []));
|
|
19066
20387
|
outputSizeLimitsSignal = computed(() => this.effectiveConfigSignal().outputSizeLimits, ...(ngDevMode ? [{ debugName: "outputSizeLimitsSignal" }] : /* istanbul ignore next */ []));
|
|
19067
20388
|
// Per-field resolution: dedicated input → config (input/token) → default. The config signal is
|
|
@@ -19102,6 +20423,10 @@ class DbxPdfMergeEditorComponent {
|
|
|
19102
20423
|
errorBytesSignal = computed(() => this.outputSizeLimitsSignal()?.errorBytes, ...(ngDevMode ? [{ debugName: "errorBytesSignal" }] : /* istanbul ignore next */ []));
|
|
19103
20424
|
hasReadyEntriesSignal = toSignal(this.store.hasReadyEntries$, { initialValue: false });
|
|
19104
20425
|
entryCountSignal = toSignal(this.store.entryCount$, { initialValue: 0 });
|
|
20426
|
+
/**
|
|
20427
|
+
* Number of pages that will actually reach the output — the page plan minus anything marked for removal. Only surfaced while page editing is enabled.
|
|
20428
|
+
*/
|
|
20429
|
+
mergeablePageCountSignal = toSignal(this.store.mergeablePageCount$, { initialValue: 0 });
|
|
19105
20430
|
/**
|
|
19106
20431
|
* Mirrors {@link DbxPdfMergeEditorStore.focusActive$} — `true` while `encryptedHandling === 'focus'` and at least one ready encrypted entry exists. Drives the encrypted-PDF focus banner.
|
|
19107
20432
|
*/
|
|
@@ -19171,6 +20496,14 @@ class DbxPdfMergeEditorComponent {
|
|
|
19171
20496
|
const handling = this.encryptedHandlingSignal();
|
|
19172
20497
|
this.store.setEncryptedHandling(handling);
|
|
19173
20498
|
});
|
|
20499
|
+
effect(() => {
|
|
20500
|
+
const pageEditing = this.pageEditingSignal();
|
|
20501
|
+
this.store.setPageEditing(pageEditing);
|
|
20502
|
+
});
|
|
20503
|
+
effect(() => {
|
|
20504
|
+
const sidecar = this.sidecarSignal();
|
|
20505
|
+
this.store.setSidecar(sidecar);
|
|
20506
|
+
});
|
|
19174
20507
|
}
|
|
19175
20508
|
async onFiles(event) {
|
|
19176
20509
|
const accepted = event.matchResult.accepted;
|
|
@@ -19184,9 +20517,20 @@ class DbxPdfMergeEditorComponent {
|
|
|
19184
20517
|
this.store.addFiles({ entries: filtered });
|
|
19185
20518
|
}
|
|
19186
20519
|
}
|
|
20520
|
+
/**
|
|
20521
|
+
* Clears the editor, resetting to a programmatic baseline when there is one. See {@link DbxPdfMergeEditorStore.clearEntries}.
|
|
20522
|
+
*
|
|
20523
|
+
* @returns The clear outcome, so a programmatic caller can tell a reset from an empty.
|
|
20524
|
+
*/
|
|
19187
20525
|
onClear() {
|
|
19188
|
-
this.store.
|
|
20526
|
+
return this.store.clearEntries({ restoreImport: this.restoreImportOnClearSignal() });
|
|
19189
20527
|
}
|
|
20528
|
+
/**
|
|
20529
|
+
* Runs as the handler of the footer Clear button's own `dbxAction`, so the `dbxActionConfirm` on that action is what stands between a misclick and the whole entry list. Delegates to {@link onClear} so a programmatic clear and the button share one path.
|
|
20530
|
+
*
|
|
20531
|
+
* @returns An observable the action completes on — a restore re-parses the document, so this is not instantaneous.
|
|
20532
|
+
*/
|
|
20533
|
+
handleClear = () => from(this.onClear()).pipe(map(() => undefined));
|
|
19190
20534
|
onPreview() {
|
|
19191
20535
|
if (!this.canMergeSignal()) {
|
|
19192
20536
|
return;
|
|
@@ -19213,18 +20557,20 @@ class DbxPdfMergeEditorComponent {
|
|
|
19213
20557
|
});
|
|
19214
20558
|
}
|
|
19215
20559
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
19216
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergeEditorComponent, isStandalone: true, selector: "dbx-pdf-merge-editor", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, fileName: { classPropertyName: "fileName", publicName: "fileName", isSignal: true, isRequired: false, transformFunction: null }, showDownloadButton: { classPropertyName: "showDownloadButton", publicName: "showDownloadButton", isSignal: true, isRequired: false, transformFunction: null }, showPreviewButton: { classPropertyName: "showPreviewButton", publicName: "showPreviewButton", isSignal: true, isRequired: false, transformFunction: null }, downloadButton: { classPropertyName: "downloadButton", publicName: "downloadButton", isSignal: true, isRequired: false, transformFunction: null }, showAddFiles: { classPropertyName: "showAddFiles", publicName: "showAddFiles", isSignal: true, isRequired: false, transformFunction: null }, showFileList: { classPropertyName: "showFileList", publicName: "showFileList", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { entriesChanged: "entriesChanged" }, host: { classAttribute: "dbx-pdf-merge-editor d-block" }, ngImport: i0, template: "@if (showAddFilesSignal()) {\n <div class=\"dbx-pdf-merge-editor-upload d-block dbx-mb3\">\n <dbx-file-upload [accept]=\"acceptSignal()\" [multiple]=\"multipleSignal()\" (filesChanged)=\"onFiles($event)\" [hint]=\"'Drop PDFs or images here, or click to browse'\" [text]=\"'Add files'\" icon=\"upload_file\"></dbx-file-upload>\n </div>\n}\n<ng-content></ng-content>\n@if (showFileListSignal()) {\n <dbx-pdf-merge-list></dbx-pdf-merge-list>\n}\n@if (focusActiveSignal()) {\n <div class=\"dbx-pdf-merge-editor-encrypted-banner\">\n <mat-icon>lock</mat-icon>\n <span>Encrypted PDF detected \u2014 only the encrypted file will be used; other files are ignored.</span>\n </div>\n}\n@switch (outputSizeStateSignal()) {\n @case ('warn') {\n <div class=\"dbx-pdf-merge-editor-size-warning\">\n <mat-icon>warning</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }} \u2014 above the recommended {{ formattedWarnLimitSignal() }} limit.</span>\n </div>\n }\n @case ('error') {\n <div class=\"dbx-pdf-merge-editor-size-error\">\n <mat-icon>error</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }}, exceeds the {{ formattedErrorLimitSignal() }} limit. Remove or compress files to continue.</span>\n </div>\n }\n}\n<div class=\"dbx-pdf-merge-editor-actions\">\n <span class=\"dbx-hint dbx-small\">{{ entryCountSignal() }} file(s)</span>\n <span class=\"dbx-
|
|
20560
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergeEditorComponent, isStandalone: true, selector: "dbx-pdf-merge-editor", inputs: { accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, fileName: { classPropertyName: "fileName", publicName: "fileName", isSignal: true, isRequired: false, transformFunction: null }, showDownloadButton: { classPropertyName: "showDownloadButton", publicName: "showDownloadButton", isSignal: true, isRequired: false, transformFunction: null }, showPreviewButton: { classPropertyName: "showPreviewButton", publicName: "showPreviewButton", isSignal: true, isRequired: false, transformFunction: null }, downloadButton: { classPropertyName: "downloadButton", publicName: "downloadButton", isSignal: true, isRequired: false, transformFunction: null }, showAddFiles: { classPropertyName: "showAddFiles", publicName: "showAddFiles", isSignal: true, isRequired: false, transformFunction: null }, showFileList: { classPropertyName: "showFileList", publicName: "showFileList", isSignal: true, isRequired: false, transformFunction: null }, pageEditing: { classPropertyName: "pageEditing", publicName: "pageEditing", isSignal: true, isRequired: false, transformFunction: null }, sidecar: { classPropertyName: "sidecar", publicName: "sidecar", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { entriesChanged: "entriesChanged" }, host: { classAttribute: "dbx-pdf-merge-editor d-block" }, ngImport: i0, template: "@if (showAddFilesSignal()) {\n <div class=\"dbx-pdf-merge-editor-upload d-block dbx-mb3\">\n <dbx-file-upload [accept]=\"acceptSignal()\" [multiple]=\"multipleSignal()\" (filesChanged)=\"onFiles($event)\" [hint]=\"'Drop PDFs or images here, or click to browse'\" [text]=\"'Add files'\" icon=\"upload_file\"></dbx-file-upload>\n </div>\n}\n<ng-content></ng-content>\n@if (showFileListSignal()) {\n @if (pageEditingSignal()) {\n <dbx-pdf-merge-page-list></dbx-pdf-merge-page-list>\n } @else {\n <dbx-pdf-merge-list></dbx-pdf-merge-list>\n }\n}\n@if (focusActiveSignal()) {\n <div class=\"dbx-pdf-merge-editor-encrypted-banner\">\n <mat-icon>lock</mat-icon>\n <span>Encrypted PDF detected \u2014 only the encrypted file will be used; other files are ignored.</span>\n </div>\n}\n@switch (outputSizeStateSignal()) {\n @case ('warn') {\n <div class=\"dbx-pdf-merge-editor-size-warning\">\n <mat-icon>warning</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }} \u2014 above the recommended {{ formattedWarnLimitSignal() }} limit.</span>\n </div>\n }\n @case ('error') {\n <div class=\"dbx-pdf-merge-editor-size-error\">\n <mat-icon>error</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }}, exceeds the {{ formattedErrorLimitSignal() }} limit. Remove or compress files to continue.</span>\n </div>\n }\n}\n<div class=\"dbx-pdf-merge-editor-actions\">\n <span class=\"dbx-hint dbx-small\">{{ entryCountSignal() }} file(s)</span>\n @if (pageEditingSignal()) {\n <span class=\"dbx-hint dbx-small\">{{ mergeablePageCountSignal() }} page(s)</span>\n }\n <span class=\"dbx-spacer\"></span>\n <!-- Emptiness gates the button through the action's own disabled channel: dbxActionButton pushes\n `isDisabled$` into the button, which would otherwise win over a plain [disabled] input. -->\n <div dbxAction dbxActionSnackbarError [dbxActionDisabled]=\"entryCountSignal() === 0\" [dbxActionHandler]=\"handleClear\" [dbxActionConfirm]=\"clearConfirmSignal()\">\n <dbx-button dbxActionButton text=\"Clear\" icon=\"delete\"></dbx-button>\n </div>\n @if (showPreviewButtonSignal()) {\n <dbx-button text=\"Preview\" icon=\"picture_as_pdf\" [disabled]=\"!canMergeSignal()\" (buttonClick)=\"onPreview()\"></dbx-button>\n }\n @if (showDownloadButtonSignal() && canMergeSignal()) {\n <dbx-download-blob-button [config]=\"downloadConfigSignal()\"></dbx-download-blob-button>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }, { kind: "component", type: DbxFileUploadComponent, selector: "dbx-file-upload", inputs: ["config", "buttonStyle", "buttonDisplay", "mode", "text", "icon", "hint", "clickAreaToUpload"], outputs: ["filesChanged"] }, { kind: "component", type: DbxDownloadBlobButtonComponent, selector: "dbx-download-blob-button", inputs: ["config"] }, { kind: "directive", type: DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: DbxActionButtonDirective, selector: "[dbxActionButton]", inputs: ["dbxActionButtonEcho"] }, { kind: "directive", type: DbxActionDisabledDirective, selector: "[dbxActionDisabled]", inputs: ["dbxActionDisabled"] }, { kind: "directive", type: DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: DbxActionConfirmDirective, selector: "[dbxActionConfirm]", inputs: ["dbxActionConfirm", "dbxActionConfirmSkip"] }, { kind: "directive", type: DbxActionSnackbarErrorDirective, selector: "[dbxActionSnackbarError]", inputs: ["dbxActionSnackbarError"] }, { kind: "component", type: DbxPdfMergeListComponent, selector: "dbx-pdf-merge-list" }, { kind: "component", type: DbxPdfMergePageListComponent, selector: "dbx-pdf-merge-page-list", inputs: ["slotId", "showGroupLabels"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
19217
20561
|
}
|
|
19218
20562
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorComponent, decorators: [{
|
|
19219
20563
|
type: Component,
|
|
19220
20564
|
args: [{ selector: 'dbx-pdf-merge-editor', host: {
|
|
19221
20565
|
class: 'dbx-pdf-merge-editor d-block'
|
|
19222
|
-
}, imports: [MatIconModule, DbxButtonComponent, DbxFileUploadComponent, DbxDownloadBlobButtonComponent, DbxPdfMergeListComponent], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "@if (showAddFilesSignal()) {\n <div class=\"dbx-pdf-merge-editor-upload d-block dbx-mb3\">\n <dbx-file-upload [accept]=\"acceptSignal()\" [multiple]=\"multipleSignal()\" (filesChanged)=\"onFiles($event)\" [hint]=\"'Drop PDFs or images here, or click to browse'\" [text]=\"'Add files'\" icon=\"upload_file\"></dbx-file-upload>\n </div>\n}\n<ng-content></ng-content>\n@if (showFileListSignal()) {\n <dbx-pdf-merge-list></dbx-pdf-merge-list>\n}\n@if (focusActiveSignal()) {\n <div class=\"dbx-pdf-merge-editor-encrypted-banner\">\n <mat-icon>lock</mat-icon>\n <span>Encrypted PDF detected \u2014 only the encrypted file will be used; other files are ignored.</span>\n </div>\n}\n@switch (outputSizeStateSignal()) {\n @case ('warn') {\n <div class=\"dbx-pdf-merge-editor-size-warning\">\n <mat-icon>warning</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }} \u2014 above the recommended {{ formattedWarnLimitSignal() }} limit.</span>\n </div>\n }\n @case ('error') {\n <div class=\"dbx-pdf-merge-editor-size-error\">\n <mat-icon>error</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }}, exceeds the {{ formattedErrorLimitSignal() }} limit. Remove or compress files to continue.</span>\n </div>\n }\n}\n<div class=\"dbx-pdf-merge-editor-actions\">\n <span class=\"dbx-hint dbx-small\">{{ entryCountSignal() }} file(s)</span>\n <span class=\"dbx-
|
|
19223
|
-
}], ctorParameters: () => [], propDecorators: { accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], fileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileName", required: false }] }], showDownloadButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDownloadButton", required: false }] }], showPreviewButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPreviewButton", required: false }] }], downloadButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadButton", required: false }] }], showAddFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAddFiles", required: false }] }], showFileList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFileList", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], entriesChanged: [{ type: i0.Output, args: ["entriesChanged"] }] } });
|
|
20566
|
+
}, imports: [MatIconModule, DbxButtonComponent, DbxFileUploadComponent, DbxDownloadBlobButtonComponent, DbxActionDirective, DbxActionButtonDirective, DbxActionDisabledDirective, DbxActionHandlerDirective, DbxActionConfirmDirective, DbxActionSnackbarErrorDirective, DbxPdfMergeListComponent, DbxPdfMergePageListComponent], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "@if (showAddFilesSignal()) {\n <div class=\"dbx-pdf-merge-editor-upload d-block dbx-mb3\">\n <dbx-file-upload [accept]=\"acceptSignal()\" [multiple]=\"multipleSignal()\" (filesChanged)=\"onFiles($event)\" [hint]=\"'Drop PDFs or images here, or click to browse'\" [text]=\"'Add files'\" icon=\"upload_file\"></dbx-file-upload>\n </div>\n}\n<ng-content></ng-content>\n@if (showFileListSignal()) {\n @if (pageEditingSignal()) {\n <dbx-pdf-merge-page-list></dbx-pdf-merge-page-list>\n } @else {\n <dbx-pdf-merge-list></dbx-pdf-merge-list>\n }\n}\n@if (focusActiveSignal()) {\n <div class=\"dbx-pdf-merge-editor-encrypted-banner\">\n <mat-icon>lock</mat-icon>\n <span>Encrypted PDF detected \u2014 only the encrypted file will be used; other files are ignored.</span>\n </div>\n}\n@switch (outputSizeStateSignal()) {\n @case ('warn') {\n <div class=\"dbx-pdf-merge-editor-size-warning\">\n <mat-icon>warning</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }} \u2014 above the recommended {{ formattedWarnLimitSignal() }} limit.</span>\n </div>\n }\n @case ('error') {\n <div class=\"dbx-pdf-merge-editor-size-error\">\n <mat-icon>error</mat-icon>\n <span>Merged file is {{ formattedOutputSizeSignal() }}, exceeds the {{ formattedErrorLimitSignal() }} limit. Remove or compress files to continue.</span>\n </div>\n }\n}\n<div class=\"dbx-pdf-merge-editor-actions\">\n <span class=\"dbx-hint dbx-small\">{{ entryCountSignal() }} file(s)</span>\n @if (pageEditingSignal()) {\n <span class=\"dbx-hint dbx-small\">{{ mergeablePageCountSignal() }} page(s)</span>\n }\n <span class=\"dbx-spacer\"></span>\n <!-- Emptiness gates the button through the action's own disabled channel: dbxActionButton pushes\n `isDisabled$` into the button, which would otherwise win over a plain [disabled] input. -->\n <div dbxAction dbxActionSnackbarError [dbxActionDisabled]=\"entryCountSignal() === 0\" [dbxActionHandler]=\"handleClear\" [dbxActionConfirm]=\"clearConfirmSignal()\">\n <dbx-button dbxActionButton text=\"Clear\" icon=\"delete\"></dbx-button>\n </div>\n @if (showPreviewButtonSignal()) {\n <dbx-button text=\"Preview\" icon=\"picture_as_pdf\" [disabled]=\"!canMergeSignal()\" (buttonClick)=\"onPreview()\"></dbx-button>\n }\n @if (showDownloadButtonSignal() && canMergeSignal()) {\n <dbx-download-blob-button [config]=\"downloadConfigSignal()\"></dbx-download-blob-button>\n }\n</div>\n" }]
|
|
20567
|
+
}], ctorParameters: () => [], propDecorators: { accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], fileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileName", required: false }] }], showDownloadButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDownloadButton", required: false }] }], showPreviewButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPreviewButton", required: false }] }], downloadButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadButton", required: false }] }], showAddFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAddFiles", required: false }] }], showFileList: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFileList", required: false }] }], pageEditing: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageEditing", required: false }] }], sidecar: [{ type: i0.Input, args: [{ isSignal: true, alias: "sidecar", required: false }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], entriesChanged: [{ type: i0.Output, args: ["entriesChanged"] }] } });
|
|
19224
20568
|
|
|
19225
20569
|
/**
|
|
19226
20570
|
* Directive that registers as the active {@link DbxPdfMergeEditorValidator} on a {@link DbxPdfMergeEditorStore} and gates merge emissions on the readiness of every registered slot. {@link DbxPdfMergeEditorFileUploadComponent} instances walk the injector tree, find this directive, and call {@link registerSlot}/{@link unregisterSlot} during their own lifecycle.
|
|
19227
20571
|
*
|
|
20572
|
+
* This slot set is intentionally separate from {@link DbxPdfMergeEditorStore.registeredSlotIds$}, and the two should not be merged. This one is keyed by object identity because it exists solely to `combineLatest` each slot's `isValid$`; the store's is keyed by slot id, answers "which sections does this editor declare", and must be populated whether or not a validator is present — which is why it cannot live on this optional directive.
|
|
20573
|
+
*
|
|
19228
20574
|
* @example
|
|
19229
20575
|
* ```html
|
|
19230
20576
|
* <dbx-pdf-merge-editor [showAddFiles]="false" [showFileList]="false">
|
|
@@ -19293,11 +20639,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
19293
20639
|
|
|
19294
20640
|
const DEFAULT_MIN_FILES = 1;
|
|
19295
20641
|
const DEFAULT_REQUIRED = true;
|
|
20642
|
+
const DEFAULT_ADD_BUTTON_TEXT = 'Add';
|
|
20643
|
+
const DEFAULT_CLEAR_BUTTON_TEXT = 'Clear';
|
|
20644
|
+
const DEFAULT_CLEAR_CONFIRM_TITLE = 'Clear this section?';
|
|
20645
|
+
const DEFAULT_CLEAR_CONFIRM_PROMPT = 'Every file in this section will be removed, along with any page edits made to them.';
|
|
20646
|
+
const DEFAULT_CLEAR_CONFIRM_CONFIRM_TEXT = 'Clear';
|
|
20647
|
+
const DEFAULT_CLEAR_CONFIRM_CANCEL_TEXT = 'Cancel';
|
|
19296
20648
|
/**
|
|
19297
20649
|
* Slot-scoped uploader for use inside a {@link DbxPdfMergeEditorComponent}. Adds files to the shared {@link DbxPdfMergeEditorStore} tagged with this slot's `slotId`, displays the slot's owned entries inline using {@link DbxPdfMergeEntryComponent}, and reports its readiness to the optional ancestor {@link DbxPdfMergeEditorFileUploadValidatorDirective}. On destroy the slot removes its owned entries from the store.
|
|
19298
20650
|
*
|
|
19299
20651
|
* Projects an `<ng-content>` slot inside its header element so consumers can render state-aware indicators (e.g. via the `dbxPdfMergeEditorFileUploadHasState` structural directive) alongside the optional `label`.
|
|
19300
20652
|
*
|
|
20653
|
+
* Once the slot owns entries its header gains an Add button (while the slot is under capacity) and a Clear button, so a section that already has content can still be extended, emptied, or replaced. Clearing runs as its own `dbxAction` behind a `dbxActionConfirm` prompt — a misclick costs a dialog, not the section's files.
|
|
20654
|
+
*
|
|
19301
20655
|
* @example
|
|
19302
20656
|
* ```html
|
|
19303
20657
|
* <dbx-pdf-merge-editor-file-upload slotId="license" [config]="{ label: 'Driver’s License', accept: ['application/pdf'] }">
|
|
@@ -19310,6 +20664,10 @@ class DbxPdfMergeEditorFileUploadComponent {
|
|
|
19310
20664
|
_validator = inject(DbxPdfMergeEditorFileUploadValidatorDirective, { optional: true });
|
|
19311
20665
|
_injectedConfig = inject(DBX_PDF_MERGE_EDITOR_CONFIG, { optional: true });
|
|
19312
20666
|
_preserveEntriesOnDestroy = inject(DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY, { optional: true }) ?? false;
|
|
20667
|
+
/**
|
|
20668
|
+
* The slot id this component registered with the store, captured at registration time so destroy deregisters the same key. `null` before {@link ngOnInit} and after teardown.
|
|
20669
|
+
*/
|
|
20670
|
+
_registeredSlotId = null;
|
|
19313
20671
|
slotId = input.required(...(ngDevMode ? [{ debugName: "slotId" }] : /* istanbul ignore next */ []));
|
|
19314
20672
|
config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
|
|
19315
20673
|
acceptSignal = computed(() => this.config()?.accept ?? DEFAULT_PDF_MERGE_ACCEPT, ...(ngDevMode ? [{ debugName: "acceptSignal" }] : /* istanbul ignore next */ []));
|
|
@@ -19346,13 +20704,78 @@ class DbxPdfMergeEditorFileUploadComponent {
|
|
|
19346
20704
|
ownedEntries$ = toObservable(this.slotId).pipe(switchMap((slotId) => this.store.entriesForSlotId$(slotId)), shareReplay(1));
|
|
19347
20705
|
ownedEntriesSignal = toSignal(this.ownedEntries$, { initialValue: [] });
|
|
19348
20706
|
/**
|
|
19349
|
-
* Whether the
|
|
20707
|
+
* Whether the shared store has page editing enabled. Drives whether this slot lists its own pages or its own files.
|
|
20708
|
+
*/
|
|
20709
|
+
pageEditingSignal = toSignal(this.store.pageEditing$, { initialValue: false });
|
|
20710
|
+
/**
|
|
20711
|
+
* Pages contributed by this slot's entries. Empty while page editing is disabled.
|
|
20712
|
+
*/
|
|
20713
|
+
ownedPages$ = toObservable(this.slotId).pipe(switchMap((slotId) => this.store.pagesForSlotId$(slotId)), shareReplay(1));
|
|
20714
|
+
/**
|
|
20715
|
+
* Whether the slot still has room for more files. Gates every add affordance — once the slot is at capacity neither the drop area nor the header Add button is offered, and the user must clear the slot (or remove an entry) to add another.
|
|
19350
20716
|
*/
|
|
19351
20717
|
canAddFilesSignal = computed(() => this.ownedEntriesSignal().length < this.capacitySignal(), ...(ngDevMode ? [{ debugName: "canAddFilesSignal" }] : /* istanbul ignore next */ []));
|
|
20718
|
+
showAddButtonConfigSignal = computed(() => this.config()?.showAddButton ?? true, ...(ngDevMode ? [{ debugName: "showAddButtonConfigSignal" }] : /* istanbul ignore next */ []));
|
|
20719
|
+
showClearButtonConfigSignal = computed(() => this.config()?.showClearButton ?? true, ...(ngDevMode ? [{ debugName: "showClearButtonConfigSignal" }] : /* istanbul ignore next */ []));
|
|
20720
|
+
/**
|
|
20721
|
+
* Whether the compact header Add button is rendered. Only meaningful once the slot owns something — while it is empty the drop area is already the add affordance.
|
|
20722
|
+
*/
|
|
20723
|
+
showAddButtonSignal = computed(() => {
|
|
20724
|
+
const ownedEntries = this.ownedEntriesSignal();
|
|
20725
|
+
const canAddFiles = this.canAddFilesSignal();
|
|
20726
|
+
return this.showAddButtonConfigSignal() && ownedEntries.length > 0 && canAddFiles;
|
|
20727
|
+
}, ...(ngDevMode ? [{ debugName: "showAddButtonSignal" }] : /* istanbul ignore next */ []));
|
|
20728
|
+
showClearButtonSignal = computed(() => {
|
|
20729
|
+
const ownedEntries = this.ownedEntriesSignal();
|
|
20730
|
+
return this.showClearButtonConfigSignal() && ownedEntries.length > 0;
|
|
20731
|
+
}, ...(ngDevMode ? [{ debugName: "showClearButtonSignal" }] : /* istanbul ignore next */ []));
|
|
20732
|
+
/**
|
|
20733
|
+
* Whether the full drop area is rendered. It is the slot's empty state while the header Add button is enabled; with the button turned off it falls back to staying visible until the slot reaches capacity.
|
|
20734
|
+
*/
|
|
20735
|
+
showUploadAreaSignal = computed(() => {
|
|
20736
|
+
const showAddButtonConfig = this.showAddButtonConfigSignal();
|
|
20737
|
+
const canAddFiles = this.canAddFilesSignal();
|
|
20738
|
+
const isEmpty = this.ownedEntriesSignal().length === 0;
|
|
20739
|
+
return canAddFiles && (isEmpty || !showAddButtonConfig);
|
|
20740
|
+
}, ...(ngDevMode ? [{ debugName: "showUploadAreaSignal" }] : /* istanbul ignore next */ []));
|
|
20741
|
+
addButtonTextSignal = computed(() => this.config()?.addButtonText ?? DEFAULT_ADD_BUTTON_TEXT, ...(ngDevMode ? [{ debugName: "addButtonTextSignal" }] : /* istanbul ignore next */ []));
|
|
20742
|
+
clearButtonTextSignal = computed(() => this.config()?.clearButtonText ?? DEFAULT_CLEAR_BUTTON_TEXT, ...(ngDevMode ? [{ debugName: "clearButtonTextSignal" }] : /* istanbul ignore next */ []));
|
|
20743
|
+
addButtonAriaLabelSignal = computed(() => {
|
|
20744
|
+
const label = this.labelSignal();
|
|
20745
|
+
return label ? `Add files to ${label}` : 'Add files to this section';
|
|
20746
|
+
}, ...(ngDevMode ? [{ debugName: "addButtonAriaLabelSignal" }] : /* istanbul ignore next */ []));
|
|
20747
|
+
clearButtonAriaLabelSignal = computed(() => {
|
|
20748
|
+
const label = this.labelSignal();
|
|
20749
|
+
return label ? `Clear ${label}` : 'Clear this section';
|
|
20750
|
+
}, ...(ngDevMode ? [{ debugName: "clearButtonAriaLabelSignal" }] : /* istanbul ignore next */ []));
|
|
20751
|
+
/**
|
|
20752
|
+
* Config for the confirmation shown before the slot is cleared. The slot's `label` is folded into the default title so a page of sections says which one is about to be emptied; `clearConfirm` overrides any field of it.
|
|
20753
|
+
*/
|
|
20754
|
+
clearConfirmSignal = computed(() => {
|
|
20755
|
+
const label = this.labelSignal();
|
|
20756
|
+
const overrides = this.config()?.clearConfirm;
|
|
20757
|
+
return {
|
|
20758
|
+
title: label ? `Clear ${label}?` : DEFAULT_CLEAR_CONFIRM_TITLE,
|
|
20759
|
+
prompt: DEFAULT_CLEAR_CONFIRM_PROMPT,
|
|
20760
|
+
confirmText: DEFAULT_CLEAR_CONFIRM_CONFIRM_TEXT,
|
|
20761
|
+
cancelText: DEFAULT_CLEAR_CONFIRM_CANCEL_TEXT,
|
|
20762
|
+
...overrides
|
|
20763
|
+
};
|
|
20764
|
+
}, ...(ngDevMode ? [{ debugName: "clearConfirmSignal" }] : /* istanbul ignore next */ []));
|
|
20765
|
+
/**
|
|
20766
|
+
* Clears every entry this slot owns. Runs as the handler of the header's own `dbxAction`, so the `dbxActionConfirm` on that action is what stands between a misclick and the section's files.
|
|
20767
|
+
*
|
|
20768
|
+
* @param _value - Unused. The confirmation supplies no value, only the go-ahead.
|
|
20769
|
+
* @param context - Work context completed as soon as the store drops the entries.
|
|
20770
|
+
*/
|
|
20771
|
+
handleClear = (_value, context) => {
|
|
20772
|
+
this.store.removeEntriesBySlotId(this.slotId());
|
|
20773
|
+
context.success();
|
|
20774
|
+
};
|
|
19352
20775
|
/**
|
|
19353
20776
|
* High-level state of the slot — `no_file` when empty, `valid` when owned entries satisfy the slot's thresholds, `invalid` when owned entries fail or are still being validated.
|
|
19354
20777
|
*/
|
|
19355
|
-
state$ = this.ownedEntries
|
|
20778
|
+
state$ = combineLatest([this.ownedEntries$, this.store.pageEditing$, this.ownedPages$]).pipe(map(([entries, pageEditing, pages]) => {
|
|
19356
20779
|
let state;
|
|
19357
20780
|
if (entries.length === 0) {
|
|
19358
20781
|
state = 'no_file';
|
|
@@ -19362,6 +20785,10 @@ class DbxPdfMergeEditorFileUploadComponent {
|
|
|
19362
20785
|
const readyCount = entries.filter((entry) => entry.status === 'ready').length;
|
|
19363
20786
|
const minFiles = this.minFilesSignal();
|
|
19364
20787
|
const maxFiles = this.maxFilesSignal();
|
|
20788
|
+
// Only meaningful once the slot actually has pages: an empty list means editing is off, the
|
|
20789
|
+
// entry is still hydrating, or it could not be expanded (encrypted) — none of which are the
|
|
20790
|
+
// user deleting their own content.
|
|
20791
|
+
const allPagesRemoved = pageEditing && pages.length > 0 && pages.every((page) => page.removed);
|
|
19365
20792
|
if (validating) {
|
|
19366
20793
|
state = 'invalid';
|
|
19367
20794
|
}
|
|
@@ -19371,6 +20798,9 @@ class DbxPdfMergeEditorFileUploadComponent {
|
|
|
19371
20798
|
else if (maxFiles != null && readyCount > maxFiles) {
|
|
19372
20799
|
state = 'invalid';
|
|
19373
20800
|
}
|
|
20801
|
+
else if (allPagesRemoved) {
|
|
20802
|
+
state = 'invalid';
|
|
20803
|
+
}
|
|
19374
20804
|
else {
|
|
19375
20805
|
state = 'valid';
|
|
19376
20806
|
}
|
|
@@ -19397,9 +20827,26 @@ class DbxPdfMergeEditorFileUploadComponent {
|
|
|
19397
20827
|
}), distinctUntilChanged(), shareReplay(1));
|
|
19398
20828
|
isValidSignal = toSignal(this.isValid$, { initialValue: false });
|
|
19399
20829
|
ngOnInit() {
|
|
20830
|
+
// Two registrations, deliberately: the store learns this slot's ID (so consumers like
|
|
20831
|
+
// <dbx-pdf-merge-import> can enumerate the sections this editor declares), while the optional
|
|
20832
|
+
// validator directive tracks the component itself to aggregate `isValid$`. They are separate
|
|
20833
|
+
// because the store is always present while the validator is not, and because the store's
|
|
20834
|
+
// `setValidator` seam exists precisely so it never has to hold slot component references.
|
|
20835
|
+
this._registeredSlotId = this.slotId();
|
|
20836
|
+
this.store.registerSlotId(this._registeredSlotId);
|
|
19400
20837
|
this._validator?.registerSlot(this);
|
|
19401
20838
|
}
|
|
19402
20839
|
ngOnDestroy() {
|
|
20840
|
+
// Unconditional, and deliberately OUTSIDE the `_preserveEntriesOnDestroy` check below: that
|
|
20841
|
+
// token governs whether the slot's ENTRIES survive, not whether the slot is mounted. The
|
|
20842
|
+
// upload dialog sets it to `true` and is torn down on every close, so gating this on it would
|
|
20843
|
+
// leave the store permanently claiming a section that is no longer on screen. The captured id
|
|
20844
|
+
// (rather than a fresh `slotId()` read) guarantees this decrements the same key `ngOnInit`
|
|
20845
|
+
// incremented, and clearing it keeps a repeat destroy from evicting a still-mounted duplicate.
|
|
20846
|
+
if (this._registeredSlotId != null) {
|
|
20847
|
+
this.store.unregisterSlotId(this._registeredSlotId);
|
|
20848
|
+
this._registeredSlotId = null;
|
|
20849
|
+
}
|
|
19403
20850
|
this._validator?.unregisterSlot(this);
|
|
19404
20851
|
// Default behavior: removing a slot from a template (e.g. via `@if`) also drops the slot's
|
|
19405
20852
|
// entries from the store. Opt out by providing `DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY`
|
|
@@ -19460,20 +20907,36 @@ class DbxPdfMergeEditorFileUploadComponent {
|
|
|
19460
20907
|
<span class="dbx-pdf-merge-editor-file-upload-label">{{ label }}</span>
|
|
19461
20908
|
}
|
|
19462
20909
|
<ng-content></ng-content>
|
|
20910
|
+
@if (showAddButtonSignal() || showClearButtonSignal()) {
|
|
20911
|
+
<div class="dbx-pdf-merge-editor-file-upload-actions">
|
|
20912
|
+
@if (showAddButtonSignal()) {
|
|
20913
|
+
<dbx-file-upload-button [accept]="acceptSignal()" [multiple]="multipleSignal()" [text]="addButtonTextSignal()" icon="add" [ariaLabel]="addButtonAriaLabelSignal()" (filesChanged)="onFiles($event)"></dbx-file-upload-button>
|
|
20914
|
+
}
|
|
20915
|
+
@if (showClearButtonSignal()) {
|
|
20916
|
+
<div dbxAction dbxActionSnackbarError [dbxActionHandler]="handleClear" [dbxActionConfirm]="clearConfirmSignal()">
|
|
20917
|
+
<dbx-button dbxActionButton [text]="clearButtonTextSignal()" icon="delete" [ariaLabel]="clearButtonAriaLabelSignal()"></dbx-button>
|
|
20918
|
+
</div>
|
|
20919
|
+
}
|
|
20920
|
+
</div>
|
|
20921
|
+
}
|
|
19463
20922
|
</div>
|
|
19464
|
-
@if (
|
|
20923
|
+
@if (showUploadAreaSignal()) {
|
|
19465
20924
|
<dbx-file-upload [accept]="acceptSignal()" [multiple]="multipleSignal()" [mode]="modeSignal()" [hint]="hintSignal()" [text]="textSignal()" [icon]="iconSignal()" (filesChanged)="onFiles($event)"></dbx-file-upload>
|
|
19466
20925
|
}
|
|
19467
20926
|
@if (ownedEntriesSignal(); as owned) {
|
|
19468
20927
|
@if (owned.length > 0) {
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
20928
|
+
@if (pageEditingSignal()) {
|
|
20929
|
+
<dbx-pdf-merge-page-list class="dbx-pdf-merge-editor-file-upload-pages" [slotId]="slotId()"></dbx-pdf-merge-page-list>
|
|
20930
|
+
} @else {
|
|
20931
|
+
<div class="dbx-pdf-merge-editor-file-upload-entries" cdkDropList (cdkDropListDropped)="onDrop($event)">
|
|
20932
|
+
@for (entry of owned; track entry.id) {
|
|
20933
|
+
<dbx-pdf-merge-entry [entry]="entry"></dbx-pdf-merge-entry>
|
|
20934
|
+
}
|
|
20935
|
+
</div>
|
|
20936
|
+
}
|
|
19474
20937
|
}
|
|
19475
20938
|
}
|
|
19476
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "component", type: DbxFileUploadComponent, selector: "dbx-file-upload", inputs: ["config", "buttonStyle", "buttonDisplay", "mode", "text", "icon", "hint", "clickAreaToUpload"], outputs: ["filesChanged"] }, { kind: "component", type: DbxPdfMergeEntryComponent, selector: "dbx-pdf-merge-entry", inputs: ["entry"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
20939
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }, { kind: "component", type: DbxFileUploadComponent, selector: "dbx-file-upload", inputs: ["config", "buttonStyle", "buttonDisplay", "mode", "text", "icon", "hint", "clickAreaToUpload"], outputs: ["filesChanged"] }, { kind: "component", type: DbxFileUploadButtonComponent, selector: "dbx-file-upload-button", inputs: ["text", "icon", "ariaLabel", "buttonStyle"], outputs: ["filesChanged"] }, { kind: "directive", type: DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: DbxActionButtonDirective, selector: "[dbxActionButton]", inputs: ["dbxActionButtonEcho"] }, { kind: "directive", type: DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: DbxActionConfirmDirective, selector: "[dbxActionConfirm]", inputs: ["dbxActionConfirm", "dbxActionConfirmSkip"] }, { kind: "directive", type: DbxActionSnackbarErrorDirective, selector: "[dbxActionSnackbarError]", inputs: ["dbxActionSnackbarError"] }, { kind: "component", type: DbxPdfMergeEntryComponent, selector: "dbx-pdf-merge-entry", inputs: ["entry"] }, { kind: "component", type: DbxPdfMergePageListComponent, selector: "dbx-pdf-merge-page-list", inputs: ["slotId", "showGroupLabels"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
19477
20940
|
}
|
|
19478
20941
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeEditorFileUploadComponent, decorators: [{
|
|
19479
20942
|
type: Component,
|
|
@@ -19485,17 +20948,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
19485
20948
|
<span class="dbx-pdf-merge-editor-file-upload-label">{{ label }}</span>
|
|
19486
20949
|
}
|
|
19487
20950
|
<ng-content></ng-content>
|
|
20951
|
+
@if (showAddButtonSignal() || showClearButtonSignal()) {
|
|
20952
|
+
<div class="dbx-pdf-merge-editor-file-upload-actions">
|
|
20953
|
+
@if (showAddButtonSignal()) {
|
|
20954
|
+
<dbx-file-upload-button [accept]="acceptSignal()" [multiple]="multipleSignal()" [text]="addButtonTextSignal()" icon="add" [ariaLabel]="addButtonAriaLabelSignal()" (filesChanged)="onFiles($event)"></dbx-file-upload-button>
|
|
20955
|
+
}
|
|
20956
|
+
@if (showClearButtonSignal()) {
|
|
20957
|
+
<div dbxAction dbxActionSnackbarError [dbxActionHandler]="handleClear" [dbxActionConfirm]="clearConfirmSignal()">
|
|
20958
|
+
<dbx-button dbxActionButton [text]="clearButtonTextSignal()" icon="delete" [ariaLabel]="clearButtonAriaLabelSignal()"></dbx-button>
|
|
20959
|
+
</div>
|
|
20960
|
+
}
|
|
20961
|
+
</div>
|
|
20962
|
+
}
|
|
19488
20963
|
</div>
|
|
19489
|
-
@if (
|
|
20964
|
+
@if (showUploadAreaSignal()) {
|
|
19490
20965
|
<dbx-file-upload [accept]="acceptSignal()" [multiple]="multipleSignal()" [mode]="modeSignal()" [hint]="hintSignal()" [text]="textSignal()" [icon]="iconSignal()" (filesChanged)="onFiles($event)"></dbx-file-upload>
|
|
19491
20966
|
}
|
|
19492
20967
|
@if (ownedEntriesSignal(); as owned) {
|
|
19493
20968
|
@if (owned.length > 0) {
|
|
19494
|
-
|
|
19495
|
-
|
|
19496
|
-
|
|
19497
|
-
|
|
19498
|
-
|
|
20969
|
+
@if (pageEditingSignal()) {
|
|
20970
|
+
<dbx-pdf-merge-page-list class="dbx-pdf-merge-editor-file-upload-pages" [slotId]="slotId()"></dbx-pdf-merge-page-list>
|
|
20971
|
+
} @else {
|
|
20972
|
+
<div class="dbx-pdf-merge-editor-file-upload-entries" cdkDropList (cdkDropListDropped)="onDrop($event)">
|
|
20973
|
+
@for (entry of owned; track entry.id) {
|
|
20974
|
+
<dbx-pdf-merge-entry [entry]="entry"></dbx-pdf-merge-entry>
|
|
20975
|
+
}
|
|
20976
|
+
</div>
|
|
20977
|
+
}
|
|
19499
20978
|
}
|
|
19500
20979
|
}
|
|
19501
20980
|
`,
|
|
@@ -19505,7 +20984,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
19505
20984
|
'[class.dbx-pdf-merge-editor-file-upload--valid]': 'stateSignal() === "valid"',
|
|
19506
20985
|
'[class.dbx-pdf-merge-editor-file-upload--no-file]': 'stateSignal() === "no_file"'
|
|
19507
20986
|
},
|
|
19508
|
-
imports: [CdkDropList, DbxFileUploadComponent, DbxPdfMergeEntryComponent],
|
|
20987
|
+
imports: [CdkDropList, DbxButtonComponent, DbxFileUploadComponent, DbxFileUploadButtonComponent, DbxActionDirective, DbxActionButtonDirective, DbxActionHandlerDirective, DbxActionConfirmDirective, DbxActionSnackbarErrorDirective, DbxPdfMergeEntryComponent, DbxPdfMergePageListComponent],
|
|
19509
20988
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
19510
20989
|
standalone: true
|
|
19511
20990
|
}]
|
|
@@ -19544,6 +21023,252 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
19544
21023
|
}]
|
|
19545
21024
|
}], propDecorators: { targetState: [{ type: i0.Input, args: [{ isSignal: true, alias: "dbxPdfMergeEditorFileUploadHasState", required: false }] }] } });
|
|
19546
21025
|
|
|
21026
|
+
/**
|
|
21027
|
+
* Messages shown for each reason an import can fail.
|
|
21028
|
+
*/
|
|
21029
|
+
const DBX_PDF_MERGE_IMPORT_ERROR_MESSAGES = {
|
|
21030
|
+
unreadable: 'This file could not be read as a PDF. It may be corrupt or password protected.',
|
|
21031
|
+
no_sidecar: 'This PDF has no embedded manifest, so its pages cannot be matched back to sections. Choose a PDF that was exported from this editor.',
|
|
21032
|
+
no_documents: 'This PDF has a manifest, but none of its recorded pages could be found in the file.'
|
|
21033
|
+
};
|
|
21034
|
+
/**
|
|
21035
|
+
* Loads a PDF that was previously exported from the editor with an embedded manifest, and repopulates the editor's slots from it.
|
|
21036
|
+
*
|
|
21037
|
+
* This is the payoff of the sidecar: a completed single-file document goes back to being the set of per-slot documents it was assembled from, so a user can replace or edit one section in place instead of rebuilding the whole file. Every entry currently in the store is replaced.
|
|
21038
|
+
*
|
|
21039
|
+
* **Zero configuration.** The sections this editor accepts are read from the `<dbx-pdf-merge-editor-file-upload>` slots mounted against the shared {@link DbxPdfMergeEditorStore} (see {@link DbxPdfMergeEditorStore.registeredSlotIds$}), so the common case needs no bindings at all. A file naming any other section is rejected rather than partially imported — without that check, entries carrying an unrecognized slot id would import and then render nowhere.
|
|
21040
|
+
*
|
|
21041
|
+
* **Renders nothing when no sections are configured.** This component exists to split a document back into slots, so with no slots mounted (and no explicit {@link expectedSlotIds}) it has nothing to import into and collapses to zero size. Bind {@link enforceExpectedSlots} to `false` for the unusual case of an editor that wants the import affordance without declaring slots.
|
|
21042
|
+
*
|
|
21043
|
+
* @example
|
|
21044
|
+
* ```html
|
|
21045
|
+
* <!-- derives its sections from the slots below it -->
|
|
21046
|
+
* <dbx-pdf-merge-import (imported)="onImported($event)"></dbx-pdf-merge-import>
|
|
21047
|
+
* <dbx-pdf-merge-editor>
|
|
21048
|
+
* <dbx-pdf-merge-editor-file-upload slotId="license"></dbx-pdf-merge-editor-file-upload>
|
|
21049
|
+
* <dbx-pdf-merge-editor-file-upload slotId="cert"></dbx-pdf-merge-editor-file-upload>
|
|
21050
|
+
* </dbx-pdf-merge-editor>
|
|
21051
|
+
* ```
|
|
21052
|
+
*/
|
|
21053
|
+
class DbxPdfMergeImportComponent {
|
|
21054
|
+
store = inject(DbxPdfMergeEditorStore);
|
|
21055
|
+
config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
|
|
21056
|
+
/**
|
|
21057
|
+
* Optional override for the slot ids this editor renders. Leave unbound to derive them from the slots mounted against the store — the zero-configuration default. When set, a file whose manifest names any other slot is rejected rather than partially imported.
|
|
21058
|
+
*
|
|
21059
|
+
* An explicitly bound empty array reads as "this editor deliberately has no sections" and hides the component, the same as having no slots mounted.
|
|
21060
|
+
*/
|
|
21061
|
+
expectedSlotIds = input(...(ngDevMode ? [undefined, { debugName: "expectedSlotIds" }] : /* istanbul ignore next */ []));
|
|
21062
|
+
/**
|
|
21063
|
+
* Whether the imported file's sections must match this editor's. Defaults to `true`.
|
|
21064
|
+
*
|
|
21065
|
+
* Binding `false` is the single escape hatch for an editor with no slots that still wants the import affordance: it both skips the check and keeps the component rendered.
|
|
21066
|
+
*/
|
|
21067
|
+
enforceExpectedSlots = input(true, ...(ngDevMode ? [{ debugName: "enforceExpectedSlots" }] : /* istanbul ignore next */ []));
|
|
21068
|
+
imported = output();
|
|
21069
|
+
importFailed = output();
|
|
21070
|
+
/**
|
|
21071
|
+
* Emits the expected sections the imported file did not fill, when there are any. The import itself still succeeds — a missing section is a partial state the user can fill in.
|
|
21072
|
+
*/
|
|
21073
|
+
missingSlots = output();
|
|
21074
|
+
_error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : /* istanbul ignore next */ []));
|
|
21075
|
+
_result = signal(null, ...(ngDevMode ? [{ debugName: "_result" }] : /* istanbul ignore next */ []));
|
|
21076
|
+
_missingSlotIds = signal([], ...(ngDevMode ? [{ debugName: "_missingSlotIds" }] : /* istanbul ignore next */ []));
|
|
21077
|
+
errorSignal = this._error.asReadonly();
|
|
21078
|
+
resultSignal = this._result.asReadonly();
|
|
21079
|
+
missingSlotIdsSignal = this._missingSlotIds.asReadonly();
|
|
21080
|
+
_registeredSlotIdsSignal = toSignal(this.store.registeredSlotIds$, { initialValue: [] });
|
|
21081
|
+
/**
|
|
21082
|
+
* The exact import state the notices below currently describe — the one this component's own picked file produced. Identity, not contents: it is the only reliable way to tell "still my file" from "the store moved on".
|
|
21083
|
+
*/
|
|
21084
|
+
_displayedState;
|
|
21085
|
+
constructor() {
|
|
21086
|
+
// The notices are set from what `onFiles` gets back rather than from `importState$`, so that
|
|
21087
|
+
// they only ever describe a file picked here. They must not outlive it, though: once the store
|
|
21088
|
+
// is showing anything other than this component's own import — cleared outright, restored to a
|
|
21089
|
+
// programmatic baseline, or replaced by an import from elsewhere — the notice is describing
|
|
21090
|
+
// content the editor no longer holds, and goes.
|
|
21091
|
+
this.store.importState$.pipe(takeUntilDestroyed()).subscribe((state) => {
|
|
21092
|
+
if (state !== this._displayedState) {
|
|
21093
|
+
this.resetDisplay();
|
|
21094
|
+
}
|
|
21095
|
+
});
|
|
21096
|
+
}
|
|
21097
|
+
/**
|
|
21098
|
+
* The sections an imported file is allowed to name, or `null` when no check applies. Resolves the explicit {@link expectedSlotIds} binding over the slots registered with the store.
|
|
21099
|
+
*/
|
|
21100
|
+
effectiveExpectedSlotIdsSignal = computed(() => {
|
|
21101
|
+
const registered = this._registeredSlotIdsSignal();
|
|
21102
|
+
const bound = this.expectedSlotIds();
|
|
21103
|
+
let expected;
|
|
21104
|
+
if (!this.enforceExpectedSlots()) {
|
|
21105
|
+
expected = null;
|
|
21106
|
+
}
|
|
21107
|
+
else if (bound == null) {
|
|
21108
|
+
// An empty registry is "nothing declared yet", not "nothing allowed" — the component hides
|
|
21109
|
+
// rather than rejecting, so a null here never becomes a check that fails every file.
|
|
21110
|
+
expected = registered.length > 0 ? registered : null;
|
|
21111
|
+
}
|
|
21112
|
+
else {
|
|
21113
|
+
expected = bound;
|
|
21114
|
+
}
|
|
21115
|
+
return expected;
|
|
21116
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveExpectedSlotIdsSignal" }] : /* istanbul ignore next */ []));
|
|
21117
|
+
/**
|
|
21118
|
+
* Whether this component has anything to import into. `false` collapses it to nothing — see the class docs.
|
|
21119
|
+
*/
|
|
21120
|
+
activeSignal = computed(() => {
|
|
21121
|
+
const expected = this.effectiveExpectedSlotIdsSignal();
|
|
21122
|
+
return !this.enforceExpectedSlots() || (expected != null && expected.length > 0);
|
|
21123
|
+
}, ...(ngDevMode ? [{ debugName: "activeSignal" }] : /* istanbul ignore next */ []));
|
|
21124
|
+
acceptSignal = computed(() => this.config()?.accept ?? [PDF_MIME_TYPE], ...(ngDevMode ? [{ debugName: "acceptSignal" }] : /* istanbul ignore next */ []));
|
|
21125
|
+
modeSignal = computed(() => this.config()?.mode ?? 'default', ...(ngDevMode ? [{ debugName: "modeSignal" }] : /* istanbul ignore next */ []));
|
|
21126
|
+
labelSignal = computed(() => this.config()?.label, ...(ngDevMode ? [{ debugName: "labelSignal" }] : /* istanbul ignore next */ []));
|
|
21127
|
+
hintSignal = computed(() => this.config()?.hint ?? 'Choose a PDF previously exported from this editor', ...(ngDevMode ? [{ debugName: "hintSignal" }] : /* istanbul ignore next */ []));
|
|
21128
|
+
textSignal = computed(() => this.config()?.text ?? 'Import merged PDF', ...(ngDevMode ? [{ debugName: "textSignal" }] : /* istanbul ignore next */ []));
|
|
21129
|
+
iconSignal = computed(() => this.config()?.icon ?? 'upload_file', ...(ngDevMode ? [{ debugName: "iconSignal" }] : /* istanbul ignore next */ []));
|
|
21130
|
+
successLabelSignal = computed(() => {
|
|
21131
|
+
const result = this._result();
|
|
21132
|
+
let label;
|
|
21133
|
+
if (result == null) {
|
|
21134
|
+
label = null;
|
|
21135
|
+
}
|
|
21136
|
+
else {
|
|
21137
|
+
const sections = result.slotIds.map((slotId) => slotId ?? 'unsectioned').join(', ');
|
|
21138
|
+
label = `Imported ${result.entries.length} section(s): ${sections}.`;
|
|
21139
|
+
}
|
|
21140
|
+
return label;
|
|
21141
|
+
}, ...(ngDevMode ? [{ debugName: "successLabelSignal" }] : /* istanbul ignore next */ []));
|
|
21142
|
+
/**
|
|
21143
|
+
* Warning shown when the imported file left one or more expected sections empty. The import succeeded — this only explains why a slot is still blank.
|
|
21144
|
+
*/
|
|
21145
|
+
missingLabelSignal = computed(() => {
|
|
21146
|
+
const missing = this._missingSlotIds();
|
|
21147
|
+
return missing.length > 0 ? `This PDF did not include section(s): ${missing.join(', ')}.` : null;
|
|
21148
|
+
}, ...(ngDevMode ? [{ debugName: "missingLabelSignal" }] : /* istanbul ignore next */ []));
|
|
21149
|
+
async onFiles(event) {
|
|
21150
|
+
const file = event.matchResult.accepted[0];
|
|
21151
|
+
if (file != null) {
|
|
21152
|
+
this.resetDisplay();
|
|
21153
|
+
// Delegates to the store so the picker and a programmatic import share one implementation
|
|
21154
|
+
// and one set of error semantics. The slot check is opt-in on the store, so the component's
|
|
21155
|
+
// own resolved expectation is what preserves the picker's behavior. The returned state is
|
|
21156
|
+
// used rather than `importState$` so these outputs only ever describe a picked file.
|
|
21157
|
+
// `origin: 'user'` marks this as discardable — it must not become the store's clear-time
|
|
21158
|
+
// restore point, which belongs to whatever document the app itself supplied.
|
|
21159
|
+
const state = await this.store.importMergedPdf({ source: file, expectedSlotIds: this.effectiveExpectedSlotIdsSignal(), origin: 'user' });
|
|
21160
|
+
// Claim this state before rendering it: the subscription above resets on anything it does
|
|
21161
|
+
// not recognise, and the 'importing'/terminal emissions have already gone past by now.
|
|
21162
|
+
this._displayedState = state;
|
|
21163
|
+
if (state.status === 'failed') {
|
|
21164
|
+
this.fail(this.messageForFailure(state));
|
|
21165
|
+
}
|
|
21166
|
+
else if (state.result != null) {
|
|
21167
|
+
const { result } = state;
|
|
21168
|
+
this._result.set(result);
|
|
21169
|
+
this._missingSlotIds.set(result.missingSlotIds);
|
|
21170
|
+
this.imported.emit(result);
|
|
21171
|
+
if (result.missingSlotIds.length > 0) {
|
|
21172
|
+
this.missingSlots.emit(result.missingSlotIds);
|
|
21173
|
+
}
|
|
21174
|
+
}
|
|
21175
|
+
}
|
|
21176
|
+
}
|
|
21177
|
+
messageForFailure(state) {
|
|
21178
|
+
const unexpected = state.unexpectedSlotIds ?? [];
|
|
21179
|
+
let message;
|
|
21180
|
+
if (state.error === 'unexpected_slots') {
|
|
21181
|
+
message = `This PDF contains section(s) this editor does not have: ${unexpected.map((slotId) => slotId ?? 'unsectioned').join(', ')}.`;
|
|
21182
|
+
}
|
|
21183
|
+
else {
|
|
21184
|
+
message = DBX_PDF_MERGE_IMPORT_ERROR_MESSAGES[state.error ?? 'unreadable'];
|
|
21185
|
+
}
|
|
21186
|
+
return message;
|
|
21187
|
+
}
|
|
21188
|
+
fail(message) {
|
|
21189
|
+
this._error.set(message);
|
|
21190
|
+
this.importFailed.emit(message);
|
|
21191
|
+
}
|
|
21192
|
+
/**
|
|
21193
|
+
* Drops every notice this component renders, returning it to its pre-import appearance and releasing its claim on the store's import state.
|
|
21194
|
+
*/
|
|
21195
|
+
resetDisplay() {
|
|
21196
|
+
this._displayedState = null;
|
|
21197
|
+
this._error.set(null);
|
|
21198
|
+
this._result.set(null);
|
|
21199
|
+
this._missingSlotIds.set([]);
|
|
21200
|
+
}
|
|
21201
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeImportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
21202
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxPdfMergeImportComponent, isStandalone: true, selector: "dbx-pdf-merge-import", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, expectedSlotIds: { classPropertyName: "expectedSlotIds", publicName: "expectedSlotIds", isSignal: true, isRequired: false, transformFunction: null }, enforceExpectedSlots: { classPropertyName: "enforceExpectedSlots", publicName: "enforceExpectedSlots", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { imported: "imported", importFailed: "importFailed", missingSlots: "missingSlots" }, host: { properties: { "class.d-block": "activeSignal()", "class.dbx-mb3": "activeSignal()" }, classAttribute: "dbx-pdf-merge-import" }, ngImport: i0, template: `
|
|
21203
|
+
@if (activeSignal()) {
|
|
21204
|
+
@if (labelSignal(); as label) {
|
|
21205
|
+
<div class="dbx-pdf-merge-import-label">{{ label }}</div>
|
|
21206
|
+
}
|
|
21207
|
+
<dbx-file-upload [accept]="acceptSignal()" [multiple]="false" [mode]="modeSignal()" [hint]="hintSignal()" [text]="textSignal()" [icon]="iconSignal()" (filesChanged)="onFiles($event)"></dbx-file-upload>
|
|
21208
|
+
@if (errorSignal(); as error) {
|
|
21209
|
+
<div class="dbx-pdf-merge-import-error">
|
|
21210
|
+
<mat-icon class="dbx-pdf-merge-import-icon">error</mat-icon>
|
|
21211
|
+
<span>{{ error }}</span>
|
|
21212
|
+
</div>
|
|
21213
|
+
}
|
|
21214
|
+
@if (successLabelSignal(); as success) {
|
|
21215
|
+
<div class="dbx-pdf-merge-import-success">
|
|
21216
|
+
<mat-icon class="dbx-pdf-merge-import-icon">check_circle</mat-icon>
|
|
21217
|
+
<span>{{ success }}</span>
|
|
21218
|
+
</div>
|
|
21219
|
+
}
|
|
21220
|
+
@if (missingLabelSignal(); as missing) {
|
|
21221
|
+
<div class="dbx-pdf-merge-import-warning">
|
|
21222
|
+
<mat-icon class="dbx-pdf-merge-import-icon">warning</mat-icon>
|
|
21223
|
+
<span>{{ missing }}</span>
|
|
21224
|
+
</div>
|
|
21225
|
+
}
|
|
21226
|
+
}
|
|
21227
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: DbxFileUploadComponent, selector: "dbx-file-upload", inputs: ["config", "buttonStyle", "buttonDisplay", "mode", "text", "icon", "hint", "clickAreaToUpload"], outputs: ["filesChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
21228
|
+
}
|
|
21229
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeImportComponent, decorators: [{
|
|
21230
|
+
type: Component,
|
|
21231
|
+
args: [{
|
|
21232
|
+
selector: 'dbx-pdf-merge-import',
|
|
21233
|
+
template: `
|
|
21234
|
+
@if (activeSignal()) {
|
|
21235
|
+
@if (labelSignal(); as label) {
|
|
21236
|
+
<div class="dbx-pdf-merge-import-label">{{ label }}</div>
|
|
21237
|
+
}
|
|
21238
|
+
<dbx-file-upload [accept]="acceptSignal()" [multiple]="false" [mode]="modeSignal()" [hint]="hintSignal()" [text]="textSignal()" [icon]="iconSignal()" (filesChanged)="onFiles($event)"></dbx-file-upload>
|
|
21239
|
+
@if (errorSignal(); as error) {
|
|
21240
|
+
<div class="dbx-pdf-merge-import-error">
|
|
21241
|
+
<mat-icon class="dbx-pdf-merge-import-icon">error</mat-icon>
|
|
21242
|
+
<span>{{ error }}</span>
|
|
21243
|
+
</div>
|
|
21244
|
+
}
|
|
21245
|
+
@if (successLabelSignal(); as success) {
|
|
21246
|
+
<div class="dbx-pdf-merge-import-success">
|
|
21247
|
+
<mat-icon class="dbx-pdf-merge-import-icon">check_circle</mat-icon>
|
|
21248
|
+
<span>{{ success }}</span>
|
|
21249
|
+
</div>
|
|
21250
|
+
}
|
|
21251
|
+
@if (missingLabelSignal(); as missing) {
|
|
21252
|
+
<div class="dbx-pdf-merge-import-warning">
|
|
21253
|
+
<mat-icon class="dbx-pdf-merge-import-icon">warning</mat-icon>
|
|
21254
|
+
<span>{{ missing }}</span>
|
|
21255
|
+
</div>
|
|
21256
|
+
}
|
|
21257
|
+
}
|
|
21258
|
+
`,
|
|
21259
|
+
host: {
|
|
21260
|
+
class: 'dbx-pdf-merge-import',
|
|
21261
|
+
// Collapsed rather than merely emptied while inactive: without `d-block` the host is an empty
|
|
21262
|
+
// inline element of zero size, and dropping the margin keeps it from occupying layout.
|
|
21263
|
+
'[class.d-block]': 'activeSignal()',
|
|
21264
|
+
'[class.dbx-mb3]': 'activeSignal()'
|
|
21265
|
+
},
|
|
21266
|
+
imports: [MatIconModule, DbxFileUploadComponent],
|
|
21267
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
21268
|
+
standalone: true
|
|
21269
|
+
}]
|
|
21270
|
+
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], expectedSlotIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "expectedSlotIds", required: false }] }], enforceExpectedSlots: [{ type: i0.Input, args: [{ isSignal: true, alias: "enforceExpectedSlots", required: false }] }], imported: [{ type: i0.Output, args: ["imported"] }], importFailed: [{ type: i0.Output, args: ["importFailed"] }], missingSlots: [{ type: i0.Output, args: ["missingSlots"] }] } });
|
|
21271
|
+
|
|
19547
21272
|
const DEFAULT_UPLOAD_FOOTER_ICON = 'cloud_upload';
|
|
19548
21273
|
const DEFAULT_UPLOAD_FOOTER_TEXT = 'Upload';
|
|
19549
21274
|
const DEFAULT_UPLOAD_FOOTER_COLOR = 'primary';
|
|
@@ -19605,7 +21330,7 @@ class DbxPdfMergeUploadDialogComponent extends AbstractDialogDirective {
|
|
|
19605
21330
|
</div>
|
|
19606
21331
|
}
|
|
19607
21332
|
</dbx-dialog-content>
|
|
19608
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: DbxDialogContentDirective, selector: "dbx-dialog-content,[dbxDialogContent],.dbx-dialog-content", inputs: ["width"] }, { kind: "component", type: DbxDialogContentCloseComponent, selector: "dbx-dialog-content-close", inputs: ["padded"], outputs: ["close"] }, { kind: "component", type: DbxInjectionComponent, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: ["config", "template"] }, { kind: "component", type: DbxPdfMergeEditorComponent, selector: "dbx-pdf-merge-editor", inputs: ["accept", "multiple", "fileName", "showDownloadButton", "showPreviewButton", "downloadButton", "showAddFiles", "showFileList", "config"], outputs: ["entriesChanged"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
21333
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: DbxDialogContentDirective, selector: "dbx-dialog-content,[dbxDialogContent],.dbx-dialog-content", inputs: ["width"] }, { kind: "component", type: DbxDialogContentCloseComponent, selector: "dbx-dialog-content-close", inputs: ["padded"], outputs: ["close"] }, { kind: "component", type: DbxInjectionComponent, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: ["config", "template"] }, { kind: "component", type: DbxPdfMergeEditorComponent, selector: "dbx-pdf-merge-editor", inputs: ["accept", "multiple", "fileName", "showDownloadButton", "showPreviewButton", "downloadButton", "showAddFiles", "showFileList", "pageEditing", "sidecar", "config"], outputs: ["entriesChanged"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
19609
21334
|
}
|
|
19610
21335
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxPdfMergeUploadDialogComponent, decorators: [{
|
|
19611
21336
|
type: Component,
|
|
@@ -19808,5 +21533,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
19808
21533
|
* Generated bundle index. Do not edit.
|
|
19809
21534
|
*/
|
|
19810
21535
|
|
|
19811
|
-
export { APP_POPUP_MINIMIZED_WIDTH, APP_POPUP_NORMAL_HEIGHT, APP_POPUP_NORMAL_WIDTH, AbstractDbxClipboardDirective, AbstractDbxErrorWidgetComponent, AbstractDbxFileUploadComponent, AbstractDbxHelpWidgetDirective, AbstractDbxListAccordionViewDirective, AbstractDbxListGridViewDirective, AbstractDbxListViewDirective, AbstractDbxListWrapperDirective, AbstractDbxPartialPresetFilterMenuDirective, AbstractDbxSegueAnchorDirective, AbstractDbxSelectionListViewDirective, AbstractDbxSelectionListWrapperDirective, AbstractDbxValueListItemModifierDirective, AbstractDbxValueListViewDirective, AbstractDbxValueListViewItemComponent, AbstractDbxWidgetComponent, AbstractDialogDirective, AbstractFilterPopoverButtonDirective, AbstractPopoverDirective, AbstractPopoverRefDirective, AbstractPopoverRefWithEventsDirective, AbstractPopupDirective, AbstractPromptConfirmDirective, CompactContextStore, CompactMode, DBX_ACTION_SNACKBAR_DEFAULTS, DBX_ACTION_SNACKBAR_SERVICE_CONFIG, DBX_AVATAR_CONTEXT_DATA_TOKEN, DBX_COLOR_CUSTOM_BG_CSS_CLASS, DBX_COLOR_CUSTOM_TEXT_CSS_CLASS, DBX_CURATED_COLOR_COUNT, DBX_CURATED_COLOR_TEMPLATES, DBX_CURATED_COLOR_TEMPLATE_KEY_PREFIX, DBX_DARK_STYLE_CLASS_SUFFIX, DBX_HELP_WIDGET_ENTRY_DATA_TOKEN, DBX_LIST_ACCORDION_VIEW_COMPONENT_IMPORTS_AND_EXPORTS, DBX_LIST_GRID_VIEW_COMPONENT_IMPORTS_AND_EXPORTS, DBX_LIST_ITEM_DISABLE_RIPPLE_LIST_ITEM_MODIFIER_KEY, DBX_LIST_ITEM_IS_SELECTED_ITEM_MODIFIER_KEY, DBX_MODEL_VIEW_TRACKER_STORAGE_ACCESSOR_TOKEN, DBX_PDF_MERGE_EDITOR_CONFIG, DBX_PDF_MERGE_EDITOR_INITIAL_STATE, DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY, DBX_PDF_MERGE_ENCRYPTED_ERROR_MESSAGE, DBX_PROGRESS_BUTTON_GLOBAL_CONFIG, DBX_ROUTER_ANCHOR_COMPONENTS, DBX_ROUTER_VALUE_LIST_ITEM_MODIFIER_KEY, DBX_THEME_COLORS, DBX_THEME_COLORS_EXTRA, DBX_THEME_COLORS_EXTRA_SECONDARY, DBX_THEME_COLORS_MAIN, DBX_VALUE_LIST_VIEW_ITEM, DBX_WEB_FILE_PREVIEW_SERVICE_ENTRIES_TOKEN, DBX_WEB_FILE_PREVIEW_SERVICE_ZIP_COMPONENT_PRESET, DBX_WEB_FILE_PREVIEW_SERVICE_ZIP_PRESET_ENTRY, DBX_WEB_PAGE_TITLE_SERVICE_CONFIG, DEFAULT_DBX_CHIP_TONE, DEFAULT_DBX_DETACH_KEY, DEFAULT_DBX_ERROR_SNACKBAR_CONFIG, DEFAULT_DBX_HELP_VIEW_POPOVER_KEY, DEFAULT_DBX_LINKIFY_STRING_TYPE, DEFAULT_DBX_LIST_ACCORDION_VIEW_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_LIST_GRID_VIEW_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_LIST_ITEM_DISABLE_FUNCTION, DEFAULT_DBX_LIST_ITEM_IS_SELECTED_FUNCTION, DEFAULT_DBX_LIST_SCROLL_DISTANCE, DEFAULT_DBX_LIST_THROTTLE_SCROLL, DEFAULT_DBX_LIST_VIEW_META_ICON, DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING, DEFAULT_DBX_PROMPT_CONFIRM_DIALOG_CONFIG, DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_SIDENAV_MENU_ICON, DEFAULT_DBX_STYLE_CONFIG_TOKEN, DEFAULT_DBX_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_WEB_FILE_PREVIEW_SERVICE_DIALOG_WITH_COMPONENT_FUNCTION, DEFAULT_DBX_WEB_FILE_PREVIEW_SERVICE_PREVIEW_COMPONENT_FUNCTION, DEFAULT_ERROR_POPOVER_KEY, DEFAULT_ERROR_WIDGET_CODE, DEFAULT_FILTER_POPOVER_KEY, DEFAULT_IMAGE_BITMAP_TO_BLOB_ENCODER, DEFAULT_IMAGE_JPEG_QUALITY, DEFAULT_LIST_GRID_SIZE_CONFIG, DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_LOADING_PROGRESS_DIAMETER, DEFAULT_PDF_MERGE_ACCEPT, DEFAULT_SCREEN_MEDIA_SERVICE_CONFIG, DEFAULT_SNACKBAR_DIRECTIVE_DURATION, DEFAULT_TWO_COLUMNS_MIN_RIGHT_WIDTH, DEFAULT_VALUE_LIST_VIEW_CONTENT_COMPONENT_TRACK_BY_FUNCTION, DbxAccordionHeaderHeightDirective, DbxActionConfirmDirective, DbxActionDialogDirective, DbxActionErrorDirective, DbxActionKeyTriggerDirective, DbxActionLoadingContextDirective, DbxActionModule, DbxActionPopoverDirective, DbxActionSnackbarComponent, DbxActionSnackbarDirective, DbxActionSnackbarErrorDirective, DbxActionSnackbarModule, DbxActionSnackbarService, DbxActionTransitionSafetyDirective, DbxActionUIRouterTransitionSafetyDialogComponent, DbxAnchorComponent, DbxAnchorContentComponent, DbxAnchorIconComponent, DbxAnchorListComponent, DbxAngularRouterSegueAnchorComponent, DbxAvatarComponent, DbxAvatarViewComponent, DbxAvatarViewService, DbxAvatarViewServiceConfig, DbxBarDirective, DbxBarHeaderComponent, DbxBarLayoutModule, DbxBasicLoadingComponent, DbxBodyDirective, DbxButtonComponent, DbxButtonModule, DbxButtonSpacerDirective, DbxCardBoxComponent, DbxCardBoxContainerDirective, DbxCardBoxLayoutModule, DbxChipDirective, DbxChipListComponent, DbxClickToCopyTextComponent, DbxClickToCopyTextDirective, DbxColorDirective, DbxColorService, DbxColorServiceConfig, DbxColumnLayoutModule, DbxCompactDirective, DbxCompactLayoutModule, DbxContentBorderDirective, DbxContentBoxDirective, DbxContentContainerDirective, DbxContentDirective, DbxContentElevateDirective, DbxContentLayoutModule, DbxContentPageDirective, DbxContentPitDirective, DbxDetachContentComponent, DbxDetachControlButtonsComponent, DbxDetachController, DbxDetachControlsComponent, DbxDetachInitDirective, DbxDetachInteractionModule, DbxDetachOutletComponent, DbxDetachOverlayComponent, DbxDetachService, DbxDetachWindowState, DbxDetailBlockComponent, DbxDetailBlockHeaderComponent, DbxDialogContentCloseComponent, DbxDialogContentDirective, DbxDialogContentFooterComponent, DbxDialogInteractionModule, DbxDialogModule, DbxDownloadBlobButtonComponent, DbxDownloadTextViewComponent, DbxEmbedComponent, DbxErrorComponent, DbxErrorDefaultErrorWidgetComponent, DbxErrorDetailsComponent, DbxErrorPopoverComponent, DbxErrorSnackbarComponent, DbxErrorSnackbarService, DbxErrorViewComponent, DbxErrorWidgetService, DbxErrorWidgetViewComponent, DbxFileUploadActionCompatable, DbxFileUploadActionSyncDirective, DbxFileUploadAreaComponent, DbxFileUploadButtonComponent, DbxFileUploadComponent, DbxFilterInteractionModule, DbxFilterPopoverButtonComponent, DbxFilterPopoverComponent, DbxFilterWrapperComponent, DbxFlagComponent, DbxFlagLayoutModule, DbxFlagPromptComponent, DbxFlexGroupDirective, DbxFlexLayoutModule, DbxFlexSizeDirective, DbxHelpContextDirective, DbxHelpContextService, DbxHelpViewListComponent, DbxHelpViewListEntryComponent, DbxHelpViewPopoverButtonComponent, DbxHelpViewPopoverComponent, DbxHelpWidgetService, DbxHelpWidgetServiceConfig, DbxIconButtonComponent, DbxIconButtonModule, DbxIconItemComponent, DbxIconSpacerDirective, DbxIconTileComponent, DbxIconTileDirective, DbxIfSidenavDisplayModeDirective, DbxIframeComponent, DbxInjectionDialogComponent, DbxInteractionModule, DbxIntroActionSectionComponent, DbxLabelBlockComponent, DbxLayoutModule, DbxLinkComponent, DbxLinkifyComponent, DbxLinkifyService, DbxLinkifyServiceConfig, DbxListAccordionViewComponentImportsModule, DbxListComponent, DbxListEmptyContentComponent, DbxListGridViewComponentImportsModule, DbxListInternalContentDirective, DbxListItemAnchorModifierDirective, DbxListItemDisableRippleModifierDirective, DbxListItemIsSelectedModifierDirective, DbxListModifierModule, DbxListModule, DbxListTitleGroupDirective, DbxListView, DbxListViewMetaIconComponent, DbxListViewWrapper, DbxListWrapperComponentImportsModule, DbxLoadingComponent, DbxLoadingErrorDirective, DbxLoadingModule, DbxLoadingProgressComponent, DbxModelObjectStateService, actions as DbxModelStateActions, model_actions as DbxModelStateModelActions, DbxModelTrackerService, DbxModelTypesService, DbxModelViewTrackerStorage, DbxNavbarComponent, DbxNumberWithLimitComponent, DbxOneColumnComponent, DbxOneColumnLayoutModule, DbxPagebarComponent, DbxPartialPresetFilterListComponent, DbxPartialPresetFilterMenuComponent, DbxPdfMergeEditorComponent, DbxPdfMergeEditorFileUploadComponent, DbxPdfMergeEditorFileUploadHasStateDirective, DbxPdfMergeEditorFileUploadValidatorDirective, DbxPdfMergeEditorStore, DbxPdfMergeEditorStoreDirective, DbxPdfMergeEntryComponent, DbxPdfMergeListComponent, DbxPdfMergeUploadActionDirective, DbxPdfMergeUploadButtonDirective, DbxPdfMergeUploadDialogComponent, DbxPdfPreviewComponent, DbxPopoverCloseButtonComponent, DbxPopoverComponent, DbxPopoverComponentController, DbxPopoverContentComponent, DbxPopoverController, DbxPopoverControlsDirective, DbxPopoverCoordinatorComponent, DbxPopoverCoordinatorService, DbxPopoverHeaderComponent, DbxPopoverInteractionContentModule, DbxPopoverInteractionModule, DbxPopoverScrollContentDirective, DbxPopoverService, DbxPopupComponent, DbxPopupComponentController, DbxPopupContentComponent, DbxPopupControlButtonsComponent, DbxPopupController, DbxPopupControlsComponent, DbxPopupCoordinatorComponent, DbxPopupCoordinatorService, DbxPopupInteractionModule, DbxPopupService, DbxPopupWindowState, DbxPresetFilterListComponent, DbxPresetFilterMenuComponent, DbxProgressBarButtonComponent, DbxProgressButtonsModule, DbxProgressSpinnerButtonComponent, DbxPromptBoxDirective, DbxPromptComponent, DbxPromptConfirm, DbxPromptConfirmButtonDirective, DbxPromptConfirmComponent, DbxPromptConfirmDialogComponent, DbxPromptConfirmDirective, DbxPromptModule, DbxPromptPageComponent, DbxReadableErrorModule, DbxResizedDirective, DbxRouterAnchorModule, DbxRouterLayoutModule, DbxRouterSidenavModule, DbxRouterWebProviderConfig, DbxScreenMediaService, DbxScreenMediaServiceConfig, DbxSectionComponent, DbxSectionHeaderComponent, DbxSectionLayoutModule, DbxSectionPageComponent, DbxSelectionValueListViewComponent, DbxSelectionValueListViewComponentImportsModule, DbxSelectionValueListViewContentComponent, DbxSetStyleDirective, DbxSidenavButtonComponent, DbxSidenavComponent, DbxSidenavPageComponent, DbxSidenavPagebarComponent, DbxSpacerDirective, DbxStepBlockComponent, DbxStructureDirective, DbxStructureModule, DbxStyleBodyDirective, DbxStyleDirective, DbxStyleLayoutModule, DbxStyleService, DbxSubSectionComponent, DbxTextChipsComponent, DbxTextColorDirective, DbxTextModule, DbxTwoBlockComponent, DbxTwoColumnBackDirective, DbxTwoColumnColumnHeadDirective, DbxTwoColumnComponent, DbxTwoColumnContextDirective, DbxTwoColumnFullLeftDirective, DbxTwoColumnLayoutModule, DbxTwoColumnRightComponent, DbxTwoColumnSrefDirective, DbxTwoColumnSrefShowRightDirective, DbxUIRouterSegueAnchorComponent, DbxUnitedStatesAddressComponent, DbxValueListAccordionViewComponent, DbxValueListAccordionViewContentComponent, DbxValueListAccordionViewContentGroupComponent, DbxValueListGridSizeDirective, DbxValueListGridViewComponent, DbxValueListGridViewContentComponent, DbxValueListGridViewContentGroupComponent, DbxValueListItemModifier, DbxValueListItemModifierDirective, DbxValueListView, DbxValueListViewComponent, DbxValueListViewComponentImportsModule, DbxValueListViewContentComponent, DbxValueListViewContentGroupComponent, DbxValueListViewGroupDelegate, DbxWebFilePreviewComponent, DbxWebFilePreviewService, DbxWebModule, DbxWebPageTitleInfoDirective, DbxWebPageTitleService, DbxWidgetListGridComponent, DbxWidgetListGridViewComponent, DbxWidgetListGridViewItemComponent, DbxWidgetService, DbxWidgetViewComponent, DbxWindowKeyDownListenerDirective, DbxZipBlobPreviewComponent, DbxZipPreviewComponent, PDF_MERGE_RESULT_MIME_TYPE, PopoverPositionStrategy, PopupGlobalPositionStrategy, SCREEN_MEDIA_WIDTH_TYPE_SIZE_MAP, SIDE_NAV_DISPLAY_MODE_ORDER, SideNavDisplayMode, TRACK_BY_MODEL_ID, TRACK_BY_MODEL_KEY, TwoColumnsContextStore, UNKNOWN_ERROR_WIDGET_CODE, addConfigToValueListItems, allDbxModelViewTrackerEventModelKeys, allDbxModelViewTrackerEventSetModelKeys, buildPdfMergeEntry, buildPdfMergeEntrySync, catchErrorServerParams, classifyPdfMergeFile, compactModeFromInput, compareScreenMediaWidthTypes, compressImageFile, convertServerErrorParams, convertToPOJOServerErrorResponse, convertToServerErrorResponse, copyToClipboardFunction, dbxColorBackground, dbxCuratedColorConfigForString, dbxListAccordionViewComponentImportsAndExports, dbxListGridViewComponentImportsAndExports, dbxPresetFilterMenuButtonIconObservable, dbxPresetFilterMenuButtonTextObservable, dbxStyleClassCleanSuffix, dbxThemeColorCssToken, dbxThemeColorCssTokenVar, dbxThemeColorCssVariable, dbxThemeColorCssVariableVar, dbxValueListItemDecisionFunction, dbxValueListItemKeyForItemValue, dbxWebDefaultPageTitleDelegate, dbxZipBlobPreviewEntryTreeFromEntries, defaultDbxModelViewTrackerStorageAccessorFactory, defaultDbxValueListViewGroupDelegate, defaultDbxValueListViewGroupValuesFunction, disableRightClickInCdkBackdrop, fileAcceptFilterTypeStringArray, fileAcceptFunction, fileAcceptString, fileArrayAcceptMatchFunction, flattenAccordionGroups, formatPdfMergeEntrySize, index as fromDbxModel, injectCopyToClipboardFunction, injectCopyToClipboardFunctionWithSnackbarMessage, isDbxColorConfig, listItemModifier, makeDbxActionSnackbarDisplayConfigGeneratorFunction, mapCompactModeObs, mapValuesToValuesListItemConfigObs, mergePdfMergeEntries, index$1 as onDbxModel, openEmbedDialog, openIframeDialog, openPdfPreviewDialog, openZipPreviewDialog, overrideClickElementEffect, provideDbxDetachController, provideDbxFileUploadActionCompatable, provideDbxHelpServices, provideDbxLinkify, provideDbxListView, provideDbxListViewWrapper, provideDbxModelService, provideDbxPdfMergeEditorConfig, provideDbxPdfMergeEditorPreserveEntriesOnSlotDestroy, provideDbxProgressButtonGlobalConfig, provideDbxPromptConfirm, provideDbxRouterWebAngularRouterProviderConfig, provideDbxRouterWebUiRouterProviderConfig, provideDbxScreenMediaService, provideDbxStyleService, provideDbxValueListView, provideDbxValueListViewGroupDelegate, provideDbxValueListViewModifier, provideDbxWebFilePreviewServiceEntries, provideDbxWebPageTitleService, provideTwoColumnsContext, registerHelpContextKeysWithDbxHelpContextService, resizeSignal, resolveSideNavDisplayMode, sanitizeDbxDialogContentConfig, screenMediaWidthTypeIsActive, trackByModelKeyRef, trackByUniqueIdentifier, validatePdfMergeEntry };
|
|
21536
|
+
export { APP_POPUP_MINIMIZED_WIDTH, APP_POPUP_NORMAL_HEIGHT, APP_POPUP_NORMAL_WIDTH, AbstractDbxClipboardDirective, AbstractDbxErrorWidgetComponent, AbstractDbxFileUploadComponent, AbstractDbxHelpWidgetDirective, AbstractDbxListAccordionViewDirective, AbstractDbxListGridViewDirective, AbstractDbxListViewDirective, AbstractDbxListWrapperDirective, AbstractDbxPartialPresetFilterMenuDirective, AbstractDbxSegueAnchorDirective, AbstractDbxSelectionListViewDirective, AbstractDbxSelectionListWrapperDirective, AbstractDbxValueListItemModifierDirective, AbstractDbxValueListViewDirective, AbstractDbxValueListViewItemComponent, AbstractDbxWidgetComponent, AbstractDialogDirective, AbstractFilterPopoverButtonDirective, AbstractPopoverDirective, AbstractPopoverRefDirective, AbstractPopoverRefWithEventsDirective, AbstractPopupDirective, AbstractPromptConfirmDirective, CompactContextStore, CompactMode, DBX_ACTION_SNACKBAR_DEFAULTS, DBX_ACTION_SNACKBAR_SERVICE_CONFIG, DBX_AVATAR_CONTEXT_DATA_TOKEN, DBX_COLOR_CUSTOM_BG_CSS_CLASS, DBX_COLOR_CUSTOM_TEXT_CSS_CLASS, DBX_CURATED_COLOR_COUNT, DBX_CURATED_COLOR_TEMPLATES, DBX_CURATED_COLOR_TEMPLATE_KEY_PREFIX, DBX_DARK_STYLE_CLASS_SUFFIX, DBX_HELP_WIDGET_ENTRY_DATA_TOKEN, DBX_LIST_ACCORDION_VIEW_COMPONENT_IMPORTS_AND_EXPORTS, DBX_LIST_GRID_VIEW_COMPONENT_IMPORTS_AND_EXPORTS, DBX_LIST_ITEM_DISABLE_RIPPLE_LIST_ITEM_MODIFIER_KEY, DBX_LIST_ITEM_IS_SELECTED_ITEM_MODIFIER_KEY, DBX_MODEL_VIEW_TRACKER_STORAGE_ACCESSOR_TOKEN, DBX_PDF_MERGE_EDITOR_CONFIG, DBX_PDF_MERGE_EDITOR_INITIAL_STATE, DBX_PDF_MERGE_EDITOR_PRESERVE_ENTRIES_ON_SLOT_DESTROY, DBX_PDF_MERGE_ENCRYPTED_ERROR_MESSAGE, DBX_PDF_MERGE_ENCRYPTED_NOT_EDITABLE_MESSAGE, DBX_PDF_MERGE_IMPORT_ERROR_MESSAGES, DBX_PROGRESS_BUTTON_GLOBAL_CONFIG, DBX_ROUTER_ANCHOR_COMPONENTS, DBX_ROUTER_VALUE_LIST_ITEM_MODIFIER_KEY, DBX_THEME_COLORS, DBX_THEME_COLORS_EXTRA, DBX_THEME_COLORS_EXTRA_SECONDARY, DBX_THEME_COLORS_MAIN, DBX_VALUE_LIST_VIEW_ITEM, DBX_WEB_FILE_PREVIEW_SERVICE_ENTRIES_TOKEN, DBX_WEB_FILE_PREVIEW_SERVICE_ZIP_COMPONENT_PRESET, DBX_WEB_FILE_PREVIEW_SERVICE_ZIP_PRESET_ENTRY, DBX_WEB_PAGE_TITLE_SERVICE_CONFIG, DEFAULT_DBX_CHIP_TONE, DEFAULT_DBX_DETACH_KEY, DEFAULT_DBX_ERROR_SNACKBAR_CONFIG, DEFAULT_DBX_HELP_VIEW_POPOVER_KEY, DEFAULT_DBX_LINKIFY_STRING_TYPE, DEFAULT_DBX_LIST_ACCORDION_VIEW_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_LIST_GRID_VIEW_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_LIST_ITEM_DISABLE_FUNCTION, DEFAULT_DBX_LIST_ITEM_IS_SELECTED_FUNCTION, DEFAULT_DBX_LIST_SCROLL_DISTANCE, DEFAULT_DBX_LIST_THROTTLE_SCROLL, DEFAULT_DBX_LIST_VIEW_META_ICON, DEFAULT_DBX_PDF_MERGE_ENCRYPTED_HANDLING, DEFAULT_DBX_PDF_MERGE_PAGE_EDITING, DEFAULT_DBX_PDF_MERGE_RESTORE_IMPORT_ON_CLEAR, DEFAULT_DBX_PDF_MERGE_SIDECAR, DEFAULT_DBX_PROMPT_CONFIRM_DIALOG_CONFIG, DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_SIDENAV_MENU_ICON, DEFAULT_DBX_STYLE_CONFIG_TOKEN, DEFAULT_DBX_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_DBX_WEB_FILE_PREVIEW_SERVICE_DIALOG_WITH_COMPONENT_FUNCTION, DEFAULT_DBX_WEB_FILE_PREVIEW_SERVICE_PREVIEW_COMPONENT_FUNCTION, DEFAULT_ERROR_POPOVER_KEY, DEFAULT_ERROR_WIDGET_CODE, DEFAULT_FILTER_POPOVER_KEY, DEFAULT_IMAGE_BITMAP_TO_BLOB_ENCODER, DEFAULT_IMAGE_JPEG_QUALITY, DEFAULT_LIST_GRID_SIZE_CONFIG, DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE, DEFAULT_LOADING_PROGRESS_DIAMETER, DEFAULT_PDF_MERGE_ACCEPT, DEFAULT_PDF_MERGE_PAGE_GROUP_KEY, DEFAULT_SCREEN_MEDIA_SERVICE_CONFIG, DEFAULT_SNACKBAR_DIRECTIVE_DURATION, DEFAULT_TWO_COLUMNS_MIN_RIGHT_WIDTH, DEFAULT_VALUE_LIST_VIEW_CONTENT_COMPONENT_TRACK_BY_FUNCTION, DbxAccordionHeaderHeightDirective, DbxActionConfirmDirective, DbxActionDialogDirective, DbxActionErrorDirective, DbxActionKeyTriggerDirective, DbxActionLoadingContextDirective, DbxActionModule, DbxActionPopoverDirective, DbxActionSnackbarComponent, DbxActionSnackbarDirective, DbxActionSnackbarErrorDirective, DbxActionSnackbarModule, DbxActionSnackbarService, DbxActionTransitionSafetyDirective, DbxActionUIRouterTransitionSafetyDialogComponent, DbxAnchorComponent, DbxAnchorContentComponent, DbxAnchorIconComponent, DbxAnchorListComponent, DbxAngularRouterSegueAnchorComponent, DbxAvatarComponent, DbxAvatarViewComponent, DbxAvatarViewService, DbxAvatarViewServiceConfig, DbxBarDirective, DbxBarHeaderComponent, DbxBarLayoutModule, DbxBasicLoadingComponent, DbxBodyDirective, DbxButtonComponent, DbxButtonModule, DbxButtonSpacerDirective, DbxCardBoxComponent, DbxCardBoxContainerDirective, DbxCardBoxLayoutModule, DbxChipDirective, DbxChipListComponent, DbxClickToCopyTextComponent, DbxClickToCopyTextDirective, DbxColorDirective, DbxColorService, DbxColorServiceConfig, DbxColumnLayoutModule, DbxCompactDirective, DbxCompactLayoutModule, DbxContentBorderDirective, DbxContentBoxDirective, DbxContentContainerDirective, DbxContentDirective, DbxContentElevateDirective, DbxContentLayoutModule, DbxContentPageDirective, DbxContentPitDirective, DbxDetachContentComponent, DbxDetachControlButtonsComponent, DbxDetachController, DbxDetachControlsComponent, DbxDetachInitDirective, DbxDetachInteractionModule, DbxDetachOutletComponent, DbxDetachOverlayComponent, DbxDetachService, DbxDetachWindowState, DbxDetailBlockComponent, DbxDetailBlockHeaderComponent, DbxDialogContentCloseComponent, DbxDialogContentDirective, DbxDialogContentFooterComponent, DbxDialogInteractionModule, DbxDialogModule, DbxDownloadBlobButtonComponent, DbxDownloadTextViewComponent, DbxEmbedComponent, DbxErrorComponent, DbxErrorDefaultErrorWidgetComponent, DbxErrorDetailsComponent, DbxErrorPopoverComponent, DbxErrorSnackbarComponent, DbxErrorSnackbarService, DbxErrorViewComponent, DbxErrorWidgetService, DbxErrorWidgetViewComponent, DbxFileUploadActionCompatable, DbxFileUploadActionSyncDirective, DbxFileUploadAreaComponent, DbxFileUploadButtonComponent, DbxFileUploadComponent, DbxFilterInteractionModule, DbxFilterPopoverButtonComponent, DbxFilterPopoverComponent, DbxFilterWrapperComponent, DbxFlagComponent, DbxFlagLayoutModule, DbxFlagPromptComponent, DbxFlexGroupDirective, DbxFlexLayoutModule, DbxFlexSizeDirective, DbxHelpContextDirective, DbxHelpContextService, DbxHelpViewListComponent, DbxHelpViewListEntryComponent, DbxHelpViewPopoverButtonComponent, DbxHelpViewPopoverComponent, DbxHelpWidgetService, DbxHelpWidgetServiceConfig, DbxIconButtonComponent, DbxIconButtonModule, DbxIconItemComponent, DbxIconSpacerDirective, DbxIconTileComponent, DbxIconTileDirective, DbxIfSidenavDisplayModeDirective, DbxIframeComponent, DbxInjectionDialogComponent, DbxInteractionModule, DbxIntroActionSectionComponent, DbxLabelBlockComponent, DbxLayoutModule, DbxLinkComponent, DbxLinkifyComponent, DbxLinkifyService, DbxLinkifyServiceConfig, DbxListAccordionViewComponentImportsModule, DbxListComponent, DbxListEmptyContentComponent, DbxListGridViewComponentImportsModule, DbxListInternalContentDirective, DbxListItemAnchorModifierDirective, DbxListItemDisableRippleModifierDirective, DbxListItemIsSelectedModifierDirective, DbxListModifierModule, DbxListModule, DbxListTitleGroupDirective, DbxListView, DbxListViewMetaIconComponent, DbxListViewWrapper, DbxListWrapperComponentImportsModule, DbxLoadingComponent, DbxLoadingErrorDirective, DbxLoadingModule, DbxLoadingProgressComponent, DbxModelObjectStateService, actions as DbxModelStateActions, model_actions as DbxModelStateModelActions, DbxModelTrackerService, DbxModelTypesService, DbxModelViewTrackerStorage, DbxNavbarComponent, DbxNumberWithLimitComponent, DbxOneColumnComponent, DbxOneColumnLayoutModule, DbxPagebarComponent, DbxPartialPresetFilterListComponent, DbxPartialPresetFilterMenuComponent, DbxPdfMergeEditorComponent, DbxPdfMergeEditorFileUploadComponent, DbxPdfMergeEditorFileUploadHasStateDirective, DbxPdfMergeEditorFileUploadValidatorDirective, DbxPdfMergeEditorStore, DbxPdfMergeEditorStoreDirective, DbxPdfMergeEntryComponent, DbxPdfMergeImportComponent, DbxPdfMergeListComponent, DbxPdfMergePageComponent, DbxPdfMergePageListComponent, DbxPdfMergeUploadActionDirective, DbxPdfMergeUploadButtonDirective, DbxPdfMergeUploadDialogComponent, DbxPdfPreviewComponent, DbxPopoverCloseButtonComponent, DbxPopoverComponent, DbxPopoverComponentController, DbxPopoverContentComponent, DbxPopoverController, DbxPopoverControlsDirective, DbxPopoverCoordinatorComponent, DbxPopoverCoordinatorService, DbxPopoverHeaderComponent, DbxPopoverInteractionContentModule, DbxPopoverInteractionModule, DbxPopoverScrollContentDirective, DbxPopoverService, DbxPopupComponent, DbxPopupComponentController, DbxPopupContentComponent, DbxPopupControlButtonsComponent, DbxPopupController, DbxPopupControlsComponent, DbxPopupCoordinatorComponent, DbxPopupCoordinatorService, DbxPopupInteractionModule, DbxPopupService, DbxPopupWindowState, DbxPresetFilterListComponent, DbxPresetFilterMenuComponent, DbxProgressBarButtonComponent, DbxProgressButtonsModule, DbxProgressSpinnerButtonComponent, DbxPromptBoxDirective, DbxPromptComponent, DbxPromptConfirm, DbxPromptConfirmButtonDirective, DbxPromptConfirmComponent, DbxPromptConfirmDialogComponent, DbxPromptConfirmDirective, DbxPromptModule, DbxPromptPageComponent, DbxReadableErrorModule, DbxResizedDirective, DbxRouterAnchorModule, DbxRouterLayoutModule, DbxRouterSidenavModule, DbxRouterWebProviderConfig, DbxScreenMediaService, DbxScreenMediaServiceConfig, DbxSectionComponent, DbxSectionHeaderComponent, DbxSectionLayoutModule, DbxSectionPageComponent, DbxSelectionValueListViewComponent, DbxSelectionValueListViewComponentImportsModule, DbxSelectionValueListViewContentComponent, DbxSetStyleDirective, DbxSidenavButtonComponent, DbxSidenavComponent, DbxSidenavPageComponent, DbxSidenavPagebarComponent, DbxSpacerDirective, DbxStepBlockComponent, DbxStructureDirective, DbxStructureModule, DbxStyleBodyDirective, DbxStyleDirective, DbxStyleLayoutModule, DbxStyleService, DbxSubSectionComponent, DbxTextChipsComponent, DbxTextColorDirective, DbxTextModule, DbxTwoBlockComponent, DbxTwoColumnBackDirective, DbxTwoColumnColumnHeadDirective, DbxTwoColumnComponent, DbxTwoColumnContextDirective, DbxTwoColumnFullLeftDirective, DbxTwoColumnLayoutModule, DbxTwoColumnRightComponent, DbxTwoColumnSrefDirective, DbxTwoColumnSrefShowRightDirective, DbxUIRouterSegueAnchorComponent, DbxUnitedStatesAddressComponent, DbxValueListAccordionViewComponent, DbxValueListAccordionViewContentComponent, DbxValueListAccordionViewContentGroupComponent, DbxValueListGridSizeDirective, DbxValueListGridViewComponent, DbxValueListGridViewContentComponent, DbxValueListGridViewContentGroupComponent, DbxValueListItemModifier, DbxValueListItemModifierDirective, DbxValueListView, DbxValueListViewComponent, DbxValueListViewComponentImportsModule, DbxValueListViewContentComponent, DbxValueListViewContentGroupComponent, DbxValueListViewGroupDelegate, DbxWebFilePreviewComponent, DbxWebFilePreviewService, DbxWebModule, DbxWebPageTitleInfoDirective, DbxWebPageTitleService, DbxWidgetListGridComponent, DbxWidgetListGridViewComponent, DbxWidgetListGridViewItemComponent, DbxWidgetService, DbxWidgetViewComponent, DbxWindowKeyDownListenerDirective, DbxZipBlobPreviewComponent, DbxZipPreviewComponent, PDF_MERGE_PAGE_ID_SEPARATOR, PDF_MERGE_PAGE_TAG_KEY, PDF_MERGE_RESULT_MIME_TYPE, PDF_MERGE_SIDECAR_DESCRIPTION, PDF_MERGE_SIDECAR_FILE_NAME, PDF_MERGE_SIDECAR_VERSION, PopoverPositionStrategy, PopupGlobalPositionStrategy, SCREEN_MEDIA_WIDTH_TYPE_SIZE_MAP, SIDE_NAV_DISPLAY_MODE_ORDER, SideNavDisplayMode, TRACK_BY_MODEL_ID, TRACK_BY_MODEL_KEY, TwoColumnsContextStore, UNKNOWN_ERROR_WIDGET_CODE, addConfigToValueListItems, allDbxModelViewTrackerEventModelKeys, allDbxModelViewTrackerEventSetModelKeys, asPdfMergeFile, attachPdfMergeSidecar, buildPdfMergeEntriesFromSidecar, buildPdfMergeEntry, buildPdfMergeEntrySync, buildPdfMergePagePlan, catchErrorServerParams, classifyPdfMergeFile, compactModeFromInput, compareScreenMediaWidthTypes, compressImageFile, convertServerErrorParams, convertToPOJOServerErrorResponse, convertToServerErrorResponse, copyToClipboardFunction, dbxColorBackground, dbxCuratedColorConfigForString, dbxListAccordionViewComponentImportsAndExports, dbxListGridViewComponentImportsAndExports, dbxPresetFilterMenuButtonIconObservable, dbxPresetFilterMenuButtonTextObservable, dbxStyleClassCleanSuffix, dbxThemeColorCssToken, dbxThemeColorCssTokenVar, dbxThemeColorCssVariable, dbxThemeColorCssVariableVar, dbxValueListItemDecisionFunction, dbxValueListItemKeyForItemValue, dbxWebDefaultPageTitleDelegate, dbxZipBlobPreviewEntryTreeFromEntries, defaultDbxModelViewTrackerStorageAccessorFactory, defaultDbxValueListViewGroupDelegate, defaultDbxValueListViewGroupValuesFunction, disableRightClickInCdkBackdrop, entryIdForPdfMergePageId, fileAcceptFilterTypeStringArray, fileAcceptFunction, fileAcceptString, fileArrayAcceptMatchFunction, flattenAccordionGroups, formatPdfMergeEntrySize, index as fromDbxModel, injectCopyToClipboardFunction, injectCopyToClipboardFunctionWithSnackbarMessage, isDbxColorConfig, listItemModifier, makeDbxActionSnackbarDisplayConfigGeneratorFunction, makePdfMergePageId, makePdfMergeSidecar, makePdfMergeSidecarPageTag, mapCompactModeObs, mapValuesToValuesListItemConfigObs, mergePdfMergeEntries, index$1 as onDbxModel, openEmbedDialog, openIframeDialog, openPdfPreviewDialog, openZipPreviewDialog, overrideClickElementEffect, pdfMergePageGroupKeyForSlotId, provideDbxDetachController, provideDbxFileUploadActionCompatable, provideDbxHelpServices, provideDbxLinkify, provideDbxListView, provideDbxListViewWrapper, provideDbxModelService, provideDbxPdfMergeEditorConfig, provideDbxPdfMergeEditorPreserveEntriesOnSlotDestroy, provideDbxProgressButtonGlobalConfig, provideDbxPromptConfirm, provideDbxRouterWebAngularRouterProviderConfig, provideDbxRouterWebUiRouterProviderConfig, provideDbxScreenMediaService, provideDbxStyleService, provideDbxValueListView, provideDbxValueListViewGroupDelegate, provideDbxValueListViewModifier, provideDbxWebFilePreviewServiceEntries, provideDbxWebPageTitleService, provideTwoColumnsContext, readPdfMergeEntryPageMetas, readPdfMergePageTag, readPdfMergeSidecar, registerHelpContextKeysWithDbxHelpContextService, resizeSignal, resolveSideNavDisplayMode, sanitizeDbxDialogContentConfig, screenMediaWidthTypeIsActive, splitPdfMergeSidecarDocuments, trackByModelKeyRef, trackByUniqueIdentifier, validatePdfMergeEntry, writePdfMergePageTag };
|
|
19812
21537
|
//# sourceMappingURL=dereekb-dbx-web.mjs.map
|