Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion src/app/core/components/root/root.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@
</div>
</div>

<p-confirm-dialog id="dialog" styleClass="w-full md:w-6 xl:w-5" [header]="'common.dialogs.confirmation' | translate" />
<p-confirm-dialog
id="dialog"
styleClass="w-full md:w-6 xl:w-5"
[header]="'common.accessibility.confirmation' | translate"
/>
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<strong> {{ selectedScientist() }}</strong>
</p>

<input pInputText class="mt-3" [ngModel]="userInput()" (ngModelChange)="onInputChange($event)" />
<input pInputText class="mt-3" [(ngModel)]="userInput" />
} @else {
<p [innerHTML]="'project.deleteProject.dialog.noPermissionsMessage' | translate"></p>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';

import { DeleteProject, SettingsSelectors } from '@osf/features/project/settings/store';
import { ScientistsNames } from '@osf/shared/constants';
import { UserPermissions } from '@osf/shared/enums';
import { ToastService } from '@osf/shared/services';
import { CurrentResourceSelectors } from '@osf/shared/stores';
import { UserPermissions } from '@shared/enums';

import { DeleteProject, SettingsSelectors } from '../../store';

@Component({
selector: 'osf-delete-project-dialog',
Expand All @@ -28,6 +29,7 @@ import { UserPermissions } from '@shared/enums';
export class DeleteProjectDialogComponent {
private toastService = inject(ToastService);
private router = inject(Router);

dialogRef = inject(DynamicDialogRef);
destroyRef = inject(DestroyRef);

Expand All @@ -42,32 +44,22 @@ export class DeleteProjectDialogComponent {
const projects = this.projects();
if (!projects || !projects.length) return false;

return projects.every((project) => {
return project.permissions?.includes(UserPermissions.Admin);
});
return projects.every((project) => project.permissions?.includes(UserPermissions.Admin));
});

selectedScientist = computed(() => {
const names = Object.values(this.scientistNames);
return names[Math.floor(Math.random() * names.length)];
});

actions = createDispatchMap({
deleteProject: DeleteProject,
});

isInputValid(): boolean {
return this.userInput() === this.selectedScientist();
}
actions = createDispatchMap({ deleteProject: DeleteProject });

onInputChange(value: string): void {
this.userInput.set(value);
}
isInputValid = computed(() => this.userInput() === this.selectedScientist());

handleDeleteProject(): void {
const projects = this.projects();

if (!projects || !projects.length) return;
if (!projects?.length) return;

this.actions
.deleteProject(projects)
Expand Down
7 changes: 4 additions & 3 deletions src/app/features/project/settings/mappers/settings.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { RegionsMapper } from '@osf/shared/mappers/regions';
import {
NodeDataJsonApi,
NodeDetailsModel,
ProjectSettingsData,
ProjectSettingsDataJsonApi,
ProjectSettingsModel,
ProjectSettingsResponseModel,
ProjectSettingsResponseJsonApi,
} from '../models';

export class SettingsMapper {
static fromResponse(
response: ProjectSettingsResponseModel | ProjectSettingsData,
response: ProjectSettingsResponseJsonApi | ProjectSettingsDataJsonApi,
nodeId: string
): ProjectSettingsModel {
const result = 'data' in response ? response.data : response;
Expand All @@ -38,6 +38,7 @@ export class SettingsMapper {
? InstitutionsMapper.fromInstitutionsResponse(data.embeds.affiliated_institutions)
: [],
currentUserPermissions: data.attributes.current_user_permissions as UserPermissions[],
rootId: data.relationships.root?.data?.id,
parentId: data.relationships.parent?.data?.id,
lastFetched: Date.now(),
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { UserPermissions } from '@osf/shared/enums';
import { IdName, Institution } from '@osf/shared/models';

export interface NodeDetailsModel {
Expand All @@ -7,7 +8,8 @@ export interface NodeDetailsModel {
isPublic: boolean;
region: IdName;
affiliatedInstitutions: Institution[];
currentUserPermissions: string[];
currentUserPermissions: UserPermissions[];
rootId?: string;
parentId?: string;
lastFetched: number;
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
export interface ProjectSettingsAttributes {
access_requests_enabled: boolean;
anyone_can_edit_wiki: boolean;
wiki_enabled: boolean;
}

export interface RelatedLink {
href: string;
meta: Record<string, unknown>;
}

export interface ProjectSettingsRelationships {
view_only_links: {
links: {
related: RelatedLink;
};
export interface ProjectSettingsResponseJsonApi {
data: ProjectSettingsDataJsonApi;
meta: {
version: string;
};
}

export interface ProjectSettingsData {
export interface ProjectSettingsDataJsonApi {
id: string;
type: 'node-settings';
attributes: ProjectSettingsAttributes;
relationships: ProjectSettingsRelationships;
attributes: ProjectSettingsAttributesJsonApi;
relationships: ProjectSettingsRelationshipsJsonApi;
links: {
self: string;
iri: string;
};
}

export interface ProjectSettingsResponseModel {
data: ProjectSettingsData;
meta: {
version: string;
export interface ProjectSettingsAttributesJsonApi {
access_requests_enabled: boolean;
anyone_can_edit_wiki: boolean;
wiki_enabled: boolean;
}

interface ProjectSettingsRelationshipsJsonApi {
view_only_links: {
links: {
related: RelatedLinkJsonApi;
};
};
}

interface RelatedLinkJsonApi {
href: string;
meta: Record<string, unknown>;
}
10 changes: 5 additions & 5 deletions src/app/features/project/settings/services/settings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import {
NodeDataJsonApi,
NodeDetailsModel,
NodeResponseJsonApi,
ProjectSettingsData,
ProjectSettingsDataJsonApi,
ProjectSettingsModel,
ProjectSettingsResponseModel,
ProjectSettingsResponseJsonApi,
} from '../models';

@Injectable({
Expand All @@ -37,13 +37,13 @@ export class SettingsService {

getProjectSettings(nodeId: string): Observable<ProjectSettingsModel> {
return this.jsonApiService
.get<ProjectSettingsResponseModel>(`${this.apiUrl}/nodes/${nodeId}/settings/`)
.get<ProjectSettingsResponseJsonApi>(`${this.apiUrl}/nodes/${nodeId}/settings/`)
.pipe(map((response) => SettingsMapper.fromResponse(response, nodeId)));
}

updateProjectSettings(model: ProjectSettingsData): Observable<ProjectSettingsModel> {
updateProjectSettings(model: ProjectSettingsDataJsonApi): Observable<ProjectSettingsModel> {
return this.jsonApiService
.patch<ProjectSettingsResponseModel>(`${this.apiUrl}/nodes/${model.id}/settings/`, { data: model })
.patch<ProjectSettingsResponseJsonApi>(`${this.apiUrl}/nodes/${model.id}/settings/`, { data: model })
.pipe(map((response) => SettingsMapper.fromResponse(response, model.id)));
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/features/project/settings/settings.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@
(anyoneCanEditWikiEmitValue)="onAnyoneCanEditWikiRequestChange($event)"
[wikiEnabled]="wikiEnabled()"
[anyoneCanEditWiki]="anyoneCanEditWiki()"
[title]="title()"
[title]="projectDetails().title"
[isPublic]="projectDetails().isPublic"
/>
}

<osf-project-setting-notifications
(notificationEmitValue)="onNotificationRequestChange($event)"
[title]="title()"
[title]="projectDetails().title"
[notifications]="notifications()"
/>

Expand Down
52 changes: 20 additions & 32 deletions src/app/features/project/settings/settings.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@ import { TranslatePipe } from '@ngx-translate/core';

import { map, of } from 'rxjs';

import { ChangeDetectionStrategy, Component, computed, effect, inject, OnInit, signal } from '@angular/core';
import { ChangeDetectionStrategy, Component, effect, inject, OnInit, signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';

import { UserSelectors } from '@core/store/user';
import { LoadingSpinnerComponent, SubHeaderComponent } from '@osf/shared/components';
import { ResourceType, SubscriptionEvent, SubscriptionFrequency, UserPermissions } from '@osf/shared/enums';
import { IS_MEDIUM } from '@osf/shared/helpers';
import { ResourceType, SubscriptionEvent, SubscriptionFrequency } from '@osf/shared/enums';
import { Institution, UpdateNodeRequestModel, ViewOnlyLinkModel } from '@osf/shared/models';
import { CustomConfirmationService, CustomDialogService, LoaderService, ToastService } from '@osf/shared/services';
import {
CurrentResourceSelectors,
DeleteViewOnlyLink,
FetchViewOnlyLinks,
GetResource,
Expand All @@ -34,7 +32,7 @@ import {
SettingsViewOnlyLinksCardComponent,
SettingsWikiCardComponent,
} from './components';
import { ProjectDetailsModel, ProjectSettingsAttributes, ProjectSettingsData } from './models';
import { ProjectDetailsModel, ProjectSettingsAttributesJsonApi, ProjectSettingsDataJsonApi } from './models';
import {
DeleteInstitution,
DeleteProject,
Expand Down Expand Up @@ -76,18 +74,16 @@ export class SettingsComponent implements OnInit {

readonly projectId = toSignal(this.route.parent?.params.pipe(map((params) => params['id'])) ?? of(undefined));

currentUser = select(UserSelectors.getCurrentUser);
settings = select(SettingsSelectors.getSettings);
notifications = select(SettingsSelectors.getNotificationSubscriptions);
areNotificationsLoading = select(SettingsSelectors.areNotificationsLoading);
projectDetails = select(SettingsSelectors.getProjectDetails);
areProjectDetailsLoading = select(SettingsSelectors.areProjectDetailsLoading);
hasAdminAccess = select(SettingsSelectors.hasAdminAccess);
hasWriteAccess = select(SettingsSelectors.hasWriteAccess);
viewOnlyLinks = select(ViewOnlyLinkSelectors.getViewOnlyLinks);
isViewOnlyLinksLoading = select(ViewOnlyLinkSelectors.isViewOnlyLinksLoading);
currentUser = select(UserSelectors.getCurrentUser);
currentProject = select(CurrentResourceSelectors.getCurrentResource);
isMedium = toSignal(inject(IS_MEDIUM));

rootProjectId = computed(() => this.currentProject()?.rootResourceId);

actions = createDispatchMap({
getSettings: GetProjectSettings,
Expand All @@ -108,11 +104,6 @@ export class SettingsComponent implements OnInit {
wikiEnabled = signal(false);
anyoneCanEditWiki = signal(false);
anyoneCanComment = signal(false);
title = signal('');

userPermissions = computed(() => this.projectDetails()?.currentUserPermissions || []);
hasAdminAccess = computed(() => this.userPermissions().includes(UserPermissions.Admin));
hasWriteAccess = computed(() => this.userPermissions().includes(UserPermissions.Write));

constructor() {
this.setupEffects();
Expand Down Expand Up @@ -190,14 +181,19 @@ export class SettingsComponent implements OnInit {
}

deleteProject(): void {
const dialogWidth = this.isMedium() ? '500px' : '95vw';

this.actions.getComponentsTree(this.rootProjectId() || this.projectId(), this.projectId(), ResourceType.Project);
this.loaderService.show();

this.customDialogService.open(DeleteProjectDialogComponent, {
header: 'project.deleteProject.dialog.deleteProject',
width: dialogWidth,
});
this.actions
.getComponentsTree(this.projectDetails()?.rootId || this.projectId(), this.projectId(), ResourceType.Project)
.subscribe({
next: () => {
this.loaderService.hide();
this.customDialogService.open(DeleteProjectDialogComponent, {
header: 'project.deleteProject.dialog.deleteProject',
width: '500px',
});
},
});
}

removeAffiliation(affiliation: Institution): void {
Expand All @@ -217,7 +213,7 @@ export class SettingsComponent implements OnInit {
}

private syncSettingsChanges(changedField: string, value: boolean): void {
const payload: Partial<ProjectSettingsAttributes> = {};
const payload: Partial<ProjectSettingsAttributesJsonApi> = {};

switch (changedField) {
case 'access_requests_enabled':
Expand All @@ -231,7 +227,7 @@ export class SettingsComponent implements OnInit {
id: this.projectId(),
type: 'node-settings',
attributes: { ...payload },
} as ProjectSettingsData;
} as ProjectSettingsDataJsonApi;

this.loaderService.show();

Expand All @@ -253,14 +249,6 @@ export class SettingsComponent implements OnInit {
}
});

effect(() => {
const project = this.projectDetails();

if (project) {
this.title.set(project.title);
}
});

effect(() => {
const id = this.projectId();

Expand Down
4 changes: 2 additions & 2 deletions src/app/features/project/settings/store/settings.actions.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SubscriptionFrequency } from '@osf/shared/enums';
import { NodeShortInfoModel, UpdateNodeRequestModel } from '@shared/models';

import { ProjectSettingsData } from '../models';
import { ProjectSettingsDataJsonApi } from '../models';

export class GetProjectSettings {
static readonly type = '[Project Settings] Get Project Settings';
Expand All @@ -18,7 +18,7 @@ export class GetProjectDetails {
export class UpdateProjectSettings {
static readonly type = '[Project Settings] Update Project Settings';

constructor(public payload: ProjectSettingsData) {}
constructor(public payload: ProjectSettingsDataJsonApi) {}
}

export class UpdateProjectDetails {
Expand Down
11 changes: 11 additions & 0 deletions src/app/features/project/settings/store/settings.selectors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Selector } from '@ngxs/store';

import { UserPermissions } from '@osf/shared/enums';
import { NotificationSubscription } from '@osf/shared/models';

import { SettingsStateModel } from './settings.model';
Expand Down Expand Up @@ -35,4 +36,14 @@ export class SettingsSelectors {
static areNotificationsLoading(state: SettingsStateModel): boolean {
return state.notifications.isLoading;
}

@Selector([SettingsState])
static hasWriteAccess(state: SettingsStateModel): boolean {
return state.projectDetails.data?.currentUserPermissions?.includes(UserPermissions.Write) || false;
}

@Selector([SettingsState])
static hasAdminAccess(state: SettingsStateModel): boolean {
return state.projectDetails.data?.currentUserPermissions?.includes(UserPermissions.Admin) || false;
}
}
Loading
Loading