Files
dc/client/src/app/editor/editor.component.html
T
YuryShkoda 022981390e
Build / Build-and-ng-test (pull_request) Failing after 52s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m6s
chore(deps): upgrade Angular 19 -> 21 to resolve audit CVEs
Bumps @angular/* to 21 (passing through 20 as a transient step, since
vendored Clarity has no Angular-20-compatible release) and
@typescript-eslint to 8.65.0 for TS 5.9 compat. Applies the required
ng update migrations, including *ngIf/*ngFor/*ngSwitch -> control-flow
syntax across 37 templates.
2026-07-22 14:56:20 +03:00

979 lines
32 KiB
HTML

<main class="content-area d-flex clr-flex-column">
<clr-modal
appFileDrop
(fileOver)="fileOverBase($event)"
[uploader]="uploader"
(fileDrop)="attachFile($event, true)"
[clrModalSize]="'xl'"
[clrModalStaticBackdrop]="false"
[clrModalClosable]="excelUploadState === 'Validating-DQ'"
[(clrModalOpen)]="showUploadModal"
class="relative"
>
<h3 class="modal-title">Upload File</h3>
<div class="modal-body">
<div class="drop-area">
<span>Drop file anywhere to upload!</span>
</div>
<div class="clr-col-md-12">
<!-- <div
class="card-header"
style="display: flex; flex-direction: column; justify-content: center"
>
<div
ng2FileDrop
*ngIf="!excelFileParsing"
[class.nv-file-over]="hasBaseDropZoneOver"
(fileOver)="fileOverBase($event)"
[uploader]="uploader"
class="well my-drop-zone"
(onFileDrop)="getFileDesc($event, true)"
>
<p class="file-drop-text">Drop File Here !</p>
</div>
</div> -->
<div class="clr-row card-block mt-15 d-flex justify-content-between">
<div class="clr-col-md-auto">
<div class="encoding-block">
<clr-radio-container class="mt-0" clrInline>
<clr-radio-wrapper>
<input
type="radio"
clrRadio
value="UTF-8"
name="encoding"
[(ngModel)]="encoding"
[disabled]="filename != ''"
/>
<label>UTF-8</label>
</clr-radio-wrapper>
<clr-radio-wrapper>
<input
type="radio"
clrRadio
value="WLATIN1"
name="encoding"
[(ngModel)]="encoding"
[disabled]="filename != ''"
/>
<label>WLATIN1</label>
</clr-radio-wrapper>
</clr-radio-container>
</div>
</div>
@if (!excelFileParsing && !uploadLoading) {
<div class="clr-col-md-3 filterBtn">
<span class="filterBtn w-100">
<label
for="file-upload"
class="btn btn-sm btn-outline profile-buttons w-100"
>
Browse
</label>
</span>
<input
hidden
#fileUploadInput
id="file-upload"
type="file"
appFileSelect
[uploader]="uploader"
(change)="attachFile($event)"
/>
</div>
}
@if (!excelFileParsing && !uploadLoading) {
<div class="clr-col-md-3 editBtn">
<button
[disabled]="true"
class="btnView btn btn-sm btn-success profile-buttons w-100"
(click)="uploadParsedFiles()"
>
Upload
</button>
</div>
}
<div class="clr-col-md-auto">
@if (excelUploadState !== 'Validating') {
<button
type="button"
class="btn btn-primary mr-0"
(click)="showUploadModal = false"
[disabled]="excelFileParsing || uploadLoading"
>
Close
</button>
}
@if (excelUploadState === 'Validating-DQ') {
<button
type="button"
class="btn btn-primary mr-0"
(click)="showUploadModal = false"
>
Skip
</button>
}
</div>
@if (excelFileParsing) {
<div class="excel-parsing clr-col-md-12">
<app-upload-stater #uploadStater></app-upload-stater>
</div>
}
</div>
</div>
</div>
<div class="modal-footer">
<!-- <button type="button" class="btn btn-outline" (click)="showUploadModal = false">Cancel</button> -->
<!-- <button type="button" class="btn btn-primary" (click)="showUploadModal = false">Close</button> -->
</div>
</clr-modal>
@if (currentEditRecordIndex > -1) {
<app-edit-record
[currentRecord]="currentEditRecord"
[recordAction]="recordAction"
[libds]="libds"
[queryFilter]="queryFilter"
[filter]="filter"
[submitLoading]="submitLoading"
[headerPks]="headerPks"
[cellValidation]="cellValidation"
[currentRecordIndex]="currentEditRecordIndex"
[currentRecordLoadings]="currentEditRecordLoadings"
[currentRecordErrors]="currentEditRecordErrors"
[currentRecordValidator]="currentEditRecordValidator"
(onRecordChange)="confirmRecordEdit()"
(onRecordInputFocused)="onRecordInputFocus($event)"
(onRecordEditClose)="closeRecordEdit()"
(onNextRecord)="onNextRecord()"
(onPreviousRecord)="onPreviousRecord()"
></app-edit-record>
}
<div #ht class="clr-flex-1">
@if (!validationDone && submit) {
<div class="progresStatic progress loop">
<progress></progress>
</div>
}
<div
appDragNdrop
(fileDraggedOver)="onShowUploadModal()"
class="card border-0 box-shadow-none-i position-relative h-100 d-flex clr-flex-column background-transparent"
>
<div
class="card-header clr-row buttonBar headerBar clr-flex-md-row clr-justify-content-center clr-justify-content-lg-end"
>
@if (tableTrue && !embed) {
<div class="clr-col-12 clr-col-md-3 clr-col-lg-4 backBtn">
<span
class="btn icon-collapse btn-sm btn-icon btn-dimmed"
[routerLink]="['/home']"
>
<clr-icon
aria-hidden="true"
shape="caret"
dir="left"
size="20"
></clr-icon>
<span class="text">Back to table selection</span>
</span>
<span
(click)="viewboxManager()"
class="btn icon-collapse btn-sm btn-icon btn-dimmed viewbox-open"
>
<clr-icon
aria-hidden="true"
shape="view-cards"
size="20"
></clr-icon>
<span class="text">Viewboxes</span>
</span>
</div>
}
<div
class="clr-col-12 clr-col-md-5 clr-col-lg-4 d-flex flex-column align-items-center"
[class.clr-col-lg-12]="!tableTrue"
>
<h4
*ngVar="{
libName: (libds?.split('.'))![0],
tableName: (libds?.split('.'))![1]
} as libdsParsed"
class="editor-title text-center mt-0"
>
<clr-tooltip>
<clr-icon
clrTooltipTrigger
(click)="datasetInfo = true"
shape="info-circle"
aria-label="View dataset meta info"
class="is-highlight cursor-pointer"
size="24"
></clr-icon>
@if (libdsParsed.tableName.includes('-FC')) {
<clr-icon
aria-hidden="true"
shape="bolt"
class="color-yellow"
></clr-icon>
}
<span clrTooltipTrigger>
{{ libdsParsed.libName }}.<a
class="mr-10 view-table"
[routerLink]="'/view/data/' + libds!"
>{{ libdsParsed.tableName.replace('-FC', '') }}</a
>
</span>
@if (this.dsNote && this.dsNote.length > 0) {
<clr-tooltip-content
clrPosition="bottom-left"
clrSize="lg"
*clrIfOpen
>
{{ this.dsNote }}
</clr-tooltip-content>
}
</clr-tooltip>
@if (dataSource) {
@if (!zeroFilterRows) {
({{ dataSource.length | thousandSeparator: ',' }}
{{ dataSource.length === 1 ? 'row' : 'rows' }}, {{ cols.length
}}{{ cols.length === 1 ? ' col' : ' cols' }})
}
@if (zeroFilterRows) {
(0 rows)
}
}
</h4>
</div>
@if (tableTrue) {
<div class="clr-col-12 clr-col-md-4 clr-col-lg-4 btnCtrl">
@if (hotTable.readOnly && !uploadPreview) {
@if (!isVaEmbed) {
<button
type="button"
class="btnView btn icon-collapse btn-sm btn-icon btn-block btn-dimmed"
(click)="openQb()"
>
<clr-icon aria-hidden="true" shape="filter"></clr-icon>
<span class="text">Filter</span>
</button>
}
<button
type="button"
class="btn icon-collapse btn-sm btn-primary btn-block"
(click)="editTable()"
>
<clr-icon aria-hidden="true" shape="note"></clr-icon>
<span class="text">Edit</span>
</button>
@if (!columnLevelSecurityFlag) {
<button
(click)="onShowUploadModal()"
type="button"
class="btn icon-collapse btn-sm btn-success btn-block mr-0"
>
<clr-icon aria-hidden="true" shape="upload"></clr-icon>
<span class="text">Upload</span>
</button>
}
}
@if (!hotTable.readOnly && !uploadPreview) {
<button
type="button"
class="btn btn-sm btn-icon btn-outline-danger"
(click)="cancelEdit()"
>
<clr-icon aria-hidden="true" shape="times"></clr-icon>
<span>Cancel</span>
</button>
<clr-tooltip>
<button
clrTooltipTrigger
type="button"
class="btn btn-sm btn-success"
[clrLoading]="addingNewRow"
[class.dc-locked-control]="restrictions.restrictAddRow"
(click)="!restrictions.restrictAddRow ? addRow() : ''"
>
<clr-icon aria-hidden="true" shape="plus" size="16"></clr-icon
>Add Row
</button>
<clr-tooltip-content
clrPosition="bottom-left"
clrSize="lg"
*clrIfOpen
>
@if (restrictions.restrictAddRow) {
<span
>To unlock more than
{{ licenceState.value.editor_rows_allowed }}
{{
licenceState.value.editor_rows_allowed === 1
? 'row'
: 'rows'
}}, contact support&#64;datacontroller.io</span
>
}
</clr-tooltip-content>
</clr-tooltip>
<button
type="button"
class="btn btn-sm btn-primary"
(click)="checkSave()"
>
<clr-icon aria-hidden="true" shape="check" size="20"></clr-icon
>Submit
</button>
}
@if (uploadPreview) {
<button
type="button"
class="btn btn-sm btn-icon btn-outline-danger btn-upload-preview"
(click)="discardSourceFile = true"
>
<clr-icon aria-hidden="true" shape="times"></clr-icon>
<span>Discard file</span>
</button>
@if (hotTable.readOnly) {
<button
type="button"
class="btn btn-sm btn-primary btn-upload-preview"
(click)="manualFileEditModal = true"
>
<clr-icon aria-hidden="true" shape="note"></clr-icon>
<span>Edit</span>
</button>
}
<button
type="button"
class="btn btn-sm btn-success preview-submit"
(click)="submitExcel()"
[clrLoading]="uploadLoading"
>
<clr-icon aria-hidden="true" shape="check" size="20"></clr-icon>
Submit
</button>
}
</div>
}
@if (!['', ' '].includes(queryText)) {
<div class="clr-col-md-12 infoBar">
<span
>FILTER :
<b>{{ queryText }}</b>
</span>
</div>
}
</div>
<div
class="card-block p-0 clr-flex-1 background-transparent"
[ngClass]="{ headerBarFilter: !['', ' '].includes(queryText) }"
>
@if (!tableTrue) {
<div class="card-text">
<div
class="h-70vh d-flex justify-content-center flex-column align-items-center"
>
@if (!getdataError) {
<span class="spinner"> Loading... </span>
<div class="mt-10">
<p cds-text="section">Loading table</p>
</div>
}
@if (getdataError) {
<span>
<clr-icon
aria-hidden="true"
shape="error-standard"
class="error-icon"
></clr-icon>
</span>
<div class="mt-10">
<p cds-text="section">Loading table error</p>
</div>
}
</div>
</div>
}
<div
class="card-text hotEditor h-100 d-flex clr-flex-column background-transparent"
>
<!-- <div *ngIf="generateEditRecordUrlLoading" class="edit-record-spinner">
<span class="spinner">
Loading...
</span>
</div> -->
<!--{{ submit }}-->
<!--<hr>-->
<!--{{ validationDone }}-->
<div class="hot-wrapper clr-flex-1">
<hot-table
#hotInstance
id="hotTable"
class="edit-hot"
[class.hidden]="hotTable.hidden"
[data]="hotTable.data"
[settings]="hotTableSettings"
>
</hot-table>
</div>
<div>
@if (
tableTrue && !restrictions.removeAddRecordButton && !isVaEmbed
) {
<clr-tooltip>
<button
clrTooltipTrigger
type="button"
class="btn btn-sm btn-success mt-5-i"
[class.dc-locked-control]="restrictions.restrictAddRow"
(click)="
!restrictions.restrictAddRow ? addRecordButtonClick() : ''
"
>
<clr-icon
aria-hidden="true"
shape="plus"
size="16"
></clr-icon>
Add Record
</button>
<clr-tooltip-content
clrPosition="top-right"
clrSize="lg"
*clrIfOpen
>
@if (restrictions.restrictAddRow) {
<span
>To unlock more than
{{ licenceState.value.editor_rows_allowed }}
{{
licenceState.value.editor_rows_allowed === 1
? 'row'
: 'rows'
}}, contact support&#64;datacontroller.io</span
>
}
</clr-tooltip-content>
</clr-tooltip>
}
<!-- VA data-driven content mode: filter controls only. Edit/Submit
use the normal top toolbar (same as embed=true). VA opens
read-only and filters live with the report; filtering
repopulates the grid, so it is held while editing (change shown
pending) and resumes on return to read-only. -->
@if (isVaEmbed) {
<div class="va-filter-controls w-100 mt-2-i">
<!-- Read-only: auto-apply toggle + manual Apply (confirm mode).
Hidden while editing (filtering is held). -->
@if (hotTable.readOnly) {
<label class="va-toggle">
<input
type="checkbox"
[checked]="vaAutoApply"
(change)="toggleVaAutoApply()"
/>
Auto-apply VA filters
</label>
@if (!vaAutoApply) {
<button
[disabled]="
vaFilterStatus !== 'pending' &&
vaFilterStatus !== 'loading'
"
type="button"
class="btn btn-sm btn-primary"
(click)="applyPendingVaFilter()"
>
Apply filter
</button>
}
}
<!-- Edit mode: filtering is held so edits aren't wiped. -->
@if (!hotTable.readOnly) {
<span class="va-disabled-text">
VA filtering paused while editing
</span>
}
<!-- Persistent live region (shown in both modes) so a filter
change that arrives while editing is announced as pending. -->
<span class="va-filter-status" role="status" aria-live="polite">
@if (vaFilterStatus === 'pending') {
<span class="va-pending-text"> Filter change pending </span>
}
@if (vaFilterStatus === 'loading') {
<span class="va-loading-text"> Loading filter… </span>
}
</span>
</div>
}
@if (
licenceState.value.editor_rows_allowed !== Infinity &&
dataSource &&
dataSource.length > licenceState.value.editor_rows_allowed
) {
<p class="mt-2-i w-100 text-center">
To display more than
{{ licenceState.value.editor_rows_allowed }} rows, contact
<contact-link />
</p>
}
</div>
<!--<hr>
<hot-table hotId="hotInstance" id="hotTable">
</hot-table>-->
@if (submit) {
<div class="modal">
<!-- && validationDone should be in constraint, but validation is not correct on front end so it is comment -->
<!-- TODO: fix validation in line above -->
<div
class="modal-dialog modal-lg"
role="dialog"
aria-hidden="true"
>
<div class="modal-content">
<div class="modal-header">
<button aria-label="Close" class="close" type="button">
<clr-icon aria-hidden="true" shape="close"></clr-icon>
</button>
<h3 class="modal-title">
Submit for Approval ({{ rowsChanged.rowsAdded }} added,
{{ rowsChanged.rowsUpdated }} updated,
{{ rowsChanged.rowsDeleted }} deleted)
</h3>
</div>
<div class="modal-body">
@if (
totalRowsChanged > licenceState.value.submit_rows_limit &&
licenceState.value.submit_rows_limit !== Infinity
) {
<span class="d-block w-100 text-center color-red"
>Only
{{ licenceState.value.submit_rows_limit }}
{{
licenceState.value.submit_rows_limit === 1
? 'row'
: 'rows'
}}
will be submitted. To remove the restriction, contact
support&#64;datacontroller.io</span
>
}
@if (tableTrue) {
<div class="clr-offset-md-2 clr-col-md-8">
<div class="text-area-full-width">
<label for="formFields_8" class="mb-5 d-block"
>Message</label
>
<textarea
clrTextarea
[(ngModel)]="message"
[disabled]="!validationDone"
tabindex="0"
[value]="
!validationDone
? 'Please wait while we validate ' +
cols.length * modifedRowsIndexes.length +
' cells.'
: ''
"
class="submit-reason"
type="text"
id="formFields_8"
></textarea>
</div>
<!-- TODO:approvers list -->
<!-- <div>
<div class="col-md-12" style="max-height: 150px;overflow: auto;">
<ul class="list">
<li *ngFor="let approve of approvers">{{approve}}</li>
</ul>
</div>
</div> -->
</div>
}
</div>
<div class="modal-footer justify-content-center-i">
<button
id="submitBtn"
[disabled]="!validationDone"
type="submit"
class="btn btn-sm btn-success-outline m-0"
tabindex="0"
(click)="saveTable(hotTable.data)"
>
Submit
</button>
<button
id="cancelSubmitBtn"
type="button"
class="btn btn-sm btn-outline"
tabindex="0"
(click)="
cancelSubmit(); submit = false; validationDone = 0
"
>
Cancel
</button>
</div>
</div>
</div>
</div>
}
<clr-modal [(clrModalOpen)]="submitLimitNotice">
<h3 class="modal-title">Notice</h3>
<div class="modal-body">
<p class="m-0">
Due to current licence, only
{{ licenceState.value.submit_rows_limit }} rows in a file will
be submitted. To remove the restriction, contact
support&#64;datacontroller.io
</p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-sm btn-primary"
(click)="submitLimitNotice = false"
>
Cancel
</button>
<button
type="button"
class="btn btn-sm btn-primary"
(click)="uploadParsedFiles(); submitLimitNotice = false"
>
Submit
</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="badEdit">
<h3 class="modal-title">{{ badEditTitle || 'Error' }}</h3>
<div class="modal-body">
<p>{{ badEditCause }}</p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-sm btn-primary"
(click)="badEdit = false"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="pkNull">
<h3 class="modal-title">Error</h3>
<div class="modal-body">
<p>
Null values or incorrect data types cannot be submitted for
Primary Key columns
</p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
(click)="pkNull = false; badEdit = false"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="noPkNull">
<h3 class="modal-title">Error</h3>
<div class="modal-body">
<p>Incorrect data types in non-Primary Key columns</p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
(click)="noPkNull = false; badEdit = false"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal class="duplicate-keys-modal" [(clrModalOpen)]="pkDups">
<h3 class="modal-title">Error</h3>
<div class="modal-body">
<p>
Duplicate values cannot be submitted for Primary Key columns
</p>
<p>Duplicates found:</p>
@for (
duplicatePkIndex of duplicatePkIndexes;
track duplicatePkIndex;
let i = $index
) {
<span class="d-block">
On row {{ duplicatePkIndex + 1 }}:
{{
pkFields[duplicatePkIndex].replace('|', '')
| pkSpaceSeparate
}}
</span>
}
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
(click)="pkDups = false"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="tooLong">
<h3 class="modal-title">Error</h3>
<div class="modal-body">
<p>
The following columns have values that exceed length limits:
</p>
@for (cell of exceedCells; track cell) {
<p>{{ cell.col }}({{ cell.len }}), {{ cell.val }}</p>
}
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
(click)="tooLong = false; cleanExceed()"
>
Ok
</button>
</div>
</clr-modal>
</div>
</div>
<!-- <div class="card-footer" style="display:flex; justify-content: center;">
</div> -->
</div>
</div>
</main>
@if (nullVariables) {
<div class="modal z-index-highest">
<div class="modal-dialog" role="dialog" aria-hidden="true">
<div class="modal-content">
<div class="modal-header">
<button aria-label="Close" class="close" type="button">
<clr-icon aria-hidden="true" shape="close"></clr-icon>
</button>
<h3 class="modal-title">Error</h3>
</div>
<div class="modal-body">
<p>You cannot submit empty clauses</p>
</div>
<div class="modal-footer">
<button
class="btn btn-sm btn-primary"
(click)="nullVariables = false"
type="button"
>
Ok
</button>
</div>
</div>
</div>
</div>
}
<clr-modal
[(clrModalOpen)]="filter"
[clrModalSize]="'xl'"
[clrModalClosable]="false"
class="filter-modal"
>
<h3 class="modal-title center text-center color-darker-gray">
Filter for table:
<span> {{ libds }} </span>
</h3>
<div class="modal-body">
@if (filter) {
<app-query #queryFilter></app-query>
}
</div>
<div class="modal-footer">
<button (click)="resetFilter()" type="button" class="btn btn-sm btn-link">
reset filter
</button>
<button
type="button"
class="btn btn-sm btn-outline"
(click)="filter = false"
>
Cancel
</button>
<button
[clrLoading]="submitLoading"
type="button"
class="btn btn-sm btn-success-outline"
(click)="sendClause()"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="queryErr">
<h3 class="modal-title">Error</h3>
<div class="modal-body">
<p>{{ queryErrMessage }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="queryErr = false">
Cancel
</button>
<button type="button" class="btn btn-primary" (click)="queryErr = false">
Ok
</button>
</div>
</clr-modal>
<clr-modal
[(clrModalOpen)]="discardSourceFile"
[clrModalSize]="'xl'"
[clrModalClosable]="false"
>
<h3 class="modal-title center text-center color-darker-gray">Warning</h3>
<div class="modal-body">
This action will discard the source file and it's loaded data
<b>{{ filename }}</b> Do you wish to proceed?
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-sm btn-outline"
(click)="discardSourceFile = false"
>
Cancel
</button>
<button
type="button"
class="btn btn-sm btn-success-outline"
(click)="discardSourceFile = false; discardPendingExcel(true)"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal
[(clrModalOpen)]="filePasswordModal"
[clrModalSize]="'md'"
[clrModalClosable]="false"
>
<h3 class="modal-title center text-center color-darker-gray">
Password Protected File
</h3>
<div class="modal-body d-flex clr-justify-content-center">
<p class="m-0">Please enter password:</p>
<input
#filePasswordInput
data-lpignore="true"
autocomplete="off"
id="filePasswordInput"
type="text"
class="clr-input disable-password-manager"
/>
</div>
<div class="modal-footer">
@if (fileUnlockError) {
<p class="m-0 color-red">Sorry that didn't work, try again.</p>
}
<button
type="button"
class="btn btn-sm btn-outline"
(click)="filePasswordModal = false; filePasswordSubject.next(undefined)"
>
Cancel upload
</button>
<button
type="button"
class="btn btn-sm btn-success-outline"
[disabled]="filePasswordInput.value.length < 1"
(click)="
filePasswordModal = false;
filePasswordSubject.next(filePasswordInput.value)
"
>
Unlock
</button>
</div>
</clr-modal>
<clr-modal
[(clrModalOpen)]="manualFileEditModal"
[clrModalSize]="'xl'"
[clrModalClosable]="false"
>
<h3 class="modal-title center text-center color-darker-gray">Warning</h3>
<div class="modal-body">
Manually editing this data will mean the source file
<b>{{ filename }}</b> will be discarded. Do you wish to proceed?
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-sm btn-outline"
(click)="manualFileEditModal = false"
>
Cancel
</button>
<button
type="button"
class="btn btn-sm btn-success-outline"
(click)="manualFileEditModal = false; previewTableEditConfirm()"
>
Ok
</button>
</div>
</clr-modal>
<app-dataset-info
[(open)]="datasetInfo"
[dsmeta]="dsmeta"
[versions]="versions"
(rowClicked)="datasetInfoModalRowClicked($event)"
>
</app-dataset-info>
<app-viewboxes [(viewboxModal)]="viewboxes"></app-viewboxes>
<app-confirm-modal
[open]="confirmModal.open"
[title]="confirmModal.title"
[message]="confirmModal.message"
(result)="onConfirmModalResult($event)"
></app-confirm-modal>
<app-bulk-validation-modal
[open]="bulkValidation.active"
[done]="bulkValidation.done"
[total]="bulkValidation.total"
(cancel)="cancelBulkValidation({ revert: true })"
></app-bulk-validation-modal>