Skip to content

Commit ad5bcc6

Browse files
committed
feat: user can now toggle code repositories from the new rmm code collection
1 parent ecc458f commit ad5bcc6

12 files changed

Lines changed: 784 additions & 87 deletions

angular/src/app/search-panel/search-panel.component.html

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,38 @@ <h3 *ngIf="title" class="heading title has-text-black">{{ title }}</h3>
5353
</p>
5454

5555
<div class="p-inputgroup search-input-group">
56+
<span
57+
class="p-inputgroup-addon search-addon external-toggle-help"
58+
role="button"
59+
tabindex="0"
60+
[attr.title]="externalHelperText"
61+
aria-label="What external products do"
62+
(click)="openExternalInfo()"
63+
(keydown.enter)="openExternalInfo()"
64+
(keydown.space)="openExternalInfo()"
65+
>
66+
<i class="pi pi-question-circle"></i>
67+
</span>
68+
69+
<span
70+
class="p-inputgroup-addon search-addon external-toggle-addon"
71+
role="button"
72+
tabindex="0"
73+
aria-label="Include external products"
74+
[attr.aria-pressed]="includeExternalProducts"
75+
(click)="onExternalToggle(!includeExternalProducts)"
76+
(keydown.enter)="onExternalToggle(!includeExternalProducts)"
77+
(keydown.space)="onExternalToggle(!includeExternalProducts)"
78+
>
79+
<i class="pi pi-share-alt external-toggle__icon"></i>
80+
<span
81+
class="external-toggle__track external-toggle__track--compact"
82+
[class.external-toggle__track--on]="includeExternalProducts"
83+
>
84+
<span class="external-toggle__dot"></span>
85+
</span>
86+
</span>
87+
5688
<p-autoComplete
5789
pInputText
5890
p-fluid
@@ -196,6 +228,53 @@ <h3 *ngIf="title" class="heading title has-text-black">{{ title }}</h3>
196228
</div>
197229
</p-dialog>
198230

231+
<p-dialog
232+
header="External products"
233+
[(visible)]="externalInfoVisible"
234+
[modal]="true"
235+
[dismissableMask]="true"
236+
[style]="{ width: 'min(520px, 92vw)' }"
237+
styleClass="external-info-dialog"
238+
(onHide)="closeExternalInfo()"
239+
>
240+
<div class="external-info">
241+
<p class="external-info__lead">
242+
Include open-source projects (and soon patents/papers) alongside NIST resources. Filters and counts will blend them into your results.
243+
</p>
244+
<div class="external-info__pills">
245+
<span class="external-pill">
246+
<i class="pi pi-github"></i>
247+
NIST GitHub code
248+
</span>
249+
<span class="external-pill">
250+
<i class="pi pi-book"></i>
251+
Patents (soon)
252+
</span>
253+
<span class="external-pill">
254+
<i class="pi pi-file"></i>
255+
Scholarly papers (soon)
256+
</span>
257+
<span class="external-pill">
258+
<i class="pi pi-filter"></i>
259+
Blended filters & counts
260+
</span>
261+
<span class="external-pill">
262+
<i class="pi pi-ban"></i>
263+
Toggle off for NIST-only
264+
</span>
265+
</div>
266+
<div class="external-info__actions">
267+
<button
268+
pButton
269+
type="button"
270+
label="Got it"
271+
class="external-info__cta"
272+
(click)="closeExternalInfo()"
273+
></button>
274+
</div>
275+
</div>
276+
</p-dialog>
277+
199278
<p-overlayPanel #op1 [dismissable]="true" [showCloseIcon]="false">
200279
<div class="p-grid">
201280
<div

angular/src/app/search-panel/search-panel.component.scss

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,179 @@ body .p-highlight {
346346
font-size: 1rem;
347347
}
348348

349+
.external-toggle-addon {
350+
display: inline-flex;
351+
align-items: center;
352+
gap: 0.45rem;
353+
border-left: none !important;
354+
border-right: 1px solid rgba(255, 255, 255, 0.18) !important;
355+
padding-right: 0.6rem;
356+
cursor: pointer;
357+
outline: none;
358+
}
359+
360+
.external-toggle-addon:focus,
361+
.external-toggle-addon:active {
362+
outline: none;
363+
box-shadow: none;
364+
}
365+
366+
.external-toggle-help {
367+
border-right: 1px solid rgba(255, 255, 255, 0.18) !important;
368+
color: #ffffff;
369+
}
370+
371+
.external-toggle-help i {
372+
font-size: 1rem;
373+
opacity: 0.75;
374+
}
375+
376+
.external-toggle-help:hover i,
377+
.external-toggle-help:focus i {
378+
opacity: 1;
379+
}
380+
381+
.external-info {
382+
display: flex;
383+
flex-direction: column;
384+
gap: 0.75rem;
385+
padding: 0.25rem 0.1rem 0.5rem;
386+
}
387+
388+
.external-info__lead {
389+
margin: 0;
390+
font-size: 1rem;
391+
line-height: 1.6;
392+
color: #111111;
393+
}
394+
395+
.external-info__list {
396+
margin: 0.25rem 0 0.5rem 1.1rem;
397+
padding: 0;
398+
color: #111111;
399+
line-height: 1.5;
400+
}
401+
402+
.external-info__pills {
403+
display: flex;
404+
flex-wrap: wrap;
405+
gap: 0.5rem 0.6rem;
406+
margin-top: 0.25rem;
407+
}
408+
409+
.external-pill {
410+
display: inline-flex;
411+
align-items: center;
412+
padding: 0.28rem 0.85rem;
413+
border-radius: 999px;
414+
border: 1px solid #111111;
415+
background: #ffffff;
416+
color: #111111;
417+
font-weight: 600;
418+
font-size: 0.92rem;
419+
letter-spacing: 0.01em;
420+
gap: 0.35rem;
421+
}
422+
423+
.external-pill i {
424+
font-size: 0.95rem;
425+
}
426+
427+
.external-info__actions {
428+
display: flex;
429+
justify-content: flex-end;
430+
margin-top: 0.5rem;
431+
}
432+
433+
.external-info__cta {
434+
border: 1.5px solid #111111;
435+
background: #ffffff;
436+
color: #111111;
437+
border-radius: 999px;
438+
padding: 0.45rem 1.4rem;
439+
font-weight: 700;
440+
box-shadow: 0 8px 18px rgba(17, 17, 17, 0.08);
441+
}
442+
443+
.external-info__cta:hover,
444+
.external-info__cta:focus {
445+
background: #111111;
446+
color: #ffffff;
447+
border-color: #111111;
448+
}
449+
450+
/* External info dialog to match the white modal style */
451+
:host ::ng-deep .external-info-dialog .p-dialog-header {
452+
background: #ffffff;
453+
color: #111111;
454+
font-weight: 700;
455+
font-size: 1.2rem;
456+
border-bottom: 1px solid rgba(17, 17, 17, 0.08);
457+
padding: 1rem 1.3rem;
458+
}
459+
460+
:host ::ng-deep .external-info-dialog .p-dialog-content {
461+
background: #ffffff;
462+
color: #111111;
463+
border-radius: 0 0 12px 12px;
464+
padding: 1.1rem 1.3rem 1rem;
465+
}
466+
467+
:host ::ng-deep .external-info-dialog .p-dialog-header-icons .p-dialog-header-icon {
468+
color: #1f2937;
469+
}
470+
471+
:host ::ng-deep .external-info-dialog .p-dialog {
472+
border: 1.5px solid #111111;
473+
border-radius: 16px;
474+
}
475+
.external-toggle__icon {
476+
font-size: 1rem;
477+
opacity: 0.9;
478+
}
479+
480+
.external-toggle__track {
481+
width: 46px;
482+
height: 24px;
483+
background: linear-gradient(
484+
135deg,
485+
rgba(255, 255, 255, 0.16),
486+
rgba(255, 255, 255, 0.08)
487+
);
488+
border-radius: 999px;
489+
border: 1px solid rgba(255, 255, 255, 0.35);
490+
display: inline-flex;
491+
align-items: center;
492+
padding: 3px;
493+
transition: background 0.25s ease, border-color 0.25s ease;
494+
}
495+
496+
.external-toggle__track--compact {
497+
width: 40px;
498+
height: 18px;
499+
padding: 2px;
500+
}
501+
502+
.external-toggle__dot {
503+
width: 14px;
504+
height: 14px;
505+
background: #ffffff;
506+
border-radius: 50%;
507+
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
508+
transform: translateX(0);
509+
transition: transform 0.25s ease, background 0.25s ease;
510+
}
511+
512+
.external-toggle__track--on {
513+
background: linear-gradient(135deg, #8be5ff, #4aa3ff);
514+
border-color: rgba(255, 255, 255, 0.65);
515+
}
516+
517+
.external-toggle__track--on .external-toggle__dot {
518+
transform: translateX(18px);
519+
background: #0f172a;
520+
}
521+
349522
.examples-dialog ::ng-deep .p-dialog {
350523
max-height: 85vh;
351524
border: 1.5px solid #111111;

angular/src/app/search-panel/search-panel.component.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ export class SearchPanelComponent implements OnInit {
106106
showExampleStatus: boolean = false;
107107
searchBottonWith: string = "10%";
108108
breadcrumb_top: string = "6em";
109+
includeExternalProducts: boolean = false;
110+
externalHelperText: string =
111+
"Toggle to include external products like open-source code (patents & papers coming soon). Results and filters will blend with NIST data.";
112+
externalInfoVisible: boolean = false;
109113
examplesDialogVisible = false;
110114
placeHolderText: string[] = [
111115
"Artificial Intelligence",
@@ -259,6 +263,10 @@ export class SearchPanelComponent implements OnInit {
259263
this.imageURL = this.SDPAPI + "assets/images/ngi-background.png";
260264
});
261265

266+
this.searchService.watchExternalProducts().subscribe((enabled) => {
267+
this.includeExternalProducts = enabled;
268+
});
269+
262270
this.getTaxonomySuggestions();
263271
}
264272

@@ -292,6 +300,19 @@ export class SearchPanelComponent implements OnInit {
292300
this.currentState = this.showExampleStatus ? "final" : "initial";
293301
}
294302

303+
onExternalToggle(enabled: boolean) {
304+
this.includeExternalProducts = enabled;
305+
this.searchService.setExternalProducts(enabled);
306+
}
307+
308+
openExternalInfo() {
309+
this.externalInfoVisible = true;
310+
}
311+
312+
closeExternalInfo() {
313+
this.externalInfoVisible = false;
314+
}
315+
295316
/**
296317
* Hide syntax rules and field lookup help
297318
*/

angular/src/app/search/filters/filters.component.spec.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,39 @@ describe("FiltersComponent", () => {
5353
});
5454

5555
it("onSuccess", () => {
56-
component.onSuccess(searchResult);
57-
// Basic assertions adapted to minimal fixture
58-
expect(component.keywords).toContain("Advanced Functional Materials");
59-
expect(component.keywords.length).toBeGreaterThanOrEqual(1);
60-
expect(component.themesWithCount[0].label.startsWith("Nanotechnology")).toBeTruthy();
61-
expect(component.resourceTypesWithCount[0].label.startsWith("Public Data Resource")).toBeTruthy();
56+
component.onSuccess(searchResult);
57+
// Basic assertions adapted to minimal fixture
58+
expect(component.keywords).toContain("Advanced Functional Materials");
59+
expect(component.keywords.length).toBeGreaterThanOrEqual(1);
60+
expect(component.themesWithCount[0].label.startsWith("Nanotechnology")).toBeTruthy();
61+
expect(component.resourceTypesWithCount[0].label.startsWith("Public Data Resource")).toBeTruthy();
62+
});
63+
64+
it("ignores empty @type values when building resource types", () => {
65+
const resultsWithEmptyType = [
66+
{ "@type": ["", " ", "CodeRepository"], keyword: [], topic: [], components: [] },
67+
];
68+
component.onSuccess([...searchResult, ...resultsWithEmptyType]);
69+
const emptyLabels = component.resourceTypesWithCount.filter(
70+
(r) => !r.data || r.data.trim() === "" || r.label.startsWith("-")
71+
);
72+
expect(emptyLabels.length).toBe(0);
73+
const codeRepo = component.resourceTypesWithCount.find((r) =>
74+
r.label.toLowerCase().includes("code repository")
75+
);
76+
expect(codeRepo).toBeTruthy();
77+
});
78+
79+
it("collects keywords from tags and languages when keyword field is absent", () => {
80+
const codeRecord = {
81+
"@type": ["CodeRepository"],
82+
tags: ["github", "reflectometry"],
83+
languages: ["JavaScript", "HTML"],
84+
topic: [],
85+
components: [],
86+
};
87+
component.onSuccess([codeRecord]);
88+
expect(component.keywords).toContain("github");
89+
expect(component.keywords).toContain("javascript");
6290
});
6391
});

0 commit comments

Comments
 (0)