@noatgnu/cupcake-vanilla 1.2.7 → 1.2.9
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.
|
@@ -1526,10 +1526,63 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
|
|
|
1526
1526
|
class OntologySearchService extends BaseApiService {
|
|
1527
1527
|
/**
|
|
1528
1528
|
* Get ontology suggestions across all sources for SDRF validation
|
|
1529
|
+
*
|
|
1530
|
+
* @param params Query parameters
|
|
1531
|
+
* @param params.q Search query (required, min 2 characters)
|
|
1532
|
+
* @param params.type Ontology type to search (optional)
|
|
1533
|
+
* @param params.match Match type - 'contains' or 'startswith' (default: 'contains')
|
|
1534
|
+
* @param params.limit Maximum number of results (default: 50)
|
|
1535
|
+
* @param params.customFilters Custom filters to apply (optional)
|
|
1536
|
+
* Example: {"organism": "Homo sapiens"} or {"organism": {"icontains": "human"}}
|
|
1529
1537
|
*/
|
|
1530
1538
|
suggest(params) {
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1539
|
+
const queryParams = {
|
|
1540
|
+
q: params.q,
|
|
1541
|
+
};
|
|
1542
|
+
if (params.type) {
|
|
1543
|
+
queryParams.type = params.type;
|
|
1544
|
+
}
|
|
1545
|
+
if (params.match) {
|
|
1546
|
+
queryParams.match = params.match;
|
|
1547
|
+
}
|
|
1548
|
+
if (params.limit) {
|
|
1549
|
+
queryParams.limit = params.limit;
|
|
1550
|
+
}
|
|
1551
|
+
if (params.customFilters) {
|
|
1552
|
+
queryParams.custom_filters = JSON.stringify(params.customFilters);
|
|
1553
|
+
}
|
|
1554
|
+
const httpParams = this.buildHttpParams(queryParams);
|
|
1555
|
+
return this.get(`${this.apiUrl}/ontology/search/suggest/`, { params: httpParams });
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Get ontology suggestions for a specific ontology type with custom filters
|
|
1559
|
+
*/
|
|
1560
|
+
suggestWithFilters(query, ontologyType, customFilters, limit = 50) {
|
|
1561
|
+
return this.suggest({
|
|
1562
|
+
q: query,
|
|
1563
|
+
type: ontologyType,
|
|
1564
|
+
customFilters,
|
|
1565
|
+
limit
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Search across multiple ontology types
|
|
1570
|
+
*/
|
|
1571
|
+
searchAll(query, limit = 50) {
|
|
1572
|
+
return this.suggest({
|
|
1573
|
+
q: query,
|
|
1574
|
+
limit
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Search specific ontology type
|
|
1579
|
+
*/
|
|
1580
|
+
searchType(query, ontologyType, limit = 50) {
|
|
1581
|
+
return this.suggest({
|
|
1582
|
+
q: query,
|
|
1583
|
+
type: ontologyType,
|
|
1584
|
+
limit
|
|
1585
|
+
});
|
|
1533
1586
|
}
|
|
1534
1587
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: OntologySearchService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
1535
1588
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: OntologySearchService, providedIn: 'root' });
|
|
@@ -3207,6 +3260,7 @@ class MetadataValueEditModal {
|
|
|
3207
3260
|
metadataColumnService;
|
|
3208
3261
|
metadataColumnTemplateService;
|
|
3209
3262
|
labGroupService;
|
|
3263
|
+
ontologySearchService;
|
|
3210
3264
|
config;
|
|
3211
3265
|
valueSaved = new EventEmitter();
|
|
3212
3266
|
editForm;
|
|
@@ -3227,7 +3281,7 @@ class MetadataValueEditModal {
|
|
|
3227
3281
|
specialInputValue = signal('', ...(ngDevMode ? [{ debugName: "specialInputValue" }] : []));
|
|
3228
3282
|
selectedSampleIndices = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedSampleIndices" }] : []));
|
|
3229
3283
|
showSamplePanel = signal(false, ...(ngDevMode ? [{ debugName: "showSamplePanel" }] : []));
|
|
3230
|
-
constructor(fb, activeModal, authService, favouriteMetadataOptionService, metadataColumnService, metadataColumnTemplateService, labGroupService) {
|
|
3284
|
+
constructor(fb, activeModal, authService, favouriteMetadataOptionService, metadataColumnService, metadataColumnTemplateService, labGroupService, ontologySearchService) {
|
|
3231
3285
|
this.fb = fb;
|
|
3232
3286
|
this.activeModal = activeModal;
|
|
3233
3287
|
this.authService = authService;
|
|
@@ -3235,6 +3289,7 @@ class MetadataValueEditModal {
|
|
|
3235
3289
|
this.metadataColumnService = metadataColumnService;
|
|
3236
3290
|
this.metadataColumnTemplateService = metadataColumnTemplateService;
|
|
3237
3291
|
this.labGroupService = labGroupService;
|
|
3292
|
+
this.ontologySearchService = ontologySearchService;
|
|
3238
3293
|
this.editForm = this.fb.group({
|
|
3239
3294
|
value: ['', [Validators.maxLength(500)]]
|
|
3240
3295
|
});
|
|
@@ -3319,6 +3374,19 @@ class MetadataValueEditModal {
|
|
|
3319
3374
|
return of([]);
|
|
3320
3375
|
}), tap$1(() => this.isLoadingSuggestions.set(false)));
|
|
3321
3376
|
}
|
|
3377
|
+
else if (this.config.ontologyType) {
|
|
3378
|
+
// Fallback: use OntologySearchService directly with custom filters
|
|
3379
|
+
return this.ontologySearchService.suggest({
|
|
3380
|
+
q: term,
|
|
3381
|
+
type: this.config.ontologyType,
|
|
3382
|
+
match: this.searchType() === 'icontains' ? 'contains' : 'startswith',
|
|
3383
|
+
customFilters: this.config.customOntologyFilters,
|
|
3384
|
+
limit: 10
|
|
3385
|
+
}).pipe(map$1(response => response.suggestions || []), catchError(error => {
|
|
3386
|
+
console.error('Error getting ontology suggestions:', error);
|
|
3387
|
+
return of([]);
|
|
3388
|
+
}), tap$1(() => this.isLoadingSuggestions.set(false)));
|
|
3389
|
+
}
|
|
3322
3390
|
else {
|
|
3323
3391
|
this.isLoadingSuggestions.set(false);
|
|
3324
3392
|
return of([]);
|
|
@@ -3647,7 +3715,7 @@ class MetadataValueEditModal {
|
|
|
3647
3715
|
}
|
|
3648
3716
|
}
|
|
3649
3717
|
}
|
|
3650
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataValueEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i3.AuthService }, { token: FavouriteMetadataOptionService }, { token: MetadataColumnService }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3718
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataValueEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i3.AuthService }, { token: FavouriteMetadataOptionService }, { token: MetadataColumnService }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }, { token: OntologySearchService }], target: i0.ɵɵFactoryTarget.Component });
|
|
3651
3719
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", type: MetadataValueEditModal, isStandalone: true, selector: "ccv-metadata-value-edit-modal", inputs: { config: "config" }, outputs: { valueSaved: "valueSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-pencil-square me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <div class=\"row\">\n <!-- Main Content -->\n <div [class]=\"hasMultiSampleEdit ? 'col-md-8' : 'col-12'\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n\n <!-- Column Information -->\n <div class=\"mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Column Name:</small>\n <div class=\"fw-semibold\">{{ config.columnName }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Column Type:</small>\n <div><span class=\"badge bg-light text-dark\">{{ config.columnType }}</span></div>\n </div>\n </div>\n @if (hasOntologyType) {\n <div class=\"mt-2\">\n <small class=\"text-muted\">Ontology Type:</small>\n <div><span class=\"badge bg-info\">{{ ontologyTypeLabel }}</span></div>\n </div>\n }\n </div>\n\n <hr class=\"mb-4\">\n\n <!-- Favorite Options Section -->\n @if (hasFavorites && !isLoadingFavorites()) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-heart-fill me-2 text-danger\"></i>Recommended Values\n </h6>\n\n <!-- Favorites Navigation Tabs -->\n <ul ngbNav #favoritesNav=\"ngbNav\" [(activeId)]=\"activeFavoritesTab\" class=\"nav-tabs\">\n @if (userFavorites().length > 0) {\n <li [ngbNavItem]=\"'user'\">\n <button ngbNavLink>\n <i class=\"bi bi-person-fill me-1\"></i>Your Favorites\n <span class=\"badge bg-primary ms-1\">{{ userFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, userFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of userFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Your personal favorite values for this column\n </div>\n </div>\n </ng-template>\n </li>\n }\n\n @if (labGroupFavorites().length > 0) {\n <li [ngbNavItem]=\"'lab_group'\">\n <button ngbNavLink>\n <i class=\"bi bi-people-fill me-1\"></i>Lab Group\n <span class=\"badge bg-info ms-1\">{{ labGroupFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, labGroupFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of labGroupFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Favorite values shared by your lab group\n </div>\n </div>\n </ng-template>\n </li>\n }\n\n @if (globalFavorites().length > 0) {\n <li [ngbNavItem]=\"'global'\">\n <button ngbNavLink>\n <i class=\"bi bi-globe me-1\"></i>Global\n <span class=\"badge bg-success ms-1\">{{ globalFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, globalFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of globalFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Globally recommended values for this column\n </div>\n </div>\n </ng-template>\n </li>\n }\n </ul>\n\n <div [ngbNavOutlet]=\"favoritesNav\" class=\"mt-3\"></div>\n\n <hr class=\"my-3\">\n </div>\n }\n\n @if (isLoadingFavorites()) {\n <div class=\"text-center mb-4\">\n <div class=\"spinner-border spinner-border-sm text-primary me-2\"></div>\n <small class=\"text-muted\">Loading favorite options...</small>\n </div>\n <hr class=\"my-3\">\n }\n\n <!-- Value Input Section -->\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label for=\"metadataValue\" class=\"form-label mb-0\">\n Metadata Value\n @if (hasOntologyType) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Autocomplete Available\n </span>\n }\n @if (specialSyntaxType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n </label>\n\n @if (specialSyntaxType()) {\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"specialInputToggle\"\n [checked]=\"showSpecialInput()\"\n (change)=\"toggleSpecialInput()\">\n <label class=\"form-check-label small text-muted\" for=\"specialInputToggle\">\n Enhanced Editor\n </label>\n </div>\n }\n </div>\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType()) {\n <div class=\"border rounded p-3 mb-3 bg-body-secondary\">\n <div class=\"mb-2\">\n <small class=\"text-muted fw-semibold\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </small>\n </div>\n\n @switch (specialSyntaxType()) {\n @case ('age') {\n <ccv-sdrf-age-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-age-input>\n }\n @case ('modification') {\n <ccv-sdrf-modification-input\n [value]=\"specialInputValue()\"\n [columnId]=\"config.columnId ?? null\"\n [templateId]=\"config.templateId ?? null\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-modification-input>\n }\n @case ('cleavage') {\n <ccv-sdrf-cleavage-input\n [value]=\"specialInputValue()\"\n [columnId]=\"config.columnId ?? null\"\n [templateId]=\"config.templateId ?? null\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-cleavage-input>\n }\n @case ('spiked_compound') {\n <ccv-sdrf-spiked-compound-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-spiked-compound-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the enhanced editor for structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n @if (hasOntologyType && !showSpecialInput()) {\n <!-- Search type selection for ontology -->\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Search Mode:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'icontains' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('icontains')\">\n <i class=\"bi bi-search me-1\"></i>Contains\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'istartswith' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('istartswith')\">\n <i class=\"bi bi-arrow-right me-1\"></i>Starts with\n </button>\n </div>\n </div>\n\n <!-- Typeahead input for ontology suggestions -->\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.border-success]=\"selectedFavorite() !== null\"\n id=\"metadataValue\"\n formControlName=\"value\"\n placeholder=\"Type to search ontology suggestions...\"\n [ngbTypeahead]=\"searchOntology\"\n [resultFormatter]=\"formatSuggestion\"\n [inputFormatter]=\"inputFormatter\"\n (selectItem)=\"onSuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n @if (isLoadingSuggestions()) {\n <span class=\"input-group-text\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </span>\n } @else {\n <span class=\"input-group-text\">\n <i class=\"bi bi-search text-muted\"></i>\n </span>\n }\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearValue()\"\n title=\"Clear value\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Search {{ searchType() === 'icontains' ? 'contains' : 'starts with' }} for {{ ontologyTypeLabel }} ontology\n </div>\n } @else if (!showSpecialInput()) {\n <!-- Regular input when no ontology type or special input is disabled -->\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.border-success]=\"selectedFavorite() !== null\"\n id=\"metadataValue\"\n formControlName=\"value\"\n placeholder=\"Enter metadata value\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearValue()\"\n title=\"Clear value\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n <div class=\"form-text text-muted\">\n @if (specialSyntaxType()) {\n Manual text entry mode. Use the Enhanced Editor toggle for structured {{ specialSyntaxType() }} input.\n } @else {\n Enter the value manually (no ontology suggestions available for this column type)\n }\n </div>\n }\n </div>\n\n <!-- Character count and value info -->\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Characters: {{ editForm.get('value')?.value?.length || 0 }}/500\n </small>\n @if (getSelectedFavoriteInfo()) {\n <small class=\"text-info\">\n <i class=\"bi bi-info-circle me-1\"></i>{{ getSelectedFavoriteInfo() }}\n </small>\n }\n </div>\n </div>\n\n </form>\n </div>\n\n <!-- Sample Selection Panel -->\n @if (hasMultiSampleEdit) {\n <div class=\"col-md-4\">\n <div class=\"border-start ps-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h6 class=\"mb-0 text-muted\">\n <i class=\"bi bi-list-check me-1\"></i>Apply to Samples\n </h6>\n <span class=\"badge bg-primary\">\n {{ selectedSampleCount }}/{{ totalSampleCount }}\n </span>\n </div>\n\n <!-- Select All/Clear All Controls -->\n <div class=\"d-flex gap-1 mb-3\">\n <button \n type=\"button\" \n class=\"btn btn-outline-primary btn-sm flex-fill\"\n (click)=\"selectAllSamples()\"\n [disabled]=\"selectedSampleCount === totalSampleCount\">\n <i class=\"bi bi-check-all me-1\"></i>All\n </button>\n <button \n type=\"button\" \n class=\"btn btn-outline-secondary btn-sm flex-fill\"\n (click)=\"clearSampleSelection()\"\n [disabled]=\"selectedSampleCount === 0\">\n <i class=\"bi bi-x me-1\"></i>Clear\n </button>\n </div>\n\n <!-- Sample List -->\n <div class=\"sample-list\" style=\"max-height: 400px; overflow-y: auto;\">\n @for (sample of sampleData; track sample.index) {\n <div class=\"form-check d-flex align-items-center mb-2 p-2 rounded border-bottom\">\n <input \n class=\"form-check-input me-2\" \n type=\"checkbox\" \n [id]=\"'sample-' + sample.index\"\n [checked]=\"isSampleSelected(sample.index)\"\n (change)=\"toggleSampleSelection(sample.index)\">\n <label \n [for]=\"'sample-' + sample.index\" \n class=\"form-check-label flex-grow-1 cursor-pointer\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div>\n <div class=\"fw-semibold\">Sample {{ sample.index }}</div>\n @if (sample.sourceName) {\n <small class=\"text-muted\">{{ sample.sourceName }}</small>\n }\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted\">Current:</small>\n <div class=\"small fw-mono\" [title]=\"sample.value || ''\">\n {{ (sample.value && sample.value.length > 15) ? (sample.value | slice:0:15) + '...' : (sample.value || '-') }}\n </div>\n </div>\n </div>\n </label>\n </div>\n }\n </div>\n\n @if (selectedSampleCount > 0) {\n <div class=\"alert alert-info mt-3 p-2\">\n <small>\n <i class=\"bi bi-info-circle me-1\"></i>\n The value will be applied to {{ selectedSampleCount }} selected sample{{ selectedSampleCount !== 1 ? 's' : '' }}.\n </small>\n </div>\n }\n </div>\n </div>\n }\n </div>\n</div>\n\n<div class=\"modal-footer\">\n <div class=\"me-auto\">\n @if (editForm.get('value')?.value && !isLoading() && !isFromFavoriteManagement) {\n <div class=\"btn-group dropup\" ngbDropdown>\n <button\n type=\"button\"\n class=\"btn btn-outline-warning btn-sm dropdown-toggle\"\n ngbDropdownToggle>\n <i class=\"bi bi-heart me-1\"></i>Add to Favorites\n </button>\n <div class=\"dropdown-menu\" ngbDropdownMenu>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('user')\">\n <i class=\"bi bi-person me-2\"></i>Personal Favorite\n </button>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('lab_group')\">\n <i class=\"bi bi-people me-2\"></i>Lab Group Favorite\n </button>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('global')\">\n <i class=\"bi bi-globe me-2\"></i>Global Recommendation\n </button>\n </div>\n </div>\n }\n </div>\n\n <div class=\"d-flex gap-2\">\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]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n Save Value\n </button>\n </div>\n</div>\n", styles: [".sample-list .form-check{border:1px solid transparent;transition:all .2s ease}.sample-list .form-check:hover{background-color:rgba(var(--bs-primary-rgb),.05);border-color:rgba(var(--bs-primary-rgb),.2)}.sample-list .form-check .form-check-input:checked~.form-check-label .fw-semibold{color:var(--bs-primary)}.cursor-pointer{cursor:pointer}.fw-mono{font-family:var(--bs-font-monospace)}\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.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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.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: NgbModule }, { kind: "directive", type: i2.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i2.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i2.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "directive", type: i2.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i2.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i2.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i2.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i2.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i2.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i2.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "directive", type: i2.NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "ngmodule", type: NgbNavModule }, { kind: "component", type: SdrfAgeInput, selector: "ccv-sdrf-age-input", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: SdrfModificationInput, selector: "ccv-sdrf-modification-input", inputs: ["value", "templateId", "columnId"], outputs: ["valueChange"] }, { kind: "component", type: SdrfCleavageInput, selector: "ccv-sdrf-cleavage-input", inputs: ["value", "templateId", "columnId"], outputs: ["valueChange"] }, { kind: "component", type: SdrfSpikedCompoundInput, selector: "ccv-sdrf-spiked-compound-input", inputs: ["value"], outputs: ["valueChange"] }, { kind: "pipe", type: i5.SlicePipe, name: "slice" }] });
|
|
3652
3720
|
}
|
|
3653
3721
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataValueEditModal, decorators: [{
|
|
@@ -3662,7 +3730,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
|
|
|
3662
3730
|
SdrfCleavageInput,
|
|
3663
3731
|
SdrfSpikedCompoundInput
|
|
3664
3732
|
], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-pencil-square me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <div class=\"row\">\n <!-- Main Content -->\n <div [class]=\"hasMultiSampleEdit ? 'col-md-8' : 'col-12'\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n\n <!-- Column Information -->\n <div class=\"mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Column Name:</small>\n <div class=\"fw-semibold\">{{ config.columnName }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Column Type:</small>\n <div><span class=\"badge bg-light text-dark\">{{ config.columnType }}</span></div>\n </div>\n </div>\n @if (hasOntologyType) {\n <div class=\"mt-2\">\n <small class=\"text-muted\">Ontology Type:</small>\n <div><span class=\"badge bg-info\">{{ ontologyTypeLabel }}</span></div>\n </div>\n }\n </div>\n\n <hr class=\"mb-4\">\n\n <!-- Favorite Options Section -->\n @if (hasFavorites && !isLoadingFavorites()) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-heart-fill me-2 text-danger\"></i>Recommended Values\n </h6>\n\n <!-- Favorites Navigation Tabs -->\n <ul ngbNav #favoritesNav=\"ngbNav\" [(activeId)]=\"activeFavoritesTab\" class=\"nav-tabs\">\n @if (userFavorites().length > 0) {\n <li [ngbNavItem]=\"'user'\">\n <button ngbNavLink>\n <i class=\"bi bi-person-fill me-1\"></i>Your Favorites\n <span class=\"badge bg-primary ms-1\">{{ userFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, userFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of userFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Your personal favorite values for this column\n </div>\n </div>\n </ng-template>\n </li>\n }\n\n @if (labGroupFavorites().length > 0) {\n <li [ngbNavItem]=\"'lab_group'\">\n <button ngbNavLink>\n <i class=\"bi bi-people-fill me-1\"></i>Lab Group\n <span class=\"badge bg-info ms-1\">{{ labGroupFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, labGroupFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of labGroupFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Favorite values shared by your lab group\n </div>\n </div>\n </ng-template>\n </li>\n }\n\n @if (globalFavorites().length > 0) {\n <li [ngbNavItem]=\"'global'\">\n <button ngbNavLink>\n <i class=\"bi bi-globe me-1\"></i>Global\n <span class=\"badge bg-success ms-1\">{{ globalFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, globalFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of globalFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Globally recommended values for this column\n </div>\n </div>\n </ng-template>\n </li>\n }\n </ul>\n\n <div [ngbNavOutlet]=\"favoritesNav\" class=\"mt-3\"></div>\n\n <hr class=\"my-3\">\n </div>\n }\n\n @if (isLoadingFavorites()) {\n <div class=\"text-center mb-4\">\n <div class=\"spinner-border spinner-border-sm text-primary me-2\"></div>\n <small class=\"text-muted\">Loading favorite options...</small>\n </div>\n <hr class=\"my-3\">\n }\n\n <!-- Value Input Section -->\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label for=\"metadataValue\" class=\"form-label mb-0\">\n Metadata Value\n @if (hasOntologyType) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Autocomplete Available\n </span>\n }\n @if (specialSyntaxType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n </label>\n\n @if (specialSyntaxType()) {\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"specialInputToggle\"\n [checked]=\"showSpecialInput()\"\n (change)=\"toggleSpecialInput()\">\n <label class=\"form-check-label small text-muted\" for=\"specialInputToggle\">\n Enhanced Editor\n </label>\n </div>\n }\n </div>\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType()) {\n <div class=\"border rounded p-3 mb-3 bg-body-secondary\">\n <div class=\"mb-2\">\n <small class=\"text-muted fw-semibold\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </small>\n </div>\n\n @switch (specialSyntaxType()) {\n @case ('age') {\n <ccv-sdrf-age-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-age-input>\n }\n @case ('modification') {\n <ccv-sdrf-modification-input\n [value]=\"specialInputValue()\"\n [columnId]=\"config.columnId ?? null\"\n [templateId]=\"config.templateId ?? null\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-modification-input>\n }\n @case ('cleavage') {\n <ccv-sdrf-cleavage-input\n [value]=\"specialInputValue()\"\n [columnId]=\"config.columnId ?? null\"\n [templateId]=\"config.templateId ?? null\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-cleavage-input>\n }\n @case ('spiked_compound') {\n <ccv-sdrf-spiked-compound-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-spiked-compound-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the enhanced editor for structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n @if (hasOntologyType && !showSpecialInput()) {\n <!-- Search type selection for ontology -->\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Search Mode:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'icontains' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('icontains')\">\n <i class=\"bi bi-search me-1\"></i>Contains\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'istartswith' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('istartswith')\">\n <i class=\"bi bi-arrow-right me-1\"></i>Starts with\n </button>\n </div>\n </div>\n\n <!-- Typeahead input for ontology suggestions -->\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.border-success]=\"selectedFavorite() !== null\"\n id=\"metadataValue\"\n formControlName=\"value\"\n placeholder=\"Type to search ontology suggestions...\"\n [ngbTypeahead]=\"searchOntology\"\n [resultFormatter]=\"formatSuggestion\"\n [inputFormatter]=\"inputFormatter\"\n (selectItem)=\"onSuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n @if (isLoadingSuggestions()) {\n <span class=\"input-group-text\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </span>\n } @else {\n <span class=\"input-group-text\">\n <i class=\"bi bi-search text-muted\"></i>\n </span>\n }\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearValue()\"\n title=\"Clear value\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Search {{ searchType() === 'icontains' ? 'contains' : 'starts with' }} for {{ ontologyTypeLabel }} ontology\n </div>\n } @else if (!showSpecialInput()) {\n <!-- Regular input when no ontology type or special input is disabled -->\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.border-success]=\"selectedFavorite() !== null\"\n id=\"metadataValue\"\n formControlName=\"value\"\n placeholder=\"Enter metadata value\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearValue()\"\n title=\"Clear value\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n <div class=\"form-text text-muted\">\n @if (specialSyntaxType()) {\n Manual text entry mode. Use the Enhanced Editor toggle for structured {{ specialSyntaxType() }} input.\n } @else {\n Enter the value manually (no ontology suggestions available for this column type)\n }\n </div>\n }\n </div>\n\n <!-- Character count and value info -->\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Characters: {{ editForm.get('value')?.value?.length || 0 }}/500\n </small>\n @if (getSelectedFavoriteInfo()) {\n <small class=\"text-info\">\n <i class=\"bi bi-info-circle me-1\"></i>{{ getSelectedFavoriteInfo() }}\n </small>\n }\n </div>\n </div>\n\n </form>\n </div>\n\n <!-- Sample Selection Panel -->\n @if (hasMultiSampleEdit) {\n <div class=\"col-md-4\">\n <div class=\"border-start ps-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h6 class=\"mb-0 text-muted\">\n <i class=\"bi bi-list-check me-1\"></i>Apply to Samples\n </h6>\n <span class=\"badge bg-primary\">\n {{ selectedSampleCount }}/{{ totalSampleCount }}\n </span>\n </div>\n\n <!-- Select All/Clear All Controls -->\n <div class=\"d-flex gap-1 mb-3\">\n <button \n type=\"button\" \n class=\"btn btn-outline-primary btn-sm flex-fill\"\n (click)=\"selectAllSamples()\"\n [disabled]=\"selectedSampleCount === totalSampleCount\">\n <i class=\"bi bi-check-all me-1\"></i>All\n </button>\n <button \n type=\"button\" \n class=\"btn btn-outline-secondary btn-sm flex-fill\"\n (click)=\"clearSampleSelection()\"\n [disabled]=\"selectedSampleCount === 0\">\n <i class=\"bi bi-x me-1\"></i>Clear\n </button>\n </div>\n\n <!-- Sample List -->\n <div class=\"sample-list\" style=\"max-height: 400px; overflow-y: auto;\">\n @for (sample of sampleData; track sample.index) {\n <div class=\"form-check d-flex align-items-center mb-2 p-2 rounded border-bottom\">\n <input \n class=\"form-check-input me-2\" \n type=\"checkbox\" \n [id]=\"'sample-' + sample.index\"\n [checked]=\"isSampleSelected(sample.index)\"\n (change)=\"toggleSampleSelection(sample.index)\">\n <label \n [for]=\"'sample-' + sample.index\" \n class=\"form-check-label flex-grow-1 cursor-pointer\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div>\n <div class=\"fw-semibold\">Sample {{ sample.index }}</div>\n @if (sample.sourceName) {\n <small class=\"text-muted\">{{ sample.sourceName }}</small>\n }\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted\">Current:</small>\n <div class=\"small fw-mono\" [title]=\"sample.value || ''\">\n {{ (sample.value && sample.value.length > 15) ? (sample.value | slice:0:15) + '...' : (sample.value || '-') }}\n </div>\n </div>\n </div>\n </label>\n </div>\n }\n </div>\n\n @if (selectedSampleCount > 0) {\n <div class=\"alert alert-info mt-3 p-2\">\n <small>\n <i class=\"bi bi-info-circle me-1\"></i>\n The value will be applied to {{ selectedSampleCount }} selected sample{{ selectedSampleCount !== 1 ? 's' : '' }}.\n </small>\n </div>\n }\n </div>\n </div>\n }\n </div>\n</div>\n\n<div class=\"modal-footer\">\n <div class=\"me-auto\">\n @if (editForm.get('value')?.value && !isLoading() && !isFromFavoriteManagement) {\n <div class=\"btn-group dropup\" ngbDropdown>\n <button\n type=\"button\"\n class=\"btn btn-outline-warning btn-sm dropdown-toggle\"\n ngbDropdownToggle>\n <i class=\"bi bi-heart me-1\"></i>Add to Favorites\n </button>\n <div class=\"dropdown-menu\" ngbDropdownMenu>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('user')\">\n <i class=\"bi bi-person me-2\"></i>Personal Favorite\n </button>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('lab_group')\">\n <i class=\"bi bi-people me-2\"></i>Lab Group Favorite\n </button>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('global')\">\n <i class=\"bi bi-globe me-2\"></i>Global Recommendation\n </button>\n </div>\n </div>\n }\n </div>\n\n <div class=\"d-flex gap-2\">\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]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n Save Value\n </button>\n </div>\n</div>\n", styles: [".sample-list .form-check{border:1px solid transparent;transition:all .2s ease}.sample-list .form-check:hover{background-color:rgba(var(--bs-primary-rgb),.05);border-color:rgba(var(--bs-primary-rgb),.2)}.sample-list .form-check .form-check-input:checked~.form-check-label .fw-semibold{color:var(--bs-primary)}.cursor-pointer{cursor:pointer}.fw-mono{font-family:var(--bs-font-monospace)}\n"] }]
|
|
3665
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i3.AuthService }, { type: FavouriteMetadataOptionService }, { type: MetadataColumnService }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }], propDecorators: { config: [{
|
|
3733
|
+
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i3.AuthService }, { type: FavouriteMetadataOptionService }, { type: MetadataColumnService }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }, { type: OntologySearchService }], propDecorators: { config: [{
|
|
3666
3734
|
type: Input
|
|
3667
3735
|
}], valueSaved: [{
|
|
3668
3736
|
type: Output
|
|
@@ -4651,34 +4719,41 @@ class ColumnEditModal {
|
|
|
4651
4719
|
return this.hasOntologyType && this.editForm.get('enableTypeahead')?.value === true;
|
|
4652
4720
|
}
|
|
4653
4721
|
openValueEditModal() {
|
|
4654
|
-
|
|
4655
|
-
|
|
4722
|
+
const ontologyTypeIndexStr = this.editForm.get('ontologyType')?.value;
|
|
4723
|
+
const ontologyTypeIndex = parseInt(ontologyTypeIndexStr || '0');
|
|
4724
|
+
const enableTypeahead = this.editForm.get('enableTypeahead')?.value || false;
|
|
4656
4725
|
let ontologyType = '';
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
ontologyType = this.selectedOntologyConfig.value;
|
|
4660
|
-
if (this.selectedOntologyConfig.customFilters) {
|
|
4661
|
-
customFilters = this.selectedOntologyConfig.customFilters;
|
|
4662
|
-
}
|
|
4726
|
+
if (ontologyTypeIndex > 0 && ontologyTypeIndex < this.ontologyTypes.length) {
|
|
4727
|
+
ontologyType = this.ontologyTypes[ontologyTypeIndex].value;
|
|
4663
4728
|
}
|
|
4664
4729
|
const config = {
|
|
4665
|
-
columnId: this.column?.id,
|
|
4666
4730
|
columnName: this.editForm.get('name')?.value || 'Column',
|
|
4667
4731
|
columnType: this.editForm.get('type')?.value || '',
|
|
4668
4732
|
ontologyType: ontologyType,
|
|
4669
|
-
enableTypeahead:
|
|
4733
|
+
enableTypeahead: enableTypeahead,
|
|
4670
4734
|
currentValue: this.editForm.get('value')?.value || '',
|
|
4671
|
-
context: this.isEdit ? 'table' : 'template'
|
|
4672
|
-
templateId: this.selectedTemplate?.id || this.templateId || undefined,
|
|
4673
|
-
customOntologyFilters: customFilters
|
|
4735
|
+
context: this.isEdit ? 'table' : 'template'
|
|
4674
4736
|
};
|
|
4737
|
+
const hasTemplate = this.selectedTemplate || this.column?.template || this.templateId;
|
|
4738
|
+
if (hasTemplate) {
|
|
4739
|
+
if (this.isEdit && this.column?.id) {
|
|
4740
|
+
config.columnId = this.column.id;
|
|
4741
|
+
}
|
|
4742
|
+
config.templateId = this.selectedTemplate?.id || this.column?.template || this.templateId || undefined;
|
|
4743
|
+
}
|
|
4744
|
+
else {
|
|
4745
|
+
const columnName = this.editForm.get('name')?.value;
|
|
4746
|
+
if (columnName) {
|
|
4747
|
+
const ontologyConfig = this.getOntologyConfigForColumn(columnName);
|
|
4748
|
+
config.customOntologyFilters = ontologyConfig.customFilters;
|
|
4749
|
+
}
|
|
4750
|
+
}
|
|
4675
4751
|
const modalRef = this.modalService.open(MetadataValueEditModal, {
|
|
4676
4752
|
size: 'lg',
|
|
4677
4753
|
backdrop: 'static'
|
|
4678
4754
|
});
|
|
4679
4755
|
modalRef.componentInstance.config = config;
|
|
4680
4756
|
modalRef.componentInstance.valueSaved.subscribe((result) => {
|
|
4681
|
-
// Extract the actual value from the result
|
|
4682
4757
|
const newValue = typeof result === 'string' ? result : result.value;
|
|
4683
4758
|
this.editForm.get('value')?.setValue(newValue);
|
|
4684
4759
|
this.editForm.get('value')?.markAsTouched();
|
|
@@ -4795,16 +4870,14 @@ class ColumnEditModal {
|
|
|
4795
4870
|
// Enable controls to update values, then disable again
|
|
4796
4871
|
this.editForm.get('name')?.enable();
|
|
4797
4872
|
this.editForm.get('type')?.enable();
|
|
4798
|
-
const
|
|
4799
|
-
const ontologyTypeIndex =
|
|
4800
|
-
? this.ontologyTypes.findIndex(o => o.value === ontologyTypeValue)
|
|
4801
|
-
: 0;
|
|
4873
|
+
const ontologyConfig = this.getOntologyConfigForColumn(column.name);
|
|
4874
|
+
const ontologyTypeIndex = ontologyConfig.index;
|
|
4802
4875
|
this.editForm.patchValue({
|
|
4803
4876
|
name: column.name,
|
|
4804
4877
|
type: column.type,
|
|
4805
4878
|
value: '',
|
|
4806
|
-
ontologyType: ontologyTypeIndex
|
|
4807
|
-
enableTypeahead:
|
|
4879
|
+
ontologyType: ontologyTypeIndex.toString(),
|
|
4880
|
+
enableTypeahead: ontologyTypeIndex > 0,
|
|
4808
4881
|
mandatory: this.isColumnMandatory(column.name),
|
|
4809
4882
|
hidden: false,
|
|
4810
4883
|
readonly: false,
|
|
@@ -4825,41 +4898,89 @@ class ColumnEditModal {
|
|
|
4825
4898
|
return !this.templates.some(template => template.columnName.toLowerCase() === officialCol.name.toLowerCase());
|
|
4826
4899
|
});
|
|
4827
4900
|
}
|
|
4828
|
-
|
|
4901
|
+
getOntologyConfigForColumn(columnName) {
|
|
4829
4902
|
const lowerName = columnName.toLowerCase();
|
|
4830
|
-
|
|
4903
|
+
const MSTermType = {
|
|
4904
|
+
INSTRUMENT: 'instrument',
|
|
4905
|
+
MASS_ANALYZER_TYPE: 'mass analyzer type',
|
|
4906
|
+
CLEAVAGE_AGENT: 'cleavage agent',
|
|
4907
|
+
SAMPLE_ATTRIBUTE: 'sample attribute',
|
|
4908
|
+
ANCESTRAL_CATEGORY: 'ancestral category',
|
|
4909
|
+
SEX: 'sex',
|
|
4910
|
+
DEVELOPMENTAL_STAGE: 'developmental stage'
|
|
4911
|
+
};
|
|
4912
|
+
let ontologyType = '';
|
|
4913
|
+
let customFilters = undefined;
|
|
4831
4914
|
switch (lowerName) {
|
|
4832
4915
|
case 'characteristics[organism]':
|
|
4833
|
-
|
|
4916
|
+
ontologyType = 'species';
|
|
4917
|
+
break;
|
|
4834
4918
|
case 'characteristics[disease]':
|
|
4835
|
-
|
|
4919
|
+
ontologyType = 'human_disease';
|
|
4920
|
+
break;
|
|
4836
4921
|
case 'characteristics[organism part]':
|
|
4837
|
-
|
|
4922
|
+
ontologyType = 'tissue';
|
|
4923
|
+
break;
|
|
4838
4924
|
case 'characteristics[cell type]':
|
|
4839
|
-
|
|
4925
|
+
ontologyType = 'cell_ontology';
|
|
4926
|
+
break;
|
|
4840
4927
|
case 'comment[instrument]':
|
|
4841
|
-
|
|
4928
|
+
ontologyType = 'ms_unique_vocabularies';
|
|
4929
|
+
customFilters = { ms_unique_vocabularies: { term_type: MSTermType.INSTRUMENT } };
|
|
4930
|
+
break;
|
|
4842
4931
|
case 'comment[modification parameters]':
|
|
4843
|
-
|
|
4932
|
+
ontologyType = 'unimod';
|
|
4933
|
+
break;
|
|
4844
4934
|
case 'comment[ms2 analyzer type]':
|
|
4845
|
-
|
|
4935
|
+
ontologyType = 'ms_unique_vocabularies';
|
|
4936
|
+
customFilters = { ms_unique_vocabularies: { term_type: MSTermType.MASS_ANALYZER_TYPE } };
|
|
4937
|
+
break;
|
|
4846
4938
|
case 'comment[cleavage agent details]':
|
|
4847
|
-
|
|
4939
|
+
ontologyType = 'ms_unique_vocabularies';
|
|
4940
|
+
customFilters = { ms_unique_vocabularies: { term_type: MSTermType.CLEAVAGE_AGENT } };
|
|
4941
|
+
break;
|
|
4848
4942
|
case 'characteristics[ancestry category]':
|
|
4849
|
-
|
|
4943
|
+
ontologyType = 'ms_unique_vocabularies';
|
|
4944
|
+
customFilters = { ms_unique_vocabularies: { term_type: MSTermType.ANCESTRAL_CATEGORY } };
|
|
4945
|
+
break;
|
|
4850
4946
|
case 'characteristics[sex]':
|
|
4851
|
-
|
|
4947
|
+
ontologyType = 'ms_unique_vocabularies';
|
|
4948
|
+
customFilters = { ms_unique_vocabularies: { term_type: MSTermType.SEX } };
|
|
4949
|
+
break;
|
|
4852
4950
|
case 'characteristics[developmental stage]':
|
|
4853
|
-
|
|
4951
|
+
ontologyType = 'ms_unique_vocabularies';
|
|
4952
|
+
customFilters = { ms_unique_vocabularies: { term_type: MSTermType.DEVELOPMENTAL_STAGE } };
|
|
4953
|
+
break;
|
|
4854
4954
|
case 'comment[label]':
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4955
|
+
ontologyType = 'ms_unique_vocabularies';
|
|
4956
|
+
customFilters = { ms_unique_vocabularies: { term_type: MSTermType.SAMPLE_ATTRIBUTE } };
|
|
4957
|
+
break;
|
|
4858
4958
|
}
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4959
|
+
if (!ontologyType) {
|
|
4960
|
+
return { index: 0, ontologyType: '', customFilters: undefined };
|
|
4961
|
+
}
|
|
4962
|
+
// Find the matching ontology type config with the correct custom filters
|
|
4963
|
+
const index = this.ontologyTypes.findIndex(config => {
|
|
4964
|
+
if (config.value !== ontologyType)
|
|
4965
|
+
return false;
|
|
4966
|
+
if (!customFilters)
|
|
4967
|
+
return !config.customFilters || Object.keys(config.customFilters).length === 0;
|
|
4968
|
+
// Match custom filters
|
|
4969
|
+
if (!config.customFilters)
|
|
4970
|
+
return false;
|
|
4971
|
+
const configFilterKey = Object.keys(config.customFilters)[0];
|
|
4972
|
+
const customFilterKey = Object.keys(customFilters)[0];
|
|
4973
|
+
if (configFilterKey !== customFilterKey)
|
|
4974
|
+
return false;
|
|
4975
|
+
const configTermType = config.customFilters[configFilterKey]?.['term_type'];
|
|
4976
|
+
const customTermType = customFilters[customFilterKey]?.['term_type'];
|
|
4977
|
+
return configTermType === customTermType;
|
|
4978
|
+
});
|
|
4979
|
+
return {
|
|
4980
|
+
index: index >= 0 ? index : 0,
|
|
4981
|
+
ontologyType,
|
|
4982
|
+
customFilters
|
|
4983
|
+
};
|
|
4863
4984
|
}
|
|
4864
4985
|
isColumnMandatory(columnName) {
|
|
4865
4986
|
const lowerName = columnName.toLowerCase();
|