Skip to content

Commit 01c9e4e

Browse files
authored
Merge pull request #195 from CenterForOpenScience/feat/245-registry-resources
feat(registry-resources): resources ui draft
2 parents d2b104e + 1ac8301 commit 01c9e4e

File tree

50 files changed

+1216
-28
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1216
-28
lines changed

src/app/features/moderation/models/moderator-json-api.model.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,4 @@ export interface ModeratorAddRequestModel {
2727
full_name?: string;
2828
email?: string;
2929
};
30-
// relationships: {
31-
// users?: {
32-
// data?: RelationshipUsersData;
33-
// };
34-
// };
35-
}
36-
37-
interface RelationshipUsersData {
38-
id?: string;
39-
type?: 'users';
4030
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
@if (isCurrentResourceLoading() || isResourceConfirming()) {
2+
<osf-loading-spinner></osf-loading-spinner>
3+
} @else if (isPreviewMode() && currentResource()) {
4+
<form class="flex flex-column gap-4" [formGroup]="form">
5+
<p>{{ 'resources.check' | translate }}</p>
6+
7+
@let resourceType = currentResource()?.type;
8+
@let iconName = resourceType === 'analytic_code' ? 'code' : resourceType;
9+
@let icon = `assets/icons/colored/${iconName}-colored.svg`;
10+
@let resourceName = resourceType === RegistryResourceType.Code ? 'Analytic Code' : resourceType;
11+
12+
<div class="flex align-content-end gap-3 content">
13+
<img [src]="icon" alt="resource-type icon" class="align-self-start" />
14+
<div class="flex flex-column gap-3 mt-1">
15+
<div class="flex flex-column gap-2">
16+
<h2>{{ resourceName }}</h2>
17+
<a [href]="'https://doi/' + currentResource()?.pid">https://doi/{{ currentResource()?.pid }}</a>
18+
</div>
19+
<p>{{ currentResource()?.description }}</p>
20+
</div>
21+
</div>
22+
23+
<div class="flex justify-content-between w-full gap-1">
24+
<p-button
25+
class="btn-full-width"
26+
[label]="'common.buttons.edit' | translate"
27+
severity="info"
28+
(click)="backToEdit()"
29+
/>
30+
<p-button
31+
class="btn-full-width"
32+
[label]="'resources.add' | translate"
33+
severity="primary"
34+
(onClick)="onAddResource()"
35+
/>
36+
</div>
37+
</form>
38+
} @else {
39+
<osf-resource-form
40+
[formGroup]="form"
41+
[showCancelButton]="true"
42+
[showPreviewButton]="false"
43+
[cancelButtonLabel]="'common.buttons.cancel'"
44+
[primaryButtonLabel]="'common.buttons.preview'"
45+
(cancelClicked)="closeDialog()"
46+
(submitClicked)="previewResource()"
47+
/>
48+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.content {
2+
overflow: hidden;
3+
text-overflow: clip;
4+
white-space: wrap;
5+
word-wrap: break-word;
6+
overflow-wrap: break-word;
7+
word-break: break-word;
8+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { AddResourceDialogComponent } from './add-resource-dialog.component';
4+
5+
describe('AddResourceDialogComponent', () => {
6+
let component: AddResourceDialogComponent;
7+
let fixture: ComponentFixture<AddResourceDialogComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [AddResourceDialogComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(AddResourceDialogComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { createDispatchMap, select } from '@ngxs/store';
2+
3+
import { TranslatePipe, TranslateService } from '@ngx-translate/core';
4+
5+
import { Button } from 'primeng/button';
6+
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
7+
8+
import { finalize, take } from 'rxjs';
9+
10+
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
11+
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
12+
13+
import { LoadingSpinnerComponent } from '@osf/shared/components';
14+
import { InputLimits } from '@osf/shared/constants';
15+
import { RegistryResourceType } from '@osf/shared/enums';
16+
import { SelectOption } from '@osf/shared/models';
17+
import { CustomValidators } from '@osf/shared/utils';
18+
19+
import { resourceTypeOptions } from '../../constants';
20+
import { AddResource, ConfirmAddResource } from '../../models';
21+
import {
22+
ConfirmAddRegistryResource,
23+
PreviewRegistryResource,
24+
RegistryResourcesSelectors,
25+
SilentDelete,
26+
} from '../../store/registry-resources';
27+
import { ResourceFormComponent } from '../resource-form/resource-form.component';
28+
29+
@Component({
30+
selector: 'osf-add-resource-dialog',
31+
imports: [Button, TranslatePipe, ReactiveFormsModule, LoadingSpinnerComponent, ResourceFormComponent],
32+
templateUrl: './add-resource-dialog.component.html',
33+
styleUrl: './add-resource-dialog.component.scss',
34+
changeDetection: ChangeDetectionStrategy.OnPush,
35+
})
36+
export class AddResourceDialogComponent {
37+
protected readonly dialogRef = inject(DynamicDialogRef);
38+
protected readonly currentResource = select(RegistryResourcesSelectors.getCurrentResource);
39+
protected readonly isCurrentResourceLoading = select(RegistryResourcesSelectors.isCurrentResourceLoading);
40+
private translateService = inject(TranslateService);
41+
42+
private dialogConfig = inject(DynamicDialogConfig);
43+
private registryId: string = this.dialogConfig.data.id;
44+
45+
protected inputLimits = InputLimits;
46+
protected isResourceConfirming = signal(false);
47+
48+
protected form = new FormGroup({
49+
pid: new FormControl<string | null>('', [CustomValidators.requiredTrimmed(), CustomValidators.doiValidator]),
50+
resourceType: new FormControl<string | null>('', [Validators.required]),
51+
description: new FormControl<string | null>(''),
52+
});
53+
54+
private readonly actions = createDispatchMap({
55+
previewResource: PreviewRegistryResource,
56+
confirmAddResource: ConfirmAddRegistryResource,
57+
deleteResource: SilentDelete,
58+
});
59+
60+
public resourceOptions = signal<SelectOption[]>(resourceTypeOptions);
61+
public isPreviewMode = signal<boolean>(false);
62+
63+
protected readonly RegistryResourceType = RegistryResourceType;
64+
65+
previewResource(): void {
66+
if (this.form.invalid) {
67+
return;
68+
}
69+
70+
const addResource: AddResource = {
71+
pid: this.form.controls['pid'].value ?? '',
72+
resource_type: this.form.controls['resourceType'].value ?? '',
73+
description: this.form.controls['description'].value ?? '',
74+
};
75+
76+
const currentResource = this.currentResource();
77+
if (!currentResource) {
78+
throw new Error(this.translateService.instant('resources.errors.noCurrentResource'));
79+
}
80+
81+
this.actions.previewResource(currentResource.id, addResource).subscribe(() => {
82+
this.isPreviewMode.set(true);
83+
});
84+
}
85+
86+
backToEdit() {
87+
this.isPreviewMode.set(false);
88+
}
89+
90+
onAddResource() {
91+
const addResource: ConfirmAddResource = {
92+
finalized: true,
93+
};
94+
const currentResource = this.currentResource();
95+
96+
if (!currentResource) {
97+
throw new Error(this.translateService.instant('resources.errors.noRegistryId'));
98+
}
99+
100+
this.isResourceConfirming.set(true);
101+
this.actions
102+
.confirmAddResource(addResource, currentResource.id, this.registryId)
103+
.pipe(
104+
take(1),
105+
finalize(() => {
106+
this.dialogRef.close(true);
107+
this.isResourceConfirming.set(false);
108+
})
109+
)
110+
.subscribe({});
111+
}
112+
113+
closeDialog(): void {
114+
this.dialogRef.close();
115+
const currentResource = this.currentResource();
116+
if (!currentResource) {
117+
throw new Error(this.translateService.instant('resources.errors.noRegistryId'));
118+
}
119+
this.actions.deleteResource(currentResource.id);
120+
}
121+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@if (isCurrentResourceLoading()) {
2+
<osf-loading-spinner></osf-loading-spinner>
3+
} @else {
4+
<osf-resource-form
5+
[formGroup]="form"
6+
[showCancelButton]="true"
7+
[showPreviewButton]="false"
8+
[cancelButtonLabel]="'common.buttons.cancel'"
9+
[primaryButtonLabel]="'common.buttons.save'"
10+
(cancelClicked)="dialogRef.close()"
11+
(submitClicked)="save()"
12+
/>
13+
}

src/app/features/registry/components/edit-resource-dialog/edit-resource-dialog.component.scss

Whitespace-only changes.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { EditResourceDialogComponent } from './edit-resource-dialog.component';
4+
5+
describe('EditResourceDialogComponent', () => {
6+
let component: EditResourceDialogComponent;
7+
let fixture: ComponentFixture<EditResourceDialogComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [EditResourceDialogComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(EditResourceDialogComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { createDispatchMap, select } from '@ngxs/store';
2+
3+
import { TranslateService } from '@ngx-translate/core';
4+
5+
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
6+
7+
import { finalize, take } from 'rxjs';
8+
9+
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
10+
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
11+
12+
import { LoadingSpinnerComponent } from '@osf/shared/components';
13+
import { CustomValidators } from '@osf/shared/utils';
14+
15+
import { AddResource, RegistryResource } from '../../models';
16+
import { RegistryResourcesSelectors, UpdateResource } from '../../store/registry-resources';
17+
import { ResourceFormComponent } from '../resource-form/resource-form.component';
18+
19+
@Component({
20+
selector: 'osf-edit-resource-dialog',
21+
imports: [LoadingSpinnerComponent, ReactiveFormsModule, ResourceFormComponent],
22+
templateUrl: './edit-resource-dialog.component.html',
23+
styleUrl: './edit-resource-dialog.component.scss',
24+
changeDetection: ChangeDetectionStrategy.OnPush,
25+
})
26+
export class EditResourceDialogComponent {
27+
protected readonly dialogRef = inject(DynamicDialogRef);
28+
protected readonly isCurrentResourceLoading = select(RegistryResourcesSelectors.isCurrentResourceLoading);
29+
private translateService = inject(TranslateService);
30+
31+
private dialogConfig = inject(DynamicDialogConfig);
32+
private registryId: string = this.dialogConfig.data.id;
33+
private resource: RegistryResource = this.dialogConfig.data.resource as RegistryResource;
34+
35+
protected form = new FormGroup({
36+
pid: new FormControl<string | null>('', [CustomValidators.requiredTrimmed(), CustomValidators.doiValidator]),
37+
resourceType: new FormControl<string | null>('', [Validators.required]),
38+
description: new FormControl<string | null>(''),
39+
});
40+
41+
private readonly actions = createDispatchMap({
42+
updateResource: UpdateResource,
43+
});
44+
45+
constructor() {
46+
this.form.patchValue({
47+
pid: this.resource.pid || '',
48+
resourceType: this.resource.type || '',
49+
description: this.resource.description || '',
50+
});
51+
}
52+
53+
save() {
54+
if (this.form.invalid) {
55+
return;
56+
}
57+
58+
const addResource: AddResource = {
59+
pid: this.form.controls['pid'].value ?? '',
60+
resource_type: this.form.controls['resourceType'].value ?? '',
61+
description: this.form.controls['description'].value ?? '',
62+
};
63+
64+
if (!this.resource.id) {
65+
throw new Error(this.translateService.instant('resources.errors.noRegistryId'));
66+
}
67+
68+
this.actions
69+
.updateResource(this.registryId, this.resource.id, addResource)
70+
.pipe(
71+
take(1),
72+
finalize(() => {
73+
this.dialogRef.close(true);
74+
})
75+
)
76+
.subscribe();
77+
}
78+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
export * from './add-resource-dialog/add-resource-dialog.component';
2+
export * from './edit-resource-dialog/edit-resource-dialog.component';
13
export * from './registration-links-card/registration-links-card.component';
24
export * from './registry-revisions/registry-revisions.component';
35
export * from './registry-statuses/registry-statuses.component';
6+
export * from './resource-form/resource-form.component';
47
export * from './withdraw-dialog/withdraw-dialog.component';

0 commit comments

Comments
 (0)