@noatgnu/cupcake-vanilla 1.2.15 → 1.2.16
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.
|
@@ -7852,7 +7852,16 @@ class SamplePoolCreateModal {
|
|
|
7852
7852
|
// UI state
|
|
7853
7853
|
selectionMode = signal('manual', ...(ngDevMode ? [{ debugName: "selectionMode" }] : []));
|
|
7854
7854
|
currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
|
|
7855
|
-
samplesPerPage =
|
|
7855
|
+
samplesPerPage = 10;
|
|
7856
|
+
sourceNameColumn = computed(() => {
|
|
7857
|
+
return this.metadataTable?.columns?.find(c => c.name.toLowerCase() === 'source name');
|
|
7858
|
+
}, ...(ngDevMode ? [{ debugName: "sourceNameColumn" }] : []));
|
|
7859
|
+
assayNameColumn = computed(() => {
|
|
7860
|
+
return this.metadataTable?.columns?.find(c => c.name.toLowerCase() === 'assay name');
|
|
7861
|
+
}, ...(ngDevMode ? [{ debugName: "assayNameColumn" }] : []));
|
|
7862
|
+
fractionIdentifierColumn = computed(() => {
|
|
7863
|
+
return this.metadataTable?.columns?.find(c => c.name.toLowerCase() === 'fraction identifier');
|
|
7864
|
+
}, ...(ngDevMode ? [{ debugName: "fractionIdentifierColumn" }] : []));
|
|
7856
7865
|
constructor(fb, activeModal, samplePoolService, toastService) {
|
|
7857
7866
|
this.fb = fb;
|
|
7858
7867
|
this.activeModal = activeModal;
|
|
@@ -7949,6 +7958,28 @@ class SamplePoolCreateModal {
|
|
|
7949
7958
|
this.samples.set([...samples]);
|
|
7950
7959
|
this.updateSampleTextInputs();
|
|
7951
7960
|
}
|
|
7961
|
+
selectAllNonPooledSamples() {
|
|
7962
|
+
const samples = this.samples();
|
|
7963
|
+
samples.forEach(s => {
|
|
7964
|
+
if (!s.pooled) {
|
|
7965
|
+
s.selected = true;
|
|
7966
|
+
}
|
|
7967
|
+
});
|
|
7968
|
+
this.samples.set([...samples]);
|
|
7969
|
+
this.updateSampleTextInputs();
|
|
7970
|
+
}
|
|
7971
|
+
toggleSelectAllSamples() {
|
|
7972
|
+
const nonPooledCount = this.samples().filter(s => !s.pooled).length;
|
|
7973
|
+
if (this.selectedSamplesCount === nonPooledCount) {
|
|
7974
|
+
this.clearAllSelections();
|
|
7975
|
+
}
|
|
7976
|
+
else {
|
|
7977
|
+
this.selectAllNonPooledSamples();
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
7980
|
+
get availableSamplesCount() {
|
|
7981
|
+
return this.samples().filter(s => !s.pooled).length;
|
|
7982
|
+
}
|
|
7952
7983
|
updateSampleTextInputs() {
|
|
7953
7984
|
const selectedSamples = this.samples().filter(s => s.selected).map(s => s.sampleNumber);
|
|
7954
7985
|
const pooledOnly = this.pooledOnlySamples();
|
|
@@ -8100,12 +8131,40 @@ class SamplePoolCreateModal {
|
|
|
8100
8131
|
this.currentPage.set(this.currentPage() + 1);
|
|
8101
8132
|
}
|
|
8102
8133
|
}
|
|
8134
|
+
getColumnValueForSample(column, sampleIndex) {
|
|
8135
|
+
if (!column)
|
|
8136
|
+
return '';
|
|
8137
|
+
const modifier = column.modifiers?.find(m => this.isSampleInRange(sampleIndex, m.samples));
|
|
8138
|
+
if (modifier) {
|
|
8139
|
+
return modifier.value;
|
|
8140
|
+
}
|
|
8141
|
+
return column.value || '';
|
|
8142
|
+
}
|
|
8143
|
+
isSampleInRange(sampleIndex, rangeStr) {
|
|
8144
|
+
if (!rangeStr)
|
|
8145
|
+
return false;
|
|
8146
|
+
const ranges = rangeStr.split(',').map(s => s.trim());
|
|
8147
|
+
for (const range of ranges) {
|
|
8148
|
+
if (range.includes('-')) {
|
|
8149
|
+
const [start, end] = range.split('-').map(n => parseInt(n.trim(), 10));
|
|
8150
|
+
if (sampleIndex >= start && sampleIndex <= end) {
|
|
8151
|
+
return true;
|
|
8152
|
+
}
|
|
8153
|
+
}
|
|
8154
|
+
else {
|
|
8155
|
+
if (sampleIndex === parseInt(range, 10)) {
|
|
8156
|
+
return true;
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
8159
|
+
}
|
|
8160
|
+
return false;
|
|
8161
|
+
}
|
|
8103
8162
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolCreateModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: SamplePoolService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
|
|
8104
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SamplePoolCreateModal, isStandalone: true, selector: "ccv-sample-pool-create-modal", inputs: { metadataTable: "metadataTable" }, outputs: { poolCreated: "poolCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Grid for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"card mb-3\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-grid me-2\"></i>Select Samples \n <span class=\"badge bg-info ms-2\">{{ selectedSamplesCount }} selected</span>\n </span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"clearAllSelections()\">\n <i class=\"bi bi-x-circle me-1\"></i>Clear All\n </button>\n </div>\n <div class=\"card-body\" style=\"max-height: 300px; overflow-y: auto;\">\n <!-- Sample Grid -->\n <div class=\"row g-1 mb-3\">\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <div class=\"col-2\">\n <button \n type=\"button\" \n class=\"btn btn-sm w-100\"\n [class.btn-primary]=\"sample.selected && !sample.pooled\"\n [class.btn-success]=\"sample.selected && sample.pooled\"\n [class.btn-outline-secondary]=\"!sample.selected && !sample.pooled\"\n [class.btn-outline-warning]=\"!sample.selected && sample.pooled\"\n [disabled]=\"sample.pooled\"\n (click)=\"toggleSampleSelection(sample.sampleNumber)\"\n [title]=\"sample.pooled ? 'Sample already in another pool' : 'Click to select/deselect'\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1\"></i>\n }\n </button>\n </div>\n }\n </div>\n\n <!-- Pagination -->\n @if (totalPages > 1) {\n <nav>\n <ul class=\"pagination pagination-sm justify-content-center\">\n <li class=\"page-item\" [class.disabled]=\"currentPage() === 1\">\n <button class=\"page-link\" type=\"button\" (click)=\"previousPage()\">Previous</button>\n </li>\n @for (page of [].constructor(Math.min(5, totalPages)); track $index) {\n @let pageNum = $index + Math.max(1, currentPage() - 2);\n @if (pageNum <= totalPages) {\n <li class=\"page-item\" [class.active]=\"pageNum === currentPage()\">\n <button class=\"page-link\" type=\"button\" (click)=\"goToPage(pageNum)\">{{ pageNum }}</button>\n </li>\n }\n }\n <li class=\"page-item\" [class.disabled]=\"currentPage() === totalPages\">\n <button class=\"page-link\" type=\"button\" (click)=\"nextPage()\">Next</button>\n </li>\n </ul>\n </nav>\n }\n\n <div class=\"form-text text-center\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }} - \n {{ Math.min(currentPage() * samplesPerPage, samples().length) }} \n of {{ samples().length }} samples\n </div>\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }] });
|
|
8163
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SamplePoolCreateModal, isStandalone: true, selector: "ccv-sample-pool-create-modal", inputs: { metadataTable: "metadataTable" }, outputs: { poolCreated: "poolCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Table for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Select Samples <span class=\"text-muted\">({{ selectedSamplesCount }} of {{ availableSamplesCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllNonPooledSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearAllSelections()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"selectedSamplesCount === availableSamplesCount && selectedSamplesCount > 0\"\n [indeterminate]=\"selectedSamplesCount > 0 && selectedSamplesCount < availableSamplesCount\"\n (change)=\"toggleSelectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>Source Name</th>\n <th>Assay Name</th>\n <th>Fraction Identifier</th>\n </tr>\n </thead>\n <tbody>\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <tr\n [class.table-active]=\"sample.selected\"\n [class.table-warning]=\"sample.pooled\"\n (click)=\"!sample.pooled && toggleSampleSelection(sample.sampleNumber)\"\n [style.cursor]=\"sample.pooled ? 'not-allowed' : 'pointer'\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sample.selected\"\n [disabled]=\"sample.pooled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSampleSelection(sample.sampleNumber)\"\n />\n </td>\n <td class=\"fw-semibold\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1 text-warning\" title=\"Already in another pool\"></i>\n }\n </td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), sample.sampleNumber) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (totalPages > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }}-{{ Math.min(currentPage() * samplesPerPage, samples().length) }} of {{ samples().length }}\n </small>\n <ngb-pagination\n [collectionSize]=\"samples().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"samplesPerPage\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"goToPage($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to add to the pool. Samples with <i class=\"bi bi-lock-fill\"></i> are already in another pool.\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }] });
|
|
8105
8164
|
}
|
|
8106
8165
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolCreateModal, decorators: [{
|
|
8107
8166
|
type: Component,
|
|
8108
|
-
args: [{ selector: 'ccv-sample-pool-create-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Grid for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"card mb-3\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-grid me-2\"></i>Select Samples \n <span class=\"badge bg-info ms-2\">{{ selectedSamplesCount }} selected</span>\n </span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"clearAllSelections()\">\n <i class=\"bi bi-x-circle me-1\"></i>Clear All\n </button>\n </div>\n <div class=\"card-body\" style=\"max-height: 300px; overflow-y: auto;\">\n <!-- Sample Grid -->\n <div class=\"row g-1 mb-3\">\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <div class=\"col-2\">\n <button \n type=\"button\" \n class=\"btn btn-sm w-100\"\n [class.btn-primary]=\"sample.selected && !sample.pooled\"\n [class.btn-success]=\"sample.selected && sample.pooled\"\n [class.btn-outline-secondary]=\"!sample.selected && !sample.pooled\"\n [class.btn-outline-warning]=\"!sample.selected && sample.pooled\"\n [disabled]=\"sample.pooled\"\n (click)=\"toggleSampleSelection(sample.sampleNumber)\"\n [title]=\"sample.pooled ? 'Sample already in another pool' : 'Click to select/deselect'\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1\"></i>\n }\n </button>\n </div>\n }\n </div>\n\n <!-- Pagination -->\n @if (totalPages > 1) {\n <nav>\n <ul class=\"pagination pagination-sm justify-content-center\">\n <li class=\"page-item\" [class.disabled]=\"currentPage() === 1\">\n <button class=\"page-link\" type=\"button\" (click)=\"previousPage()\">Previous</button>\n </li>\n @for (page of [].constructor(Math.min(5, totalPages)); track $index) {\n @let pageNum = $index + Math.max(1, currentPage() - 2);\n @if (pageNum <= totalPages) {\n <li class=\"page-item\" [class.active]=\"pageNum === currentPage()\">\n <button class=\"page-link\" type=\"button\" (click)=\"goToPage(pageNum)\">{{ pageNum }}</button>\n </li>\n }\n }\n <li class=\"page-item\" [class.disabled]=\"currentPage() === totalPages\">\n <button class=\"page-link\" type=\"button\" (click)=\"nextPage()\">Next</button>\n </li>\n </ul>\n </nav>\n }\n\n <div class=\"form-text text-center\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }} - \n {{ Math.min(currentPage() * samplesPerPage, samples().length) }} \n of {{ samples().length }} samples\n </div>\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n" }]
|
|
8167
|
+
args: [{ selector: 'ccv-sample-pool-create-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Table for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Select Samples <span class=\"text-muted\">({{ selectedSamplesCount }} of {{ availableSamplesCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllNonPooledSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearAllSelections()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"selectedSamplesCount === availableSamplesCount && selectedSamplesCount > 0\"\n [indeterminate]=\"selectedSamplesCount > 0 && selectedSamplesCount < availableSamplesCount\"\n (change)=\"toggleSelectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>Source Name</th>\n <th>Assay Name</th>\n <th>Fraction Identifier</th>\n </tr>\n </thead>\n <tbody>\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <tr\n [class.table-active]=\"sample.selected\"\n [class.table-warning]=\"sample.pooled\"\n (click)=\"!sample.pooled && toggleSampleSelection(sample.sampleNumber)\"\n [style.cursor]=\"sample.pooled ? 'not-allowed' : 'pointer'\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sample.selected\"\n [disabled]=\"sample.pooled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSampleSelection(sample.sampleNumber)\"\n />\n </td>\n <td class=\"fw-semibold\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1 text-warning\" title=\"Already in another pool\"></i>\n }\n </td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), sample.sampleNumber) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (totalPages > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }}-{{ Math.min(currentPage() * samplesPerPage, samples().length) }} of {{ samples().length }}\n </small>\n <ngb-pagination\n [collectionSize]=\"samples().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"samplesPerPage\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"goToPage($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to add to the pool. Samples with <i class=\"bi bi-lock-fill\"></i> are already in another pool.\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n" }]
|
|
8109
8168
|
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: SamplePoolService }, { type: i3.ToastService }], propDecorators: { metadataTable: [{
|
|
8110
8169
|
type: Input
|
|
8111
8170
|
}], poolCreated: [{
|
|
@@ -8922,10 +8981,11 @@ class MetadataTableDetails {
|
|
|
8922
8981
|
column: column,
|
|
8923
8982
|
metadataTableId: table.id,
|
|
8924
8983
|
sampleCount: table.sampleCount,
|
|
8925
|
-
selectedSampleIndices: []
|
|
8984
|
+
selectedSampleIndices: [],
|
|
8985
|
+
allColumns: table.columns || []
|
|
8926
8986
|
};
|
|
8927
8987
|
const modalRef = this.modalService.open(MetadataColumnAutofillModal, {
|
|
8928
|
-
size: '
|
|
8988
|
+
size: 'xl',
|
|
8929
8989
|
backdrop: 'static'
|
|
8930
8990
|
});
|
|
8931
8991
|
modalRef.componentInstance.config = config;
|
|
@@ -9310,9 +9370,25 @@ class MetadataColumnAutofillModal {
|
|
|
9310
9370
|
isSubmitting = signal(false, ...(ngDevMode ? [{ debugName: "isSubmitting" }] : []));
|
|
9311
9371
|
fillMode = signal('template', ...(ngDevMode ? [{ debugName: "fillMode" }] : []));
|
|
9312
9372
|
sampleIndices = signal([], ...(ngDevMode ? [{ debugName: "sampleIndices" }] : []));
|
|
9373
|
+
currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
|
|
9374
|
+
pageSize = 10;
|
|
9313
9375
|
allSampleNumbers = computed(() => {
|
|
9314
9376
|
return Array.from({ length: this.config.sampleCount }, (_, idx) => idx + 1);
|
|
9315
9377
|
}, ...(ngDevMode ? [{ debugName: "allSampleNumbers" }] : []));
|
|
9378
|
+
paginatedSampleNumbers = computed(() => {
|
|
9379
|
+
const all = this.allSampleNumbers();
|
|
9380
|
+
const start = (this.currentPage() - 1) * this.pageSize;
|
|
9381
|
+
return all.slice(start, start + this.pageSize);
|
|
9382
|
+
}, ...(ngDevMode ? [{ debugName: "paginatedSampleNumbers" }] : []));
|
|
9383
|
+
sourceNameColumn = computed(() => {
|
|
9384
|
+
return this.config.allColumns?.find(c => c.name.toLowerCase() === 'source name');
|
|
9385
|
+
}, ...(ngDevMode ? [{ debugName: "sourceNameColumn" }] : []));
|
|
9386
|
+
assayNameColumn = computed(() => {
|
|
9387
|
+
return this.config.allColumns?.find(c => c.name.toLowerCase() === 'assay name');
|
|
9388
|
+
}, ...(ngDevMode ? [{ debugName: "assayNameColumn" }] : []));
|
|
9389
|
+
fractionIdentifierColumn = computed(() => {
|
|
9390
|
+
return this.config.allColumns?.find(c => c.name.toLowerCase() === 'fraction identifier');
|
|
9391
|
+
}, ...(ngDevMode ? [{ debugName: "fractionIdentifierColumn" }] : []));
|
|
9316
9392
|
previewValues = computed(() => {
|
|
9317
9393
|
const mode = this.fillMode();
|
|
9318
9394
|
const indices = this.sampleIndices();
|
|
@@ -9393,6 +9469,37 @@ class MetadataColumnAutofillModal {
|
|
|
9393
9469
|
isSampleSelected(index) {
|
|
9394
9470
|
return this.sampleIndices().includes(index);
|
|
9395
9471
|
}
|
|
9472
|
+
onPageChange(page) {
|
|
9473
|
+
this.currentPage.set(page);
|
|
9474
|
+
}
|
|
9475
|
+
getColumnValueForSample(column, sampleIndex) {
|
|
9476
|
+
if (!column)
|
|
9477
|
+
return '';
|
|
9478
|
+
const modifier = column.modifiers?.find(m => this.isSampleInRange(sampleIndex, m.samples));
|
|
9479
|
+
if (modifier) {
|
|
9480
|
+
return modifier.value;
|
|
9481
|
+
}
|
|
9482
|
+
return column.value || '';
|
|
9483
|
+
}
|
|
9484
|
+
isSampleInRange(sampleIndex, rangeStr) {
|
|
9485
|
+
if (!rangeStr)
|
|
9486
|
+
return false;
|
|
9487
|
+
const ranges = rangeStr.split(',').map(s => s.trim());
|
|
9488
|
+
for (const range of ranges) {
|
|
9489
|
+
if (range.includes('-')) {
|
|
9490
|
+
const [start, end] = range.split('-').map(n => parseInt(n.trim(), 10));
|
|
9491
|
+
if (sampleIndex >= start && sampleIndex <= end) {
|
|
9492
|
+
return true;
|
|
9493
|
+
}
|
|
9494
|
+
}
|
|
9495
|
+
else {
|
|
9496
|
+
if (sampleIndex === parseInt(range, 10)) {
|
|
9497
|
+
return true;
|
|
9498
|
+
}
|
|
9499
|
+
}
|
|
9500
|
+
}
|
|
9501
|
+
return false;
|
|
9502
|
+
}
|
|
9396
9503
|
onSubmit() {
|
|
9397
9504
|
if (this.autofillForm.invalid || this.isSubmitting()) {
|
|
9398
9505
|
return;
|
|
@@ -9437,11 +9544,11 @@ class MetadataColumnAutofillModal {
|
|
|
9437
9544
|
Array = Array;
|
|
9438
9545
|
Math = Math;
|
|
9439
9546
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnAutofillModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
|
|
9440
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataColumnAutofillModal, isStandalone: true, selector: "ccv-metadata-column-autofill-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"autofillForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Column:</strong> {{ config.column.name }} ({{ config.column.type }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Fill Mode</label>\n <div class=\"btn-group w-100\">\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'template'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'template'\"\n (click)=\"fillMode.set('template'); autofillForm.patchValue({ fillMode: 'template' })\"\n >\n <i class=\"bi bi-code-square me-1\"></i>Template\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'range'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'range'\"\n (click)=\"fillMode.set('range'); autofillForm.patchValue({ fillMode: 'range' })\"\n >\n <i class=\"bi bi-123 me-1\"></i>Numeric Range\n </button>\n </div>\n </div>\n\n @if (fillMode() === 'template') {\n <div class=\"col-12 mb-3\">\n <label for=\"template\" class=\"form-label\">\n Value Template *\n </label>\n <input\n type=\"text\"\n id=\"template\"\n class=\"form-control\"\n formControlName=\"template\"\n placeholder=\"run {sample_index}\"\n [class.is-invalid]=\"isFieldInvalid('template')\"\n />\n @if (isFieldInvalid('template')) {\n <div class=\"invalid-feedback\">Template is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use <code>{{ '{' }}sample_index{{ '}' }}</code>, <code>{{ '{' }}index{{ '}' }}</code>, or <code>{{ '{' }}n{{ '}' }}</code> as placeholder for sample number.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Quick Templates</label>\n <div class=\"d-flex flex-wrap gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('run {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>run {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('sample_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>sample_{{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('assay {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>assay {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('replicate_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>replicate_{{ '{' }}n{{ '}' }}\n </button>\n </div>\n </div>\n }\n\n @if (fillMode() === 'range') {\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-6\">\n <label for=\"rangeStart\" class=\"form-label\">\n Start Value *\n </label>\n <input\n type=\"number\"\n id=\"rangeStart\"\n class=\"form-control\"\n formControlName=\"rangeStart\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeStart')\"\n />\n @if (isFieldInvalid('rangeStart')) {\n <div class=\"invalid-feedback\">Start value is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n First value in the sequence\n </div>\n </div>\n <div class=\"col-6\">\n <label for=\"rangeIncrement\" class=\"form-label\">\n Increment *\n </label>\n <input\n type=\"number\"\n id=\"rangeIncrement\"\n class=\"form-control\"\n formControlName=\"rangeIncrement\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeIncrement')\"\n />\n @if (isFieldInvalid('rangeIncrement')) {\n <div class=\"invalid-feedback\">Increment is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Step between values\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Apply to Samples <span class=\"text-muted\">({{ sampleIndices().length }} of {{ config.sampleCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllSamples()\"\n >\n <i class=\"bi bi-check-all me-1\"></i>Select All\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n >\n <i class=\"bi bi-x me-1\"></i>Clear\n </button>\n </div>\n </div>\n\n <div class=\"sample-grid\">\n @for (i of allSampleNumbers(); track i) {\n <button\n type=\"button\"\n class=\"btn btn-sm sample-button\"\n [class.btn-primary]=\"isSampleSelected(i)\"\n [class.btn-outline-secondary]=\"!isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n >\n {{ i }}\n </button>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Click sample numbers to select/deselect. Auto-fill will only apply to selected samples.\n </div>\n </div>\n\n <div class=\"col-12\">\n <label class=\"form-label\">\n Preview\n @if (previewValues().length > 0) {\n <span class=\"text-muted\">(Showing {{ Math.min(10, previewValues().length) }} of {{ previewValues().length }})</span>\n }\n </label>\n\n @if (previewValues().length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No samples selected. Please select at least one sample.\n </div>\n } @else {\n <div class=\"preview-list\">\n @for (item of previewValues().slice(0, 10); track item.index) {\n <div class=\"preview-item\">\n <span class=\"badge bg-secondary me-2\">Sample {{ item.index }}</span>\n <span class=\"preview-value\">{{ item.value }}</span>\n </div>\n }\n @if (previewValues().length > 10) {\n <div class=\"preview-item text-muted\">\n <i class=\"bi bi-three-dots\"></i>\n and {{ previewValues().length - 10 }} more...\n </div>\n }\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n [disabled]=\"isSubmitting()\"\n >\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"onSubmit()\"\n [disabled]=\"autofillForm.invalid || sampleIndices().length === 0 || isSubmitting()\"\n >\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-magic me-1\"></i>\n Apply Auto-fill\n </button>\n</div>\n", styles: [".sample-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(45px,1fr));gap:.5rem;max-height:280px;overflow-y:auto;padding:.75rem;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-color:var(--bs-light)}.sample-button{min-width:45px;aspect-ratio:1;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem}.preview-list{max-height:250px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);padding:.75rem;background-color:var(--bs-light)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:#fff;border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color-translucent)}.preview-item:last-child{margin-bottom:0}.preview-value{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-weight:500;color:var(--bs-body-color)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }] });
|
|
9547
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataColumnAutofillModal, isStandalone: true, selector: "ccv-metadata-column-autofill-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"autofillForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Column:</strong> {{ config.column.name }} ({{ config.column.type }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Fill Mode</label>\n <div class=\"btn-group w-100\">\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'template'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'template'\"\n (click)=\"fillMode.set('template'); autofillForm.patchValue({ fillMode: 'template' })\"\n >\n <i class=\"bi bi-code-square me-1\"></i>Template\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'range'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'range'\"\n (click)=\"fillMode.set('range'); autofillForm.patchValue({ fillMode: 'range' })\"\n >\n <i class=\"bi bi-123 me-1\"></i>Numeric Range\n </button>\n </div>\n </div>\n\n @if (fillMode() === 'template') {\n <div class=\"col-12 mb-3\">\n <label for=\"template\" class=\"form-label\">\n Value Template *\n </label>\n <input\n type=\"text\"\n id=\"template\"\n class=\"form-control\"\n formControlName=\"template\"\n placeholder=\"run {sample_index}\"\n [class.is-invalid]=\"isFieldInvalid('template')\"\n />\n @if (isFieldInvalid('template')) {\n <div class=\"invalid-feedback\">Template is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use <code>{{ '{' }}sample_index{{ '}' }}</code>, <code>{{ '{' }}index{{ '}' }}</code>, or <code>{{ '{' }}n{{ '}' }}</code> as placeholder for sample number.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Quick Templates</label>\n <div class=\"d-flex flex-wrap gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('run {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>run {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('sample_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>sample_{{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('assay {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>assay {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('replicate_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>replicate_{{ '{' }}n{{ '}' }}\n </button>\n </div>\n </div>\n }\n\n @if (fillMode() === 'range') {\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-6\">\n <label for=\"rangeStart\" class=\"form-label\">\n Start Value *\n </label>\n <input\n type=\"number\"\n id=\"rangeStart\"\n class=\"form-control\"\n formControlName=\"rangeStart\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeStart')\"\n />\n @if (isFieldInvalid('rangeStart')) {\n <div class=\"invalid-feedback\">Start value is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n First value in the sequence\n </div>\n </div>\n <div class=\"col-6\">\n <label for=\"rangeIncrement\" class=\"form-label\">\n Increment *\n </label>\n <input\n type=\"number\"\n id=\"rangeIncrement\"\n class=\"form-control\"\n formControlName=\"rangeIncrement\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeIncrement')\"\n />\n @if (isFieldInvalid('rangeIncrement')) {\n <div class=\"invalid-feedback\">Increment is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Step between values\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Apply to Samples <span class=\"text-muted\">({{ sampleIndices().length }} of {{ config.sampleCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sampleIndices().length === config.sampleCount\"\n [indeterminate]=\"sampleIndices().length > 0 && sampleIndices().length < config.sampleCount\"\n (change)=\"sampleIndices().length === config.sampleCount ? clearSelection() : selectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>Source Name</th>\n <th>Assay Name</th>\n <th>Fraction Identifier</th>\n </tr>\n </thead>\n <tbody>\n @for (i of paginatedSampleNumbers(); track i) {\n <tr\n [class.table-active]=\"isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n style=\"cursor: pointer;\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"isSampleSelected(i)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSample(i)\"\n />\n </td>\n <td class=\"fw-semibold\">{{ i }}</td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), i) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (config.sampleCount > pageSize) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize + 1 }}-{{ Math.min(currentPage() * pageSize, config.sampleCount) }} of {{ config.sampleCount }}\n </small>\n <ngb-pagination\n [collectionSize]=\"config.sampleCount\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to apply auto-fill. Auto-fill will only apply to selected samples.\n </div>\n </div>\n\n <div class=\"col-12\">\n <label class=\"form-label\">\n Preview\n @if (previewValues().length > 0) {\n <span class=\"text-muted\">(Showing {{ Math.min(10, previewValues().length) }} of {{ previewValues().length }})</span>\n }\n </label>\n\n @if (previewValues().length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No samples selected. Please select at least one sample.\n </div>\n } @else {\n <div class=\"preview-list\">\n @for (item of previewValues().slice(0, 10); track item.index) {\n <div class=\"preview-item\">\n <span class=\"badge bg-secondary me-2\">Sample {{ item.index }}</span>\n <span class=\"preview-value\">{{ item.value }}</span>\n </div>\n }\n @if (previewValues().length > 10) {\n <div class=\"preview-item text-muted\">\n <i class=\"bi bi-three-dots\"></i>\n and {{ previewValues().length - 10 }} more...\n </div>\n }\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n [disabled]=\"isSubmitting()\"\n >\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"onSubmit()\"\n [disabled]=\"autofillForm.invalid || sampleIndices().length === 0 || isSubmitting()\"\n >\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-magic me-1\"></i>\n Apply Auto-fill\n </button>\n</div>\n", styles: [".table-responsive{max-height:400px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.table-responsive table{margin-bottom:0}.table-responsive table thead{position:sticky;top:0;background-color:var(--bs-tertiary-bg);z-index:1}.table-responsive table thead th{border-bottom:2px solid var(--bs-border-color);font-weight:600;font-size:.875rem;padding:.75rem}.table-responsive table tbody tr:hover{background-color:var(--bs-secondary-bg)}.table-responsive table tbody tr.table-active{background-color:var(--bs-primary-bg-subtle)}.table-responsive table tbody tr.table-active:hover{background-color:var(--bs-primary-bg-subtle);filter:brightness(.95)}.table-responsive table tbody td{font-size:.875rem;vertical-align:middle;padding:.75rem}.preview-list{max-height:250px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);padding:.75rem;background-color:var(--bs-tertiary-bg)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color)}.preview-item:last-child{margin-bottom:0}.preview-value{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-weight:500;color:var(--bs-body-color)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbPaginationModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }] });
|
|
9441
9548
|
}
|
|
9442
9549
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnAutofillModal, decorators: [{
|
|
9443
9550
|
type: Component,
|
|
9444
|
-
args: [{ selector: 'ccv-metadata-column-autofill-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"autofillForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Column:</strong> {{ config.column.name }} ({{ config.column.type }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Fill Mode</label>\n <div class=\"btn-group w-100\">\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'template'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'template'\"\n (click)=\"fillMode.set('template'); autofillForm.patchValue({ fillMode: 'template' })\"\n >\n <i class=\"bi bi-code-square me-1\"></i>Template\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'range'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'range'\"\n (click)=\"fillMode.set('range'); autofillForm.patchValue({ fillMode: 'range' })\"\n >\n <i class=\"bi bi-123 me-1\"></i>Numeric Range\n </button>\n </div>\n </div>\n\n @if (fillMode() === 'template') {\n <div class=\"col-12 mb-3\">\n <label for=\"template\" class=\"form-label\">\n Value Template *\n </label>\n <input\n type=\"text\"\n id=\"template\"\n class=\"form-control\"\n formControlName=\"template\"\n placeholder=\"run {sample_index}\"\n [class.is-invalid]=\"isFieldInvalid('template')\"\n />\n @if (isFieldInvalid('template')) {\n <div class=\"invalid-feedback\">Template is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use <code>{{ '{' }}sample_index{{ '}' }}</code>, <code>{{ '{' }}index{{ '}' }}</code>, or <code>{{ '{' }}n{{ '}' }}</code> as placeholder for sample number.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Quick Templates</label>\n <div class=\"d-flex flex-wrap gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('run {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>run {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('sample_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>sample_{{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('assay {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>assay {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('replicate_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>replicate_{{ '{' }}n{{ '}' }}\n </button>\n </div>\n </div>\n }\n\n @if (fillMode() === 'range') {\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-6\">\n <label for=\"rangeStart\" class=\"form-label\">\n Start Value *\n </label>\n <input\n type=\"number\"\n id=\"rangeStart\"\n class=\"form-control\"\n formControlName=\"rangeStart\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeStart')\"\n />\n @if (isFieldInvalid('rangeStart')) {\n <div class=\"invalid-feedback\">Start value is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n First value in the sequence\n </div>\n </div>\n <div class=\"col-6\">\n <label for=\"rangeIncrement\" class=\"form-label\">\n Increment *\n </label>\n <input\n type=\"number\"\n id=\"rangeIncrement\"\n class=\"form-control\"\n formControlName=\"rangeIncrement\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeIncrement')\"\n />\n @if (isFieldInvalid('rangeIncrement')) {\n <div class=\"invalid-feedback\">Increment is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Step between values\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Apply to Samples <span class=\"text-muted\">({{ sampleIndices().length }} of {{ config.sampleCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllSamples()\"\n >\n <i class=\"bi bi-check-all me-1\"></i>Select All\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n >\n <i class=\"bi bi-x me-1\"></i>Clear\n </button>\n </div>\n </div>\n\n <div class=\"sample-grid\">\n @for (i of allSampleNumbers(); track i) {\n <button\n type=\"button\"\n class=\"btn btn-sm sample-button\"\n [class.btn-primary]=\"isSampleSelected(i)\"\n [class.btn-outline-secondary]=\"!isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n >\n {{ i }}\n </button>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Click sample numbers to select/deselect. Auto-fill will only apply to selected samples.\n </div>\n </div>\n\n <div class=\"col-12\">\n <label class=\"form-label\">\n Preview\n @if (previewValues().length > 0) {\n <span class=\"text-muted\">(Showing {{ Math.min(10, previewValues().length) }} of {{ previewValues().length }})</span>\n }\n </label>\n\n @if (previewValues().length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No samples selected. Please select at least one sample.\n </div>\n } @else {\n <div class=\"preview-list\">\n @for (item of previewValues().slice(0, 10); track item.index) {\n <div class=\"preview-item\">\n <span class=\"badge bg-secondary me-2\">Sample {{ item.index }}</span>\n <span class=\"preview-value\">{{ item.value }}</span>\n </div>\n }\n @if (previewValues().length > 10) {\n <div class=\"preview-item text-muted\">\n <i class=\"bi bi-three-dots\"></i>\n and {{ previewValues().length - 10 }} more...\n </div>\n }\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n [disabled]=\"isSubmitting()\"\n >\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"onSubmit()\"\n [disabled]=\"autofillForm.invalid || sampleIndices().length === 0 || isSubmitting()\"\n >\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-magic me-1\"></i>\n Apply Auto-fill\n </button>\n</div>\n", styles: [".sample-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(45px,1fr));gap:.5rem;max-height:280px;overflow-y:auto;padding:.75rem;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);background-color:var(--bs-light)}.sample-button{min-width:45px;aspect-ratio:1;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem}.preview-list{max-height:250px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);padding:.75rem;background-color:var(--bs-light)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:#fff;border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color-translucent)}.preview-item:last-child{margin-bottom:0}.preview-value{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-weight:500;color:var(--bs-body-color)}\n"] }]
|
|
9551
|
+
args: [{ selector: 'ccv-metadata-column-autofill-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbPaginationModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"autofillForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Column:</strong> {{ config.column.name }} ({{ config.column.type }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Fill Mode</label>\n <div class=\"btn-group w-100\">\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'template'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'template'\"\n (click)=\"fillMode.set('template'); autofillForm.patchValue({ fillMode: 'template' })\"\n >\n <i class=\"bi bi-code-square me-1\"></i>Template\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'range'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'range'\"\n (click)=\"fillMode.set('range'); autofillForm.patchValue({ fillMode: 'range' })\"\n >\n <i class=\"bi bi-123 me-1\"></i>Numeric Range\n </button>\n </div>\n </div>\n\n @if (fillMode() === 'template') {\n <div class=\"col-12 mb-3\">\n <label for=\"template\" class=\"form-label\">\n Value Template *\n </label>\n <input\n type=\"text\"\n id=\"template\"\n class=\"form-control\"\n formControlName=\"template\"\n placeholder=\"run {sample_index}\"\n [class.is-invalid]=\"isFieldInvalid('template')\"\n />\n @if (isFieldInvalid('template')) {\n <div class=\"invalid-feedback\">Template is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use <code>{{ '{' }}sample_index{{ '}' }}</code>, <code>{{ '{' }}index{{ '}' }}</code>, or <code>{{ '{' }}n{{ '}' }}</code> as placeholder for sample number.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Quick Templates</label>\n <div class=\"d-flex flex-wrap gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('run {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>run {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('sample_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>sample_{{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('assay {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>assay {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('replicate_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>replicate_{{ '{' }}n{{ '}' }}\n </button>\n </div>\n </div>\n }\n\n @if (fillMode() === 'range') {\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-6\">\n <label for=\"rangeStart\" class=\"form-label\">\n Start Value *\n </label>\n <input\n type=\"number\"\n id=\"rangeStart\"\n class=\"form-control\"\n formControlName=\"rangeStart\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeStart')\"\n />\n @if (isFieldInvalid('rangeStart')) {\n <div class=\"invalid-feedback\">Start value is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n First value in the sequence\n </div>\n </div>\n <div class=\"col-6\">\n <label for=\"rangeIncrement\" class=\"form-label\">\n Increment *\n </label>\n <input\n type=\"number\"\n id=\"rangeIncrement\"\n class=\"form-control\"\n formControlName=\"rangeIncrement\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeIncrement')\"\n />\n @if (isFieldInvalid('rangeIncrement')) {\n <div class=\"invalid-feedback\">Increment is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Step between values\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Apply to Samples <span class=\"text-muted\">({{ sampleIndices().length }} of {{ config.sampleCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sampleIndices().length === config.sampleCount\"\n [indeterminate]=\"sampleIndices().length > 0 && sampleIndices().length < config.sampleCount\"\n (change)=\"sampleIndices().length === config.sampleCount ? clearSelection() : selectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>Source Name</th>\n <th>Assay Name</th>\n <th>Fraction Identifier</th>\n </tr>\n </thead>\n <tbody>\n @for (i of paginatedSampleNumbers(); track i) {\n <tr\n [class.table-active]=\"isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n style=\"cursor: pointer;\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"isSampleSelected(i)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSample(i)\"\n />\n </td>\n <td class=\"fw-semibold\">{{ i }}</td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), i) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (config.sampleCount > pageSize) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize + 1 }}-{{ Math.min(currentPage() * pageSize, config.sampleCount) }} of {{ config.sampleCount }}\n </small>\n <ngb-pagination\n [collectionSize]=\"config.sampleCount\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to apply auto-fill. Auto-fill will only apply to selected samples.\n </div>\n </div>\n\n <div class=\"col-12\">\n <label class=\"form-label\">\n Preview\n @if (previewValues().length > 0) {\n <span class=\"text-muted\">(Showing {{ Math.min(10, previewValues().length) }} of {{ previewValues().length }})</span>\n }\n </label>\n\n @if (previewValues().length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No samples selected. Please select at least one sample.\n </div>\n } @else {\n <div class=\"preview-list\">\n @for (item of previewValues().slice(0, 10); track item.index) {\n <div class=\"preview-item\">\n <span class=\"badge bg-secondary me-2\">Sample {{ item.index }}</span>\n <span class=\"preview-value\">{{ item.value }}</span>\n </div>\n }\n @if (previewValues().length > 10) {\n <div class=\"preview-item text-muted\">\n <i class=\"bi bi-three-dots\"></i>\n and {{ previewValues().length - 10 }} more...\n </div>\n }\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n [disabled]=\"isSubmitting()\"\n >\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n (click)=\"onSubmit()\"\n [disabled]=\"autofillForm.invalid || sampleIndices().length === 0 || isSubmitting()\"\n >\n @if (isSubmitting()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-magic me-1\"></i>\n Apply Auto-fill\n </button>\n</div>\n", styles: [".table-responsive{max-height:400px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.table-responsive table{margin-bottom:0}.table-responsive table thead{position:sticky;top:0;background-color:var(--bs-tertiary-bg);z-index:1}.table-responsive table thead th{border-bottom:2px solid var(--bs-border-color);font-weight:600;font-size:.875rem;padding:.75rem}.table-responsive table tbody tr:hover{background-color:var(--bs-secondary-bg)}.table-responsive table tbody tr.table-active{background-color:var(--bs-primary-bg-subtle)}.table-responsive table tbody tr.table-active:hover{background-color:var(--bs-primary-bg-subtle);filter:brightness(.95)}.table-responsive table tbody td{font-size:.875rem;vertical-align:middle;padding:.75rem}.preview-list{max-height:250px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);padding:.75rem;background-color:var(--bs-tertiary-bg)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color)}.preview-item:last-child{margin-bottom:0}.preview-value{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-weight:500;color:var(--bs-body-color)}\n"] }]
|
|
9445
9552
|
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { config: [{
|
|
9446
9553
|
type: Input
|
|
9447
9554
|
}] } });
|