Files
dc/client/src/app/editor/editor.component.html
T
dc fdaa249f44
Build / Build-and-ng-test (pull_request) Successful in 5m45s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m28s
Build / Build-and-test-development (pull_request) Successful in 26m36s
feat(filters): make the applied-filter panel expandable
A filter clause is shown in a panel above the grid. The panel was clamped to a
single line - in the editor with an ellipsis, and in the viewer by letting the
clause run the full width of the table - so a long or complex filter could not
be read on screen.

The panel is now collapsed to one line and carries a chevron that expands it to
show the whole clause, wrapped, and collapses it again. The chevron is rendered
only when the clause does not fit the collapsed line, so a short filter looks
exactly as it did before. Whether it fits is measured from the DOM rather than
guessed from the length of the text, since it depends on the rendered width; the
measurement is deferred out of the change-detection cycle, and re-run when the
clause, the available width, or the state changes.

This replaces the editor's hover-only reveal with a real control, which is a
button carrying aria-expanded and an accessible label, and gives the viewer the
same affordance. Both panels share the styling, which was previously duplicated
between the two component blocks.

Tested with `client/cypress/e2e/filter-panel.cy.ts`, at a laptop-sized viewport,
covering the three states:

1. no filter - the panel is not rendered
2. a short filter - shown in full, with no chevron
3. a long filter (an IN over every value of a free-text column, 646 characters)
   - collapsed with a chevron, expands to the whole clause over several lines,
   and collapses again
4. the editor panel behaves the same way for the same clause
2026-09-25 14:27:02 +00:00

998 lines
33 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 (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>