Skip to content

Feat/217 add new registries #131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jul 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8dfcb80
feat(registries): start work on registries
nmykhalkevch-exoft Jun 21, 2025
80325c0
feat(registries): work on new registrations
nmykhalkevch-exoft Jun 22, 2025
782ed30
feat(registries): add first registries page
nmykhalkevch-exoft Jun 23, 2025
a661107
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jun 23, 2025
ff1149b
feat(registries): fix comments
nmykhalkevch-exoft Jun 23, 2025
610aa53
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jun 24, 2025
d5d30c9
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jun 24, 2025
f3a589a
feat(registries): setup routes
nmykhalkevch-exoft Jun 24, 2025
fd429e4
feat(registries): setup routes
nmykhalkevch-exoft Jun 24, 2025
14b72f5
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jun 24, 2025
fe74ebf
feat(registries): add contributors section to metadata step
nmykhalkevch-exoft Jun 24, 2025
ad7feed
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jun 24, 2025
9d1df8d
Merge branch 'feat/217-add-new-registries' of github.com:CenterForOpe…
nmykhalkevch-exoft Jun 25, 2025
aa43a92
feat(registries): add licenses section
nmykhalkevch-exoft Jun 25, 2025
8745263
feat(registries): get schema-blocks for custom pages
nmykhalkevch-exoft Jun 25, 2025
f1e9da0
feat(registries): implement api for custom steps, refactor steper
nmykhalkevch-exoft Jun 26, 2025
e1c044a
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jun 26, 2025
6963bcb
feat(registries): update page-schema mapper
nmykhalkevch-exoft Jun 27, 2025
6292e9e
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jun 27, 2025
bee29ae
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jun 28, 2025
85e2ddd
feat(registries): add subjects section
nmykhalkevch-exoft Jul 1, 2025
a06e98a
Merge branch 'feat/217-add-new-registries' of github.com:CenterForOpe…
nmykhalkevch-exoft Jul 1, 2025
ea7c4e3
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jul 1, 2025
dc40c7d
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jul 1, 2025
a9684e5
feat(registries): add spme fixes
nmykhalkevch-exoft Jul 1, 2025
f677ef8
Merge branch 'main' of github.com:CenterForOpenScience/angular-osf in…
nmykhalkevch-exoft Jul 2, 2025
0c83d6f
feat(registries): PR comments, refactoring
nmykhalkevch-exoft Jul 2, 2025
0309f30
feat(registries): add fixes
nmykhalkevch-exoft Jul 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ export const routes: Routes = [
path: 'my-projects/:id',
loadChildren: () => import('./features/project/project.routes').then((mod) => mod.projectRoutes),
},
{
path: 'registries',
loadChildren: () => import('./features/registries/registries.routes').then((mod) => mod.registriesRoutes),
},

{
path: 'settings',
loadChildren: () => import('./features/settings/settings.routes').then((mod) => mod.settingsRoutes),
Expand Down
2 changes: 0 additions & 2 deletions src/app/core/constants/ngxs-states.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { NotificationSubscriptionState } from '@osf/features/settings/notificati
import { ProfileSettingsState } from '@osf/features/settings/profile-settings/store/profile-settings.state';
import { AddonsState, InstitutionsState } from '@shared/stores';
import { LicensesState } from '@shared/stores/licenses';
import { SubjectsState } from '@shared/stores/subjects';

export const STATES = [
AuthState,
Expand All @@ -35,6 +34,5 @@ export const STATES = [
MeetingsState,
RegistrationsState,
ProjectMetadataState,
SubjectsState,
LicensesState,
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<section class="flex flex-column bg-white flex-1 h-full p-5 gap-4 w-full">
@if (currentPage()) {
<h2>{{ currentPage().title }}</h2>

@let questions = currentPage().questions || [];

@if (currentPage().sections?.length) {
@for (section of currentPage().sections; track section.id) {
questions = section.questions;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What this for?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To replace the default page list question with a page section questions if exist

<p-card>
<h3 class="mb-2">{{ section.title }}</h3>
@if (section.description) {
<p class="mb-3">{{ section.description }}</p>
}
<ng-container *ngTemplateOutlet="questionList"></ng-container>
</p-card>
}
} @else {
<ng-container *ngTemplateOutlet="questionList"></ng-container>
}

<ng-template #questionList>
@for (question of questions; track question.id) {
<p-card>
<label [for]="question.groupKey">
<h3 class="mb-2">
{{ question.displayText }}
@if (question.required) {
<span class="text-red-500">*</span>
}
</h3>
@if (question.helpText) {
<p class="mb-3">{{ question.helpText }}</p>
}
@if (question.paragraphText) {
<p class="mb-3">{{ question.paragraphText }}</p>
}
</label>
@if (question.exampleText) {
<p-inplace #inplaceRef styleClass="mb-4">
<ng-template #display>
<span class="text-primary">{{ 'common.links.showExample' | translate }} </span>
</ng-template>
<ng-template #content>
<div class="p-inplace-display">
<button
class="text-primary border-none cursor-pointer bg-transparent text-sm"
tabindex="0"
role="button"
(click)="inplaceRef.deactivate()"
(keyup.enter)="inplaceRef.deactivate()"
(keyup.space)="inplaceRef.deactivate()"
>
{{ 'common.links.hideExample' | translate }}
</button>
</div>
<p class="m-0">{{ question.exampleText }}</p>
</ng-template>
</p-inplace>
}

@switch (question.fieldType) {
@case (FieldType.TextArea) {
<textarea id="{{ question.groupKey }}" class="w-full" rows="5" cols="30" pTextarea></textarea>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe here you need to add pTextarea.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is already here

}
@case (FieldType.Radio) {
<div class="flex flex-column gap-2">
@for (option of question.options; track option) {
<div class="flex align -items-center gap-2">
<p-radioButton
[inputId]="option.value"
[(ngModel)]="radio"
[name]="question.groupKey || ''"
[value]="option.value"
></p-radioButton>
<label [for]="option.value" class="ml-2">{{ option.label }}</label>
@if (option.helpText) {
<osf-info-icon [tooltipText]="option.helpText"></osf-info-icon>
}
</div>
}
</div>
}
@case (FieldType.Checkbox) {
<div class="flex flex-column gap-2">
@for (option of question.options; track option) {
<div class="flex align-items-center gap-2">
<p-checkbox
[inputId]="option.value"
[name]="question.groupKey || ''"
[value]="option.value"
></p-checkbox>
<label [for]="option.value" class="ml-2">{{ option.label }}</label>
</div>
}
</div>
}

@case (FieldType.Text) {
<input
id="{{ question.groupKey }}"
type="text"
class="w-full"
[placeholder]="question.exampleText"
pInputText
/>
}
@case (FieldType.File) {
<h3 class="mb-2">Upload File</h3>
<p class="mb-1">You may attach up to 5 file(s) to this question. Files cannot total over 5GB in size.</p>
<p>
Uploaded files will automatically be archived in this registration. They will also be added to a related
project that will be created for this registration.
</p>

<p>File input is not implemented yet.</p>
Comment on lines +109 to +116
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Translation

}
}
</p-card>
}
</ng-template>
}
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CustomStepComponent } from './custom-step.component';

describe('CustomStepComponent', () => {
let component: CustomStepComponent;
let fixture: ComponentFixture<CustomStepComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CustomStepComponent],
}).compileComponents();

fixture = TestBed.createComponent(CustomStepComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { select } from '@ngxs/store';

import { TranslatePipe } from '@ngx-translate/core';

import { Card } from 'primeng/card';
import { Checkbox } from 'primeng/checkbox';
import { Inplace } from 'primeng/inplace';
import { InputText } from 'primeng/inputtext';
import { RadioButton } from 'primeng/radiobutton';
import { Textarea } from 'primeng/textarea';

import { NgTemplateOutlet } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';

import { InfoIconComponent } from '@osf/shared/components';

import { FieldType } from '../../enums';
import { RegistriesSelectors } from '../../store';

@Component({
selector: 'osf-custom-step',
imports: [
Card,
Textarea,
RadioButton,
FormsModule,
Checkbox,

InputText,
NgTemplateOutlet,
Inplace,
TranslatePipe,
InfoIconComponent,
],
templateUrl: './custom-step.component.html',
styleUrl: './custom-step.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomStepComponent {
private readonly route = inject(ActivatedRoute);
step = signal(this.route.snapshot.params['step'].split('-')[0]);
protected readonly pages = select(RegistriesSelectors.getPagesSchema);
currentPage = computed(() => this.pages()[this.step() - 1]);
protected readonly FieldType = FieldType;

radio = null;

constructor() {
this.route.params.pipe(takeUntilDestroyed()).subscribe((params) => {
this.step.set(+params['step'].split('-')[0]);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<osf-sub-header [title]="'registries.new.addNewRegistry' | translate" />
<osf-stepper
class="block ml-4 mb-4"
[steps]="steps()"
[linear]="false"
[currentStep]="currentStep()"
(currentStepChange)="stepChange($event)"
/>
<router-outlet></router-outlet>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
height: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { DraftsComponent } from './drafts.component';

describe('DraftsComponent', () => {
let component: DraftsComponent;
let fixture: ComponentFixture<DraftsComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DraftsComponent],
}).compileComponents();

fixture = TestBed.createComponent(DraftsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
102 changes: 102 additions & 0 deletions src/app/features/registries/components/drafts/drafts.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { createDispatchMap, select } from '@ngxs/store';

import { TranslatePipe, TranslateService } from '@ngx-translate/core';

import { tap } from 'rxjs';

import { ChangeDetectionStrategy, Component, computed, effect, inject, Signal, signal } from '@angular/core';
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';

import { StepperComponent, SubHeaderComponent } from '@osf/shared/components';
import { StepOption } from '@osf/shared/models';
import { LoaderService } from '@osf/shared/services';

import { defaultSteps } from '../../constants';
import { FetchDraft, FetchSchemaBlocks, RegistriesSelectors } from '../../store';

@Component({
selector: 'osf-drafts',
imports: [RouterOutlet, StepperComponent, SubHeaderComponent, TranslatePipe],
templateUrl: './drafts.component.html',
styleUrl: './drafts.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [TranslateService],
})
export class DraftsComponent {
private readonly router = inject(Router);
private readonly route = inject(ActivatedRoute);
private readonly loaderService = inject(LoaderService);
private readonly translateService = inject(TranslateService);

protected readonly pages = select(RegistriesSelectors.getPagesSchema);
protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration);

private readonly actions = createDispatchMap({
getSchemaBlocks: FetchSchemaBlocks,
getDraftRegistration: FetchDraft,
});

get isReviewPage(): boolean {
return this.router.url.includes('/review');
}

defaultSteps: StepOption[] = defaultSteps.map((step) => ({
...step,
label: this.translateService.instant(step.label),
}));

steps: Signal<StepOption[]> = computed(() => {
const customSteps = this.pages().map((page) => ({
label: page.title,
value: page.id,
}));
return [this.defaultSteps[0], ...customSteps, this.defaultSteps[1]];
});

currentStep = signal(
this.route.snapshot.children[0]?.params['step'] ? +this.route.snapshot.children[0]?.params['step'].split('-')[0] : 0
);

registrationId = this.route.snapshot.children[0]?.params['id'] || '';

constructor() {
this.loaderService.show();
if (!this.draftRegistration()) {
this.actions.getDraftRegistration(this.registrationId);
}
effect(() => {
const registrationSchemaId = this.draftRegistration()?.registrationSchemaId;
if (registrationSchemaId) {
this.actions
.getSchemaBlocks(registrationSchemaId || '')
.pipe(
tap(() => {
this.loaderService.hide();
})
)
.subscribe();
}
});

effect(() => {
const reviewStepNumber = this.pages().length + 1;
if (this.isReviewPage) {
this.currentStep.set(reviewStepNumber);
}
});
}

stepChange(step: number): void {
// [NM] TODO: before navigating, validate the current step
this.currentStep.set(step);
const pageStep = this.steps()[step];

let pageLink = '';
if (!pageStep.value) {
pageLink = `${pageStep.routeLink}`;
} else {
pageLink = `${step}-${pageStep.value}`;
}
this.router.navigate([`/registries/drafts/${this.registrationId}/`, pageLink]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<p-card class="w-full">
<h2 class="mb-2">{{ 'project.overview.metadata.contributors' | translate }}</h2>
<osf-contributors-list
(focusout)="onFocusOut()"
class="w-full"
[contributors]="contributors()"
[isLoading]="isContributorsLoading()"
(remove)="removeContributor($event)"
(showEducationHistory)="openEducationHistory($event)"
(showEmploymentHistory)="openEmploymentHistory($event)"
></osf-contributors-list>
<div class="flex justify-content-end mt-3">
@if (hasChanges) {
<div class="flex gap-2 mr-4">
<p-button (click)="cancel()" text severity="info" [label]="'common.buttons.cancel' | translate"> </p-button>
<p-button (click)="save()" [label]="'common.buttons.save' | translate"> </p-button>
</div>
}
<p-button
[label]="'registries.metadata.addContributors' | translate"
type="submit"
(click)="openAddContributorDialog()"
></p-button>
</div>
</p-card>
Loading