@noatgnu/cupcake-vanilla 1.4.0 → 1.4.1
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.
package/README.md
CHANGED
|
File without changes
|
|
@@ -2658,7 +2658,8 @@ class SdrfSyntaxService {
|
|
|
2658
2658
|
cleavage: /^comment\[cleavage agent details\]$/i,
|
|
2659
2659
|
spiked_compound: /^characteristics\[spiked compound\]$/i,
|
|
2660
2660
|
pooled_sample: /^characteristics\[pooled sample\]$/i,
|
|
2661
|
-
synthetic_peptide: /^characteristics\[synthetic peptide\]$/i
|
|
2661
|
+
synthetic_peptide: /^characteristics\[synthetic peptide\]$/i,
|
|
2662
|
+
mz_range: /^comment\[ms\d?\s*scan\s*range\]$/i,
|
|
2662
2663
|
};
|
|
2663
2664
|
MODIFICATION_KEYS = ['NT', 'AC', 'CF', 'MT', 'PP', 'TA', 'MM', 'TS'];
|
|
2664
2665
|
CLEAVAGE_KEYS = ['NT', 'AC', 'CS'];
|
|
@@ -2683,6 +2684,12 @@ class SdrfSyntaxService {
|
|
|
2683
2684
|
if (this.SPECIAL_COLUMN_PATTERNS.spiked_compound.test(name) || this.SPECIAL_COLUMN_PATTERNS.spiked_compound.test(type)) {
|
|
2684
2685
|
return 'spiked_compound';
|
|
2685
2686
|
}
|
|
2687
|
+
if (this.SPECIAL_COLUMN_PATTERNS.pooled_sample.test(name) || this.SPECIAL_COLUMN_PATTERNS.pooled_sample.test(type)) {
|
|
2688
|
+
return 'pooled_sample';
|
|
2689
|
+
}
|
|
2690
|
+
if (this.SPECIAL_COLUMN_PATTERNS.mz_range.test(name) || this.SPECIAL_COLUMN_PATTERNS.mz_range.test(type)) {
|
|
2691
|
+
return 'mz_range';
|
|
2692
|
+
}
|
|
2686
2693
|
return null;
|
|
2687
2694
|
}
|
|
2688
2695
|
/**
|
|
@@ -2702,12 +2709,17 @@ class SdrfSyntaxService {
|
|
|
2702
2709
|
return this.parseKeyValuePairs(value, this.CLEAVAGE_KEYS);
|
|
2703
2710
|
case 'spiked_compound':
|
|
2704
2711
|
return this.parseKeyValuePairs(value, this.SPIKED_COMPOUND_KEYS);
|
|
2712
|
+
case 'pooled_sample':
|
|
2713
|
+
return this.parsePooledSample(value);
|
|
2714
|
+
case 'mz_range':
|
|
2715
|
+
return this.parseMzRange(value);
|
|
2716
|
+
case 'synthetic_peptide':
|
|
2717
|
+
return { value };
|
|
2705
2718
|
default:
|
|
2706
2719
|
throw new Error(`Unknown syntax type: ${syntaxType}`);
|
|
2707
2720
|
}
|
|
2708
2721
|
}
|
|
2709
2722
|
catch (error) {
|
|
2710
|
-
console.error(`Error parsing ${syntaxType} value:`, error);
|
|
2711
2723
|
return null;
|
|
2712
2724
|
}
|
|
2713
2725
|
}
|
|
@@ -2726,12 +2738,17 @@ class SdrfSyntaxService {
|
|
|
2726
2738
|
case 'cleavage':
|
|
2727
2739
|
case 'spiked_compound':
|
|
2728
2740
|
return this.formatKeyValuePairs(data);
|
|
2741
|
+
case 'pooled_sample':
|
|
2742
|
+
return this.formatPooledSample(data);
|
|
2743
|
+
case 'mz_range':
|
|
2744
|
+
return this.formatMzRange(data);
|
|
2745
|
+
case 'synthetic_peptide':
|
|
2746
|
+
return data.value;
|
|
2729
2747
|
default:
|
|
2730
2748
|
throw new Error(`Unknown syntax type: ${syntaxType}`);
|
|
2731
2749
|
}
|
|
2732
2750
|
}
|
|
2733
2751
|
catch (error) {
|
|
2734
|
-
console.error(`Error formatting ${syntaxType} value:`, error);
|
|
2735
2752
|
return '';
|
|
2736
2753
|
}
|
|
2737
2754
|
}
|
|
@@ -2759,6 +2776,12 @@ class SdrfSyntaxService {
|
|
|
2759
2776
|
return this.validateCleavageAgentDetails(data);
|
|
2760
2777
|
case 'spiked_compound':
|
|
2761
2778
|
return this.validateSpikedCompound(data);
|
|
2779
|
+
case 'pooled_sample':
|
|
2780
|
+
return this.validatePooledSample(data);
|
|
2781
|
+
case 'mz_range':
|
|
2782
|
+
return this.validateMzRange(data);
|
|
2783
|
+
case 'synthetic_peptide':
|
|
2784
|
+
return this.validateSyntheticPeptide(data);
|
|
2762
2785
|
default:
|
|
2763
2786
|
result.isValid = false;
|
|
2764
2787
|
result.errors.push(`Unknown syntax type: ${syntaxType}`);
|
|
@@ -2918,6 +2941,77 @@ class SdrfSyntaxService {
|
|
|
2918
2941
|
}
|
|
2919
2942
|
return result;
|
|
2920
2943
|
}
|
|
2944
|
+
parsePooledSample(value) {
|
|
2945
|
+
const trimmed = value.trim();
|
|
2946
|
+
if (trimmed === 'not pooled' || trimmed === 'pooled') {
|
|
2947
|
+
return { value: trimmed };
|
|
2948
|
+
}
|
|
2949
|
+
if (trimmed.startsWith('SN=')) {
|
|
2950
|
+
const rest = trimmed.slice(3);
|
|
2951
|
+
const sourceNames = rest.includes(';SN=')
|
|
2952
|
+
? rest.split(';SN=').map(n => n.trim())
|
|
2953
|
+
: rest.split(',').map(n => n.trim());
|
|
2954
|
+
return { value: trimmed, sourceNames };
|
|
2955
|
+
}
|
|
2956
|
+
return { value: trimmed };
|
|
2957
|
+
}
|
|
2958
|
+
formatPooledSample(data) {
|
|
2959
|
+
if (data.value === 'not pooled' || data.value === 'pooled') {
|
|
2960
|
+
return data.value;
|
|
2961
|
+
}
|
|
2962
|
+
if (data.sourceNames && data.sourceNames.length > 0) {
|
|
2963
|
+
return 'SN=' + data.sourceNames.join(';SN=');
|
|
2964
|
+
}
|
|
2965
|
+
return data.value;
|
|
2966
|
+
}
|
|
2967
|
+
validatePooledSample(data) {
|
|
2968
|
+
const result = { isValid: true, errors: [] };
|
|
2969
|
+
if (!data.value) {
|
|
2970
|
+
result.isValid = false;
|
|
2971
|
+
result.errors.push('Pooled sample value is required');
|
|
2972
|
+
return result;
|
|
2973
|
+
}
|
|
2974
|
+
if (data.value !== 'not pooled' && data.value !== 'pooled' && !data.value.startsWith('SN=')) {
|
|
2975
|
+
result.isValid = false;
|
|
2976
|
+
result.errors.push('Value must be "not pooled", "pooled", or start with "SN="');
|
|
2977
|
+
}
|
|
2978
|
+
return result;
|
|
2979
|
+
}
|
|
2980
|
+
parseMzRange(value) {
|
|
2981
|
+
const match = value.match(/^(\d+(?:\.\d+)?)\s*m\/z\s*-\s*(\d+(?:\.\d+)?)\s*m\/z$/i);
|
|
2982
|
+
if (!match) {
|
|
2983
|
+
throw new Error(`Invalid m/z range format: ${value}`);
|
|
2984
|
+
}
|
|
2985
|
+
return { low: parseFloat(match[1]), high: parseFloat(match[2]) };
|
|
2986
|
+
}
|
|
2987
|
+
formatMzRange(data) {
|
|
2988
|
+
return `${data.low}m/z-${data.high}m/z`;
|
|
2989
|
+
}
|
|
2990
|
+
validateMzRange(data) {
|
|
2991
|
+
const result = { isValid: true, errors: [] };
|
|
2992
|
+
if (data.low === undefined || data.high === undefined) {
|
|
2993
|
+
result.isValid = false;
|
|
2994
|
+
result.errors.push('Both low and high m/z values are required');
|
|
2995
|
+
return result;
|
|
2996
|
+
}
|
|
2997
|
+
if (data.low < 0 || data.high < 0) {
|
|
2998
|
+
result.isValid = false;
|
|
2999
|
+
result.errors.push('m/z values must be non-negative');
|
|
3000
|
+
}
|
|
3001
|
+
if (data.low >= data.high) {
|
|
3002
|
+
result.isValid = false;
|
|
3003
|
+
result.errors.push('Low m/z must be less than high m/z');
|
|
3004
|
+
}
|
|
3005
|
+
return result;
|
|
3006
|
+
}
|
|
3007
|
+
validateSyntheticPeptide(data) {
|
|
3008
|
+
const result = { isValid: true, errors: [] };
|
|
3009
|
+
if (!['synthetic', 'not synthetic'].includes(data.value)) {
|
|
3010
|
+
result.isValid = false;
|
|
3011
|
+
result.errors.push('Value must be "synthetic" or "not synthetic"');
|
|
3012
|
+
}
|
|
3013
|
+
return result;
|
|
3014
|
+
}
|
|
2921
3015
|
getPooledSampleValues() {
|
|
2922
3016
|
return [...this.POOLED_SAMPLE_VALUES];
|
|
2923
3017
|
}
|
|
@@ -4016,6 +4110,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
4016
4110
|
type: Output
|
|
4017
4111
|
}] } });
|
|
4018
4112
|
|
|
4113
|
+
class SdrfMzRangeInput {
|
|
4114
|
+
fb;
|
|
4115
|
+
value = '';
|
|
4116
|
+
valueChange = new EventEmitter();
|
|
4117
|
+
form;
|
|
4118
|
+
constructor(fb) {
|
|
4119
|
+
this.fb = fb;
|
|
4120
|
+
this.form = this.fb.group({
|
|
4121
|
+
low: [''],
|
|
4122
|
+
high: [''],
|
|
4123
|
+
});
|
|
4124
|
+
}
|
|
4125
|
+
ngOnInit() {
|
|
4126
|
+
if (this.value) {
|
|
4127
|
+
this.parseAndSet(this.value);
|
|
4128
|
+
}
|
|
4129
|
+
this.form.valueChanges.subscribe(() => this.emitValue());
|
|
4130
|
+
}
|
|
4131
|
+
ngOnChanges(changes) {
|
|
4132
|
+
if (changes['value'] && !changes['value'].firstChange && this.form) {
|
|
4133
|
+
this.parseAndSet(this.value);
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
parseAndSet(value) {
|
|
4137
|
+
const match = value.match(/^(\d+(?:\.\d+)?)\s*m\/z\s*-\s*(\d+(?:\.\d+)?)\s*m\/z$/i);
|
|
4138
|
+
if (match) {
|
|
4139
|
+
this.form.patchValue({ low: match[1], high: match[2] }, { emitEvent: false });
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
emitValue() {
|
|
4143
|
+
const { low, high } = this.form.value;
|
|
4144
|
+
if (low && high) {
|
|
4145
|
+
this.valueChange.emit(`${low}m/z-${high}m/z`);
|
|
4146
|
+
}
|
|
4147
|
+
else {
|
|
4148
|
+
this.valueChange.emit('');
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfMzRangeInput, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
|
|
4152
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: SdrfMzRangeInput, isStandalone: true, selector: "ccv-sdrf-mz-range-input", inputs: { value: "value" }, outputs: { valueChange: "valueChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"mz-range-input\">\n <form [formGroup]=\"form\">\n <div class=\"input-group input-group-sm\">\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"low\"\n placeholder=\"Low\"\n step=\"any\"\n min=\"0\">\n <span class=\"input-group-text\">m/z</span>\n <span class=\"input-group-text\">-</span>\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"high\"\n placeholder=\"High\"\n step=\"any\"\n min=\"0\">\n <span class=\"input-group-text\">m/z</span>\n </div>\n </form>\n <small class=\"text-muted d-block mt-1\">e.g. 400m/z-1250m/z</small>\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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number]:not([ngNoCva])[formControlName],input[type=number]:not([ngNoCva])[formControl],input[type=number]:not([ngNoCva])[ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4153
|
+
}
|
|
4154
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SdrfMzRangeInput, decorators: [{
|
|
4155
|
+
type: Component,
|
|
4156
|
+
args: [{ selector: 'ccv-sdrf-mz-range-input', standalone: true, imports: [CommonModule, ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"mz-range-input\">\n <form [formGroup]=\"form\">\n <div class=\"input-group input-group-sm\">\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"low\"\n placeholder=\"Low\"\n step=\"any\"\n min=\"0\">\n <span class=\"input-group-text\">m/z</span>\n <span class=\"input-group-text\">-</span>\n <input\n type=\"number\"\n class=\"form-control\"\n formControlName=\"high\"\n placeholder=\"High\"\n step=\"any\"\n min=\"0\">\n <span class=\"input-group-text\">m/z</span>\n </div>\n </form>\n <small class=\"text-muted d-block mt-1\">e.g. 400m/z-1250m/z</small>\n</div>\n" }]
|
|
4157
|
+
}], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { value: [{
|
|
4158
|
+
type: Input
|
|
4159
|
+
}], valueChange: [{
|
|
4160
|
+
type: Output
|
|
4161
|
+
}] } });
|
|
4162
|
+
|
|
4019
4163
|
class MetadataValueEditModal {
|
|
4020
4164
|
fb;
|
|
4021
4165
|
activeModal;
|
|
@@ -4088,10 +4232,12 @@ class MetadataValueEditModal {
|
|
|
4088
4232
|
ngOnInit() {
|
|
4089
4233
|
this.initializeValidatorInputType();
|
|
4090
4234
|
const syntaxType = this.sdrfSyntaxService.detectSpecialSyntax(this.config.columnName, this.config.columnType);
|
|
4091
|
-
if (syntaxType
|
|
4235
|
+
if (syntaxType) {
|
|
4092
4236
|
this.specialSyntaxType.set(syntaxType);
|
|
4093
|
-
this.
|
|
4094
|
-
|
|
4237
|
+
if (syntaxType !== 'pooled_sample' && syntaxType !== 'synthetic_peptide' && !this.validatorInputType()) {
|
|
4238
|
+
this.showSpecialInput.set(true);
|
|
4239
|
+
this.specialInputValue.set(this.config?.currentValue || '');
|
|
4240
|
+
}
|
|
4095
4241
|
}
|
|
4096
4242
|
if (this.config?.currentValue) {
|
|
4097
4243
|
this.editForm.patchValue({
|
|
@@ -4105,14 +4251,6 @@ class MetadataValueEditModal {
|
|
|
4105
4251
|
this.loadFavoriteOptions();
|
|
4106
4252
|
if (this.config?.enableMultiSampleEdit && this.config?.sampleData) {
|
|
4107
4253
|
this.showSamplePanel.set(true);
|
|
4108
|
-
console.log('Multi-sample editing enabled with', this.config.sampleData.length, 'samples');
|
|
4109
|
-
}
|
|
4110
|
-
else {
|
|
4111
|
-
console.log('Multi-sample editing not enabled:', {
|
|
4112
|
-
enableMultiSampleEdit: this.config?.enableMultiSampleEdit,
|
|
4113
|
-
hasSampleData: !!this.config?.sampleData,
|
|
4114
|
-
sampleDataLength: this.config?.sampleData?.length
|
|
4115
|
-
});
|
|
4116
4254
|
}
|
|
4117
4255
|
this.editForm.get('value')?.valueChanges.subscribe((newValue) => {
|
|
4118
4256
|
const selected = this.selectedFavorite();
|
|
@@ -4199,7 +4337,6 @@ class MetadataValueEditModal {
|
|
|
4199
4337
|
limit: 10,
|
|
4200
4338
|
searchType: this.searchType()
|
|
4201
4339
|
}).pipe(map$1(response => response.suggestions || []), catchError(error => {
|
|
4202
|
-
console.error('Error getting ontology suggestions:', error);
|
|
4203
4340
|
return of([]);
|
|
4204
4341
|
}), tap(() => this.isLoadingSuggestions.set(false)));
|
|
4205
4342
|
}
|
|
@@ -4212,7 +4349,6 @@ class MetadataValueEditModal {
|
|
|
4212
4349
|
customFilters: this.config.customOntologyFilters,
|
|
4213
4350
|
limit: 10
|
|
4214
4351
|
}).pipe(map$1(response => response.suggestions || []), catchError(error => {
|
|
4215
|
-
console.error('Error getting ontology suggestions:', error);
|
|
4216
4352
|
return of([]);
|
|
4217
4353
|
}), tap(() => this.isLoadingSuggestions.set(false)));
|
|
4218
4354
|
}
|
|
@@ -4352,9 +4488,6 @@ class MetadataValueEditModal {
|
|
|
4352
4488
|
if (favorite.columnTemplate && favorite.columnTemplate !== this.config.templateId) {
|
|
4353
4489
|
this.config.templateId = favorite.columnTemplate;
|
|
4354
4490
|
}
|
|
4355
|
-
if (this.hasCustomDisplayValue(favorite)) {
|
|
4356
|
-
console.log(`Selected "${favorite.displayValue}" (value: ${favorite.value})`);
|
|
4357
|
-
}
|
|
4358
4491
|
}
|
|
4359
4492
|
onFavoriteSelected(event, favoritesList) {
|
|
4360
4493
|
const selectElement = event.target;
|
|
@@ -4409,9 +4542,7 @@ class MetadataValueEditModal {
|
|
|
4409
4542
|
this.globalFavorites.set([...this.globalFavorites(), created]);
|
|
4410
4543
|
}
|
|
4411
4544
|
},
|
|
4412
|
-
error: (
|
|
4413
|
-
console.error('Error adding to favorites:', error);
|
|
4414
|
-
}
|
|
4545
|
+
error: () => { }
|
|
4415
4546
|
});
|
|
4416
4547
|
}
|
|
4417
4548
|
get hasFavorites() {
|
|
@@ -4453,8 +4584,7 @@ class MetadataValueEditModal {
|
|
|
4453
4584
|
const labGroupIds = response.results.map(group => group.id);
|
|
4454
4585
|
this.userLabGroups.set(labGroupIds);
|
|
4455
4586
|
},
|
|
4456
|
-
error: (
|
|
4457
|
-
console.error('Error loading user lab groups:', error);
|
|
4587
|
+
error: () => {
|
|
4458
4588
|
this.userLabGroups.set([]);
|
|
4459
4589
|
}
|
|
4460
4590
|
});
|
|
@@ -4478,6 +4608,7 @@ class MetadataValueEditModal {
|
|
|
4478
4608
|
case 'modification': return 'Modification Parameters Editor';
|
|
4479
4609
|
case 'cleavage': return 'Cleavage Agent Editor';
|
|
4480
4610
|
case 'spiked_compound': return 'Spiked Compound Editor';
|
|
4611
|
+
case 'mz_range': return 'Scan Range Editor';
|
|
4481
4612
|
default: return 'Special Format Editor';
|
|
4482
4613
|
}
|
|
4483
4614
|
}
|
|
@@ -4550,7 +4681,7 @@ class MetadataValueEditModal {
|
|
|
4550
4681
|
}
|
|
4551
4682
|
}
|
|
4552
4683
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", 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 });
|
|
4553
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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() && !validatorInputType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n @if (validatorInputType()) {\n <span class=\"badge bg-warning text-dark ms-2\">\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number + Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </span>\n }\n </label>\n\n @if (specialSyntaxType() || validatorInputType()) {\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 <!-- Quick-select buttons for standard values -->\n @if (config.allowNotApplicable || config.allowNotAvailable) {\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Quick Select:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (config.allowNotApplicable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (config.allowNotAvailable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Validator-based Input Components -->\n @if (validatorInputType() && showSpecialInput()) {\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 @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number with Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </small>\n </div>\n\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <ccv-sdrf-number-with-unit-input\n [value]=\"specialInputValue()\"\n [units]=\"validatorUnits()\"\n [description]=\"validatorDescription()\"\n [examples]=\"validatorExamples()\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-number-with-unit-input>\n }\n @case ('select') {\n <ccv-sdrf-select-input\n [value]=\"specialInputValue()\"\n [options]=\"validatorOptions()\"\n [description]=\"validatorDescription()\"\n [allowCustom]=\"true\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-select-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType() && !validatorInputType()) {\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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i6.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i6.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i6.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "directive", type: i6$1.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i6$1.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i6$1.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i6$1.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i6$1.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i6$1.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i6$1.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "directive", type: i7.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: "component", type: SdrfNumberWithUnitInput, selector: "ccv-sdrf-number-with-unit-input", inputs: ["value", "units", "placeholder", "description", "examples"], outputs: ["valueChange"] }, { kind: "component", type: SdrfSelectInput, selector: "ccv-sdrf-select-input", inputs: ["value", "options", "placeholder", "description", "allowCustom"], outputs: ["valueChange"] }, { kind: "pipe", type: i1$1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4684
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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() && !validatorInputType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n @if (validatorInputType()) {\n <span class=\"badge bg-warning text-dark ms-2\">\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number + Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </span>\n }\n </label>\n\n @if (specialSyntaxType() || validatorInputType()) {\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 <!-- Quick-select buttons for standard values -->\n @if (config.allowNotApplicable || config.allowNotAvailable || specialSyntaxType() === 'pooled_sample' || specialSyntaxType() === 'synthetic_peptide') {\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Quick Select:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (config.allowNotApplicable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (config.allowNotAvailable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n @if (specialSyntaxType() === 'pooled_sample') {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not pooled')\">\n Not Pooled\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('pooled')\">\n Pooled\n </button>\n }\n @if (specialSyntaxType() === 'synthetic_peptide') {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('synthetic')\">\n Synthetic\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not synthetic')\">\n Not Synthetic\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Validator-based Input Components -->\n @if (validatorInputType() && showSpecialInput()) {\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 @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number with Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </small>\n </div>\n\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <ccv-sdrf-number-with-unit-input\n [value]=\"specialInputValue()\"\n [units]=\"validatorUnits()\"\n [description]=\"validatorDescription()\"\n [examples]=\"validatorExamples()\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-number-with-unit-input>\n }\n @case ('select') {\n <ccv-sdrf-select-input\n [value]=\"specialInputValue()\"\n [options]=\"validatorOptions()\"\n [description]=\"validatorDescription()\"\n [allowCustom]=\"true\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-select-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType() && !validatorInputType()) {\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 @case ('mz_range') {\n <ccv-sdrf-mz-range-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-mz-range-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]=\"editFormInvalid() || 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]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],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: i6.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i6.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i6.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "directive", type: i6$1.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i6$1.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i6$1.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i6$1.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i6$1.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i6$1.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i6$1.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "directive", type: i7.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: "component", type: SdrfNumberWithUnitInput, selector: "ccv-sdrf-number-with-unit-input", inputs: ["value", "units", "placeholder", "description", "examples"], outputs: ["valueChange"] }, { kind: "component", type: SdrfSelectInput, selector: "ccv-sdrf-select-input", inputs: ["value", "options", "placeholder", "description", "allowCustom"], outputs: ["valueChange"] }, { kind: "component", type: SdrfMzRangeInput, selector: "ccv-sdrf-mz-range-input", inputs: ["value"], outputs: ["valueChange"] }, { kind: "pipe", type: i1$1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4554
4685
|
}
|
|
4555
4686
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: MetadataValueEditModal, decorators: [{
|
|
4556
4687
|
type: Component,
|
|
@@ -4564,8 +4695,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
4564
4695
|
SdrfCleavageInput,
|
|
4565
4696
|
SdrfSpikedCompoundInput,
|
|
4566
4697
|
SdrfNumberWithUnitInput,
|
|
4567
|
-
SdrfSelectInput
|
|
4568
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, 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() && !validatorInputType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n @if (validatorInputType()) {\n <span class=\"badge bg-warning text-dark ms-2\">\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number + Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </span>\n }\n </label>\n\n @if (specialSyntaxType() || validatorInputType()) {\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 <!-- Quick-select buttons for standard values -->\n @if (config.allowNotApplicable || config.allowNotAvailable) {\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Quick Select:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (config.allowNotApplicable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (config.allowNotAvailable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Validator-based Input Components -->\n @if (validatorInputType() && showSpecialInput()) {\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 @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number with Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </small>\n </div>\n\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <ccv-sdrf-number-with-unit-input\n [value]=\"specialInputValue()\"\n [units]=\"validatorUnits()\"\n [description]=\"validatorDescription()\"\n [examples]=\"validatorExamples()\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-number-with-unit-input>\n }\n @case ('select') {\n <ccv-sdrf-select-input\n [value]=\"specialInputValue()\"\n [options]=\"validatorOptions()\"\n [description]=\"validatorDescription()\"\n [allowCustom]=\"true\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-select-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType() && !validatorInputType()) {\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]=\"editFormInvalid() || 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"] }]
|
|
4698
|
+
SdrfSelectInput,
|
|
4699
|
+
SdrfMzRangeInput
|
|
4700
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, 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() && !validatorInputType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n @if (validatorInputType()) {\n <span class=\"badge bg-warning text-dark ms-2\">\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number + Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </span>\n }\n </label>\n\n @if (specialSyntaxType() || validatorInputType()) {\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 <!-- Quick-select buttons for standard values -->\n @if (config.allowNotApplicable || config.allowNotAvailable || specialSyntaxType() === 'pooled_sample' || specialSyntaxType() === 'synthetic_peptide') {\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Quick Select:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (config.allowNotApplicable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (config.allowNotAvailable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n @if (specialSyntaxType() === 'pooled_sample') {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not pooled')\">\n Not Pooled\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('pooled')\">\n Pooled\n </button>\n }\n @if (specialSyntaxType() === 'synthetic_peptide') {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('synthetic')\">\n Synthetic\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not synthetic')\">\n Not Synthetic\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Validator-based Input Components -->\n @if (validatorInputType() && showSpecialInput()) {\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 @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <i class=\"bi bi-123 me-1\"></i>Number with Unit\n }\n @case ('select') {\n <i class=\"bi bi-list me-1\"></i>Predefined Values\n }\n @default {\n <i class=\"bi bi-gear me-1\"></i>Structured Input\n }\n }\n </small>\n </div>\n\n @switch (validatorInputType()) {\n @case ('number_with_unit') {\n <ccv-sdrf-number-with-unit-input\n [value]=\"specialInputValue()\"\n [units]=\"validatorUnits()\"\n [description]=\"validatorDescription()\"\n [examples]=\"validatorExamples()\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-number-with-unit-input>\n }\n @case ('select') {\n <ccv-sdrf-select-input\n [value]=\"specialInputValue()\"\n [options]=\"validatorOptions()\"\n [description]=\"validatorDescription()\"\n [allowCustom]=\"true\"\n (valueChange)=\"onValidatorInputChange($event)\">\n </ccv-sdrf-select-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType() && !validatorInputType()) {\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 @case ('mz_range') {\n <ccv-sdrf-mz-range-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-mz-range-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]=\"editFormInvalid() || 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"] }]
|
|
4569
4701
|
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i3.AuthService }, { type: FavouriteMetadataOptionService }, { type: MetadataColumnService }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }, { type: OntologySearchService }], propDecorators: { config: [{
|
|
4570
4702
|
type: Input
|
|
4571
4703
|
}], valueSaved: [{
|
|
@@ -11567,5 +11699,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
|
|
|
11567
11699
|
* Generated bundle index. Do not edit.
|
|
11568
11700
|
*/
|
|
11569
11701
|
|
|
11570
|
-
export { AsyncExportService, AsyncImportService, AsyncTaskService, AsyncTaskUIService, AsyncValidationService, COLUMN_TYPE_CONFIGS, ChunkedImportService, ChunkedUploadService, ColumnEditModal, ColumnOverrideImportModal, ColumnTemplateEditModal, ColumnTemplates, ColumnType, CupcakeVanilla, DesktopService, DualScrollDirective, ElectronService, ExcelExportModalComponent, ExcelLaunchService, FavoriteManagement, FavouriteMetadataOptionService, MSTermType, MetadataColumnAutofillModal, MetadataColumnHistoryModal, MetadataColumnService, MetadataColumnTemplateService, MetadataColumnTemplateShareService, MetadataManagementService, MetadataTableDetails, MetadataTableEditModal, MetadataTableService, MetadataTableTemplateEditModal, MetadataTableTemplateService, MetadataTableTemplates, MetadataValidationModal, MetadataValueEditModal, NotificationService, NotificationType, OFFICIAL_SDRF_COLUMNS, ONTOLOGY_TYPE_CONFIGS, OfficialColumnCacheService, OntologySearchService, OntologyType, OntologyTypeLabels, OntologyUtils, OpenInExcel, SDRFDefaultsService, SamplePoolCreateModal, SamplePoolDetailsModal, SamplePoolEditModal, SamplePoolService, SchemaSelectionModal, SchemaService, SdrfAgeInput, SdrfCleavageInput, SdrfColumnCategory, SdrfModificationInput, SdrfNumberWithUnitInput, SdrfSelectInput, SdrfSpikedCompoundInput, SdrfSyntaxService, SearchReplaceModal, TableCreationModalComponent, TemplatePermissionLabels, TemplatePermissionLevel, TemplateSchemaSelectionModal, ValidationResultsModal, Websocket, createSyntheticColumnFromTemplate, getRequiredSdrfColumns, getSdrfColumnByName, getSdrfColumnsByCategory, isSdrfColumnRequired, isUnimodFullData };
|
|
11702
|
+
export { AsyncExportService, AsyncImportService, AsyncTaskService, AsyncTaskUIService, AsyncValidationService, COLUMN_TYPE_CONFIGS, ChunkedImportService, ChunkedUploadService, ColumnEditModal, ColumnOverrideImportModal, ColumnTemplateEditModal, ColumnTemplates, ColumnType, CupcakeVanilla, DesktopService, DualScrollDirective, ElectronService, ExcelExportModalComponent, ExcelLaunchService, FavoriteManagement, FavouriteMetadataOptionService, MSTermType, MetadataColumnAutofillModal, MetadataColumnHistoryModal, MetadataColumnService, MetadataColumnTemplateService, MetadataColumnTemplateShareService, MetadataManagementService, MetadataTableDetails, MetadataTableEditModal, MetadataTableService, MetadataTableTemplateEditModal, MetadataTableTemplateService, MetadataTableTemplates, MetadataValidationModal, MetadataValueEditModal, NotificationService, NotificationType, OFFICIAL_SDRF_COLUMNS, ONTOLOGY_TYPE_CONFIGS, OfficialColumnCacheService, OntologySearchService, OntologyType, OntologyTypeLabels, OntologyUtils, OpenInExcel, SDRFDefaultsService, SamplePoolCreateModal, SamplePoolDetailsModal, SamplePoolEditModal, SamplePoolService, SchemaSelectionModal, SchemaService, SdrfAgeInput, SdrfCleavageInput, SdrfColumnCategory, SdrfModificationInput, SdrfMzRangeInput, SdrfNumberWithUnitInput, SdrfSelectInput, SdrfSpikedCompoundInput, SdrfSyntaxService, SearchReplaceModal, TableCreationModalComponent, TemplatePermissionLabels, TemplatePermissionLevel, TemplateSchemaSelectionModal, ValidationResultsModal, Websocket, createSyntheticColumnFromTemplate, getRequiredSdrfColumns, getSdrfColumnByName, getSdrfColumnsByCategory, isSdrfColumnRequired, isUnimodFullData };
|
|
11571
11703
|
//# sourceMappingURL=noatgnu-cupcake-vanilla.mjs.map
|