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
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ import {
import { ResourceType, UserPermissions } from '@osf/shared/enums';
import { ToolbarResource } from '@osf/shared/models';
import { Duplicate } from '@osf/shared/models/duplicates';
import { CustomDialogService } from '@osf/shared/services';
import { ClearDuplicates, DuplicatesSelectors, GetAllDuplicates } from '@osf/shared/stores';
import { CustomDialogService, LoaderService } from '@osf/shared/services';
import { ClearDuplicates, DuplicatesSelectors, GetAllDuplicates, GetResourceWithChildren } from '@osf/shared/stores';

@Component({
selector: 'osf-view-duplicates',
Expand All @@ -65,6 +65,7 @@ import { ClearDuplicates, DuplicatesSelectors, GetAllDuplicates } from '@osf/sha
})
export class ViewDuplicatesComponent {
private customDialogService = inject(CustomDialogService);
private loaderService = inject(LoaderService);
private route = inject(ActivatedRoute);
private router = inject(Router);
private destroyRef = inject(DestroyRef);
Expand Down Expand Up @@ -125,6 +126,7 @@ export class ViewDuplicatesComponent {
clearDuplicates: ClearDuplicates,
clearProject: ClearProjectOverview,
clearRegistration: ClearRegistryOverview,
getComponentsTree: GetResourceWithChildren,
});

constructor() {
Expand Down Expand Up @@ -230,25 +232,38 @@ export class ViewDuplicatesComponent {
}

private handleDeleteFork(id: string): void {
this.customDialogService
.open(DeleteComponentDialogComponent, {
header: 'project.overview.dialog.deleteComponent.header',
width: '650px',
data: {
componentId: id,
resourceType: this.resourceType(),
isForksContext: true,
currentPage: parseInt(this.currentPage()),
pageSize: this.pageSize,
},
})
.onClose.subscribe((result) => {
if (result?.success) {
const resource = this.currentResource();
if (resource) {
this.actions.getDuplicates(resource.id, resource.type, parseInt(this.currentPage()), this.pageSize);
}
}
});
const resourceType = this.resourceType();
if (!resourceType) return;

this.loaderService.show();

this.actions.getComponentsTree(id, id, resourceType).subscribe({
next: () => {
this.loaderService.hide();
this.customDialogService
.open(DeleteComponentDialogComponent, {
header: 'project.overview.dialog.deleteComponent.header',
width: '650px',
data: {
componentId: id,
resourceType: resourceType,
isForksContext: true,
currentPage: parseInt(this.currentPage()),
pageSize: this.pageSize,
},
})
.onClose.subscribe((result) => {
if (result?.success) {
const resource = this.currentResource();
if (resource) {
this.actions.getDuplicates(resource.id, resource.type, parseInt(this.currentPage()), this.pageSize);
}
}
});
},
error: () => {
this.loaderService.hide();
},
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
styleClass="w-full"
(onClick)="addLink()"
[disabled]="linkName.invalid"
[label]="'project.contributors.addDialog.next' | translate"
[label]="'common.buttons.create' | translate"
></p-button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
<p>{{ 'project.overview.dialog.deleteComponent.message' | translate }}</p>
<br />
<p>
{{ 'project.overview.dialog.deleteComponent.confirmation' | translate }}
<strong> {{ selectedScientist() }}</strong>
</p>
@if (hasAdminAccessForAllComponents()) {
@if (hasSubcomponents()) {
<p>{{ 'project.overview.dialog.deleteComponent.listMessage' | translate }}</p>
@if (isLoading()) {
<p-skeleton styleClass="mt-2" width="100%" height="4rem" />
} @else {
<ul class="flex flex-column gap-1 mt-2 pl-4">
@for (component of components(); track component.id) {
<li class="list-disc">{{ component.title }}</li>
}
</ul>
}

<input pInputText class="mt-3" [ngModel]="userInput()" (ngModelChange)="onInputChange($event)" />
<p class="mt-4">{{ 'project.overview.dialog.deleteComponent.warningMessage' | translate }}</p>
} @else {
<p>{{ 'project.overview.dialog.deleteComponent.message' | translate }}</p>
}

<p class="mt-4">
{{ 'project.overview.dialog.deleteComponent.confirmation' | translate }}
<strong> {{ selectedScientist() }}</strong>
</p>

<input pInputText class="mt-3" [ngModel]="userInput()" (ngModelChange)="onInputChange($event)" />
} @else {
<p [innerHTML]="'project.overview.dialog.deleteComponent.noPermissionsMessage' | translate"></p>
}
<div class="flex pt-5 justify-content-end gap-3">
<p-button
class="w-12rem btn-full-width"
Expand All @@ -20,7 +38,7 @@
[label]="'project.overview.dialog.deleteComponent.confirmButton' | translate"
(onClick)="handleDeleteComponent()"
severity="danger"
[disabled]="isSubmitting() || !isInputValid()"
[disabled]="isSubmitting() || isLoading() || !isInputValid()"
[loading]="isSubmitting()"
/>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,24 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Button } from 'primeng/button';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
import { InputText } from 'primeng/inputtext';
import { Skeleton } from 'primeng/skeleton';

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

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

import { DeleteComponent, GetComponents, ProjectOverviewSelectors } from '../../store';
import { GetComponents, ProjectOverviewSelectors } from '../../store';

@Component({
selector: 'osf-delete-component-dialog',
imports: [TranslatePipe, Button, InputText, FormsModule],
imports: [TranslatePipe, Button, InputText, FormsModule, Skeleton],
templateUrl: './delete-component-dialog.component.html',
styleUrl: './delete-component-dialog.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -33,7 +36,9 @@ export class DeleteComponentDialogComponent {
scientistNames = ScientistsNames;
project = select(ProjectOverviewSelectors.getProject);
registration = select(RegistryOverviewSelectors.getRegistry);
isSubmitting = select(ProjectOverviewSelectors.getComponentsSubmitting);
isSubmitting = select(SettingsSelectors.isSettingsSubmitting);
isLoading = select(CurrentResourceSelectors.isResourceWithChildrenLoading);
components = select(CurrentResourceSelectors.getResourceWithChildren);
userInput = signal('');
selectedScientist = computed(() => {
const names = Object.values(this.scientistNames);
Expand All @@ -52,9 +57,21 @@ export class DeleteComponentDialogComponent {
return null;
});

hasAdminAccessForAllComponents = computed(() => {
const components = this.components();
if (!components || !components.length) return false;

return components.every((component) => component.permissions?.includes(UserPermissions.Admin));
});

hasSubcomponents = computed(() => {
const components = this.components();
return components && components.length > 1;
});

actions = createDispatchMap({
getComponents: GetComponents,
deleteComponent: DeleteComponent,
deleteComponent: DeleteProject,
});

isInputValid(): boolean {
Expand All @@ -66,25 +83,24 @@ export class DeleteComponentDialogComponent {
}

handleDeleteComponent(): void {
const resource = this.currentResource();
const componentId = this.componentId();
const components = this.components();

if (!componentId || !resource) return;
if (!components?.length) return;

this.actions
.deleteComponent(componentId)
.deleteComponent(components)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: () => {
this.dialogRef.close({ success: true });

const isForksContext = this.dialogConfig.data.isForksContext;
const resource = this.currentResource();

if (!isForksContext) {
if (!isForksContext && resource) {
this.actions.getComponents(resource.id);
}
},
complete: () => {

this.toastService.showSuccess('project.overview.dialog.toast.deleteComponent.success');
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { select } from '@ngxs/store';
import { createDispatchMap, select } from '@ngxs/store';

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

Expand All @@ -12,7 +12,8 @@ import { Router } from '@angular/router';
import { UserSelectors } from '@core/store/user';
import { ContributorsListComponent, IconComponent, TruncatedTextComponent } from '@osf/shared/components';
import { ResourceType, UserPermissions } from '@osf/shared/enums';
import { CustomDialogService } from '@osf/shared/services';
import { CustomDialogService, LoaderService } from '@osf/shared/services';
import { GetResourceWithChildren } from '@osf/shared/stores';
import { ComponentOverview } from '@shared/models';

import { ProjectOverviewSelectors } from '../../store';
Expand All @@ -29,13 +30,19 @@ import { DeleteComponentDialogComponent } from '../delete-component-dialog/delet
export class OverviewComponentsComponent {
private router = inject(Router);
private customDialogService = inject(CustomDialogService);
private loaderService = inject(LoaderService);

canEdit = input.required<boolean>();

currentUser = select(UserSelectors.getCurrentUser);
currentUserId = computed(() => this.currentUser()?.id);
components = select(ProjectOverviewSelectors.getComponents);
isComponentsLoading = select(ProjectOverviewSelectors.getComponentsLoading);
project = select(ProjectOverviewSelectors.getProject);

actions = createDispatchMap({
getComponentsTree: GetResourceWithChildren,
});

readonly componentActionItems = (component: ComponentOverview) => {
const baseItems = [
Expand Down Expand Up @@ -92,10 +99,23 @@ export class OverviewComponentsComponent {
}

private handleDeleteComponent(componentId: string): void {
this.customDialogService.open(DeleteComponentDialogComponent, {
header: 'project.overview.dialog.deleteComponent.header',
width: '650px',
data: { componentId, resourceType: ResourceType.Project },
const project = this.project();
if (!project) return;

this.loaderService.show();

this.actions.getComponentsTree(project.rootParentId || project.id, componentId, ResourceType.Project).subscribe({
next: () => {
this.loaderService.hide();
this.customDialogService.open(DeleteComponentDialogComponent, {
header: 'project.overview.dialog.deleteComponent.header',
width: '650px',
data: { componentId, resourceType: ResourceType.Project },
});
},
error: () => {
this.loaderService.hide();
},
});
}
}
1 change: 1 addition & 0 deletions src/app/features/project/project.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const projectRoutes: Routes = [
CollectionsModerationState,
ActivityLogsState,
SubjectsState,
SettingsState,
]),
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@if (isLoading() || tableData().items.length) {
@if (isLoading() || tableData().items?.length) {
<p-table [value]="isLoading() ? skeletonData : tableData().items" class="view-only-table">
<ng-template #header>
<tr>
Expand Down Expand Up @@ -35,14 +35,15 @@
{{ item.name }}
</td>
<td>
<div class="relative flex align-items-center">
<div class="relative">
<input
[id]="item.link"
[attr.aria-label]="'Link for ' + item.link"
pInputText
class="h-auto text-base w-max py-2 pl-3 pr-5"
class="h-auto text-base w-full py-2 pl-3 pr-5"
type="text"
[value]="item.link"
readonly
/>

<osf-copy-button class="icon-copy-btn absolute" [copyItem]="item.link"></osf-copy-button>
Expand Down
9 changes: 6 additions & 3 deletions src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -823,11 +823,14 @@
}
},
"deleteComponent": {
"header": "Are you sure you want to delete this component?",
"header": "Delete component",
"confirmButton": "Delete",
"cancelButton": "Cancel",
"message": "It will no longer be available to other contributors on the project.",
"confirmation": "Type the following to continue:"
"listMessage": "You are about to delete the following:",
"warningMessage": "This component contains subcomponents. To delete this component, you must also delete all subcomponents. This action is irreversible.",
"confirmation": "Type the following to continue:",
"noPermissionsMessage": "You do not have permissions to delete this component and its subcomponents. <br/><br/> To delete, you must have admin permissions across all objects. Please check your permissions and try again. <br/><br/> Contact support at support@osf.io if you have any questions."
},
"deleteNodeLink": {
"header": "Delete Link",
Expand Down Expand Up @@ -3062,4 +3065,4 @@
"software": "Software",
"other": "Other"
}
}
}
Loading