Files
dc/client/src/app/editor/editor.component.html
T
dc 34691dceec
Build / Build-and-ng-test (pull_request) Failing after 3m13s
Build / Build-and-test-development (pull_request) Skipped
Lighthouse Checks / lighthouse (pull_request) Successful in 19m53s
feat(editor): serve a truncated selection with a warning instead of aborting
A selection over DC_MAXOBS_WEBEDIT or DC_MAXCELLS_WEBEDIT was refused with
"Table is too big", leaving the user with no grid and no way to see what the
filter had matched.  getdata now serves the first rows that fit and flags it:

- the row cap is min(DC_MAXOBS_WEBEDIT, DC_MAXCELLS_WEBEDIT / columns), so a
  wide table is still bounded by the cell budget
- sasparams carries TRUNCATED (0/1) and TRUNC_MSG, naming the limit reached
- the EDIT screen shows a warning triangle next to the table name, with the
  message in a tooltip and the advice to filter

The mock mirrors the new behaviour, the Cypress spec asserts the truncated
grid and the tooltip (real hover, opacity checked so a hidden tooltip cannot
pass), and the SAS limit test now asserts the served row counts and message
rather than a cancelled child job.
2026-10-22 16:38:37 +00:00

1017 lines
34 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-auto clr-col-lg-auto 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 clr-col-lg 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 (truncationNote) {
<clr-tooltip>
<clr-icon
clrTooltipTrigger
shape="warning"
aria-label="The selection was truncated"
class="color-orange truncation-warning"
size="20"
></clr-icon>
<clr-tooltip-content
clrPosition="right"
clrSize="lg"
*clrIfOpen
>
{{ truncationNote }}
</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-auto clr-col-lg-auto btnCtrl">
@if (hotTable.readOnly && !uploadPreview) {
@if (!isVaEmbed) {
<button
type="button"
class="btnView btn icon-collapse btn-sm btn-icon 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"
(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 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
#infoBar
class="clr-col-md-12 infoBar"
[class.expanded]="filterExpanded"
>
<span class="infoBar-text"
>FILTER :
<b>{{ queryText }}</b>
</span>
@if (filterOverflows) {
<button
type="button"
class="infoBar-toggle"
[attr.aria-expanded]="filterExpanded"
[attr.aria-label]="
filterExpanded
? 'Collapse the filter clause'
: 'Expand the filter clause'
"
(click)="toggleFilterPanel()"
>
<clr-icon aria-hidden="true" shape="caret down"></clr-icon>
</button>
}
</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>