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
2 changes: 1 addition & 1 deletion src/app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import * as Sentry from '@sentry/angular';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes, withInMemoryScrolling({ scrollPositionRestoration: 'enabled', anchorScrolling: 'enabled' })),
provideRouter(routes, withInMemoryScrolling({ scrollPositionRestoration: 'top', anchorScrolling: 'enabled' })),
provideStore(STATES, withNgxsReduxDevtoolsPlugin({ disabled: false })),
providePrimeNG({
theme: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,66 +13,68 @@
<p>{{ 'project.overview.dialog.fork.forksMessage' | translate }}</p>

@for (duplicate of duplicates(); track duplicate.id) {
@if (duplicate.currentUserPermissions.includes(UserPermissions.Read)) {
<div class="duplicate-wrapper flex flex-column gap-3 p-3 sm:p-4">
<div class="flex justify-content-between align-items-center">
<h2 class="flex align-items-center gap-2">
<osf-icon [iconClass]="duplicate.public ? 'fas fa-lock-open' : 'fas fa-lock'"></osf-icon>
{{ duplicate.title }}
</h2>
<div class="duplicate-wrapper flex flex-column gap-3 p-3 sm:p-4">
<div class="flex justify-content-between align-items-center">
<h2 class="flex align-items-center gap-2">
<osf-icon [iconClass]="duplicate.public ? 'fas fa-lock-open' : 'fas fa-lock'"></osf-icon>
{{ duplicate.title }}
</h2>

<div>
@if (duplicate.currentUserPermissions.includes(UserPermissions.Write)) {
<p-button
severity="contrast"
icon="fas fa-ellipsis-vertical"
raised
variant="outlined"
[ariaLabel]="'common.buttons.more' | translate"
(onClick)="componentActionMenu.toggle($event)"
>
</p-button>
}
<div>
@if (showMoreOptions(duplicate)) {
<p-button
severity="contrast"
icon="fas fa-ellipsis-vertical"
raised
variant="outlined"
[ariaLabel]="'common.buttons.more' | translate"
(onClick)="componentActionMenu.toggle($event)"
>
</p-button>
}

<p-menu appendTo="body" #componentActionMenu [model]="forkActionItems(duplicate.id)" popup>
<ng-template #item let-item>
<a class="p-menu-item-link">{{ item.label | translate }}</a>
</ng-template>
</p-menu>
</div>
<p-menu appendTo="body" #componentActionMenu [model]="forkActionItems(duplicate.id)" popup>
<ng-template #item let-item>
<a class="p-menu-item-link">{{ item.label | translate }}</a>
</ng-template>
</p-menu>
</div>
</div>

<div class="component-name flex flex-column gap-2">
<div class="flex flex-wrap align-items-center gap-1">
<span class="font-bold">{{ 'common.labels.forked' | translate }}:</span>
<p>{{ duplicate.dateCreated | date: 'MMM d, y, h:mm a' }}</p>
</div>

<div class="flex flex-wrap align-items-center gap-1">
<span class="font-bold">{{ 'common.labels.lastUpdated' | translate }}:</span>
<p>{{ duplicate.dateModified | date: 'MMM d, y, h:mm a' }}</p>
</div>
<div class="component-name flex flex-column gap-2">
<div class="flex flex-wrap align-items-center gap-1">
<span class="font-bold">{{ 'common.labels.forked' | translate }}:</span>
<p>{{ duplicate.dateCreated | date: 'MMM d, y, h:mm a' }}</p>
</div>

<div class="flex flex-wrap align-items-center gap-1">
<span class="font-bold">{{ 'common.labels.contributors' | translate }}:</span>
@for (contributor of duplicate.contributors; track contributor.id) {
<div>
<a [routerLink]="['/user', contributor.id]" class="font-bold"> {{ contributor.fullName }}</a>
<span>{{ $last ? '' : ',' }}</span>
</div>
}
</div>
<div class="flex flex-wrap align-items-center gap-1">
<span class="font-bold">{{ 'common.labels.lastUpdated' | translate }}:</span>
<p>{{ duplicate.dateModified | date: 'MMM d, y, h:mm a' }}</p>
</div>

<div class="component-description">
<div class="flex flex-wrap align-items-baseline gap-1">
<span class="font-bold">{{ 'common.labels.description' | translate }}:</span>
<osf-truncated-text [text]="duplicate.description" />
<div class="flex flex-wrap align-items-center gap-1">
<span class="font-bold">{{ 'common.labels.contributors' | translate }}:</span>
@for (contributor of duplicate.contributors; track contributor.id) {
<div>
<a [routerLink]="['/user', contributor.id]" class="font-bold"> {{ contributor.fullName }}</a>
<span>{{ $last ? '' : ',' }}</span>
</div>
}
</div>

<div class="component-description">
<div class="flex flex-wrap align-items-baseline gap-1">
<span class="font-bold">{{ 'common.labels.description' | translate }}:</span>
<osf-truncated-text [text]="duplicate.description" />
</div>
</div>
<p-button [label]="'common.buttons.view' | translate" severity="secondary" [routerLink]="duplicate.id" />
</div>
}
<p-button
[label]="'common.buttons.view' | translate"
severity="secondary"
[routerLink]="['/', duplicate.id]"
/>
</div>
}

@if (totalDuplicates() > pageSize) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
@use "styles/variables" as var;
@use "styles/mixins" as mix;

:host {
display: flex;
flex-direction: column;
flex: 1;
}

.duplicate-wrapper {
border: 1px solid var.$grey-2;
border-radius: mix.rem(12px);
color: var.$dark-blue-1;
border: 1px solid var(--grey-2);
border-radius: 0.75rem;
color: var(--dark-blue-1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
import { ResourceType, UserPermissions } from '@osf/shared/enums';
import { IS_SMALL } from '@osf/shared/helpers';
import { ToolbarResource } from '@osf/shared/models';
import { Duplicate } from '@osf/shared/models/duplicates';
import { ClearDuplicates, DuplicatesSelectors, GetAllDuplicates } from '@osf/shared/stores';

@Component({
Expand Down Expand Up @@ -165,6 +166,13 @@ export class ViewDuplicatesComponent {
return null;
});

showMoreOptions(duplicate: Duplicate) {
return (
duplicate.currentUserPermissions.includes(UserPermissions.Admin) ||
duplicate.currentUserPermissions.includes(UserPermissions.Write)
);
}

handleForkResource(): void {
const toolbarResource = this.toolbarResource();
const dialogWidth = !this.isSmall() ? '95vw' : '450px';
Expand Down
4 changes: 2 additions & 2 deletions src/app/features/files/pages/files/files.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@

<p-button icon="fas fa-info-circle blue-icon" severity="secondary" text (onClick)="showInfoDialog()"></p-button>

@if (!isReadonly() && !hasViewOnly()) {
@if (canEdit() && !hasViewOnly()) {
<p-button
[disabled]="isButtonDisabled()"
outlined
Expand Down Expand Up @@ -129,7 +129,7 @@
[storage]="currentRootFolder()"
[isLoading]="isFilesLoading()"
[actions]="filesTreeActions"
[viewOnly]="isReadonly()"
[viewOnly]="!canEdit()"
[viewOnlyDownloadable]="isViewOnlyDownloadable()"
[resourceId]="resourceId()"
[provider]="provider()"
Expand Down
6 changes: 6 additions & 0 deletions src/app/features/files/pages/files/files.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import {
ViewOnlyLinkMessageComponent,
} from '@osf/shared/components';
import { GoogleFilePickerComponent } from '@osf/shared/components/addons/storage-item-selector/google-file-picker/google-file-picker.component';
import { testNode } from '@osf/shared/mocks';
import { OsfFile } from '@osf/shared/models';
import { CustomConfirmationService, FilesService } from '@osf/shared/services';
import { CurrentResourceSelectors } from '@osf/shared/stores';

import { FilesSelectors } from '../../store';

Expand Down Expand Up @@ -66,6 +68,10 @@ describe('Component: Files', () => {
DialogService,
provideMockStore({
signals: [
{
selector: CurrentResourceSelectors.getResourceDetails,
value: testNode,
},
{
selector: FilesSelectors.getRootFolders,
value: getNodeFilesMappedData(),
Expand Down
14 changes: 9 additions & 5 deletions src/app/features/files/pages/files/files.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,15 @@ export class FilesComponent {

readonly hasViewOnly = computed(() => hasViewOnlyParam(this.router));

readonly isReadonly = computed(
() =>
this.resourceDetails().isRegistration ||
this.resourceDetails().currentUserPermissions.includes(UserPermissions.Read)
);
readonly canEdit = computed(() => {
const details = this.resourceDetails();
const hasAdminOrWrite = details.currentUserPermissions.some(
(permission) => permission === UserPermissions.Admin || permission === UserPermissions.Write
);

return !details.isRegistration && hasAdminOrWrite;
});

readonly isViewOnlyDownloadable = computed(() => this.resourceType() === ResourceType.Registration);

isButtonDisabled = computed(() => this.fileIsUploading() || this.isFilesLoading());
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/files/store/files.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface FilesStateModel {
isAnonymous: boolean;
}

export const filesStateDefaults: FilesStateModel = {
export const FILES_STATE_DEFAULTS: FilesStateModel = {
files: {
data: [],
isLoading: false,
Expand Down
8 changes: 4 additions & 4 deletions src/app/features/files/store/files.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ import {
SetSort,
UpdateTags,
} from './files.actions';
import { filesStateDefaults, FilesStateModel } from './files.model';
import { FILES_STATE_DEFAULTS, FilesStateModel } from './files.model';

@Injectable()
@State<FilesStateModel>({
name: 'filesState',
defaults: filesStateDefaults,
name: 'files',
defaults: FILES_STATE_DEFAULTS,
})
export class FilesState {
filesService = inject(FilesService);
Expand Down Expand Up @@ -328,6 +328,6 @@ export class FilesState {

@Action(ResetState)
resetState(ctx: StateContext<FilesStateModel>) {
ctx.patchState(filesStateDefaults);
ctx.patchState(FILES_STATE_DEFAULTS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,52 +62,52 @@ <h3 class="font-bold">

<div class="flex flex-wrap align-items-center gap-3">
@if (currentUser()?.social?.github) {
<a class="cursor-pointer" [href]="currentUser()?.social?.github">
<a class="cursor-pointer custom-light-hover" [href]="currentUser()?.social?.github">
<img ngSrc="assets/icons/socials/github.svg" width="24" height="24" alt="github" />
</a>
}
@if (currentUser()?.social?.scholar) {
<a class="cursor-pointer" [href]="currentUser()?.social?.scholar">
<a class="cursor-pointer custom-light-hover" [href]="currentUser()?.social?.scholar">
<img ngSrc="assets/icons/socials/scholar.svg" width="24" height="24" alt="scholar" />
</a>
}
@if (currentUser()?.social?.twitter) {
<a class="cursor-pointer" [href]="currentUser()?.social?.twitter">
<a class="cursor-pointer custom-light-hover" [href]="currentUser()?.social?.twitter">
<img ngSrc="assets/icons/socials/x.svg" width="24" height="24" alt="x(twitter)" />
</a>
}
@if (currentUser()?.social?.linkedIn) {
<a class="cursor-pointer" [href]="currentUser()?.social?.linkedIn">
<a class="cursor-pointer custom-light-hover" [href]="currentUser()?.social?.linkedIn">
<img ngSrc="assets/icons/socials/linkedin.svg" width="24" height="24" alt="linkedin" />
</a>
}
@if (currentUser()?.social?.impactStory) {
<a class="cursor-pointer" [href]="currentUser()?.social?.impactStory">
<a class="cursor-pointer custom-light-hover" [href]="currentUser()?.social?.impactStory">
<img ngSrc="assets/icons/socials/impactstory.png" width="24" height="24" alt="impactstory" />
</a>
}
@if (currentUser()?.social?.baiduScholar) {
<a class="cursor-pointer" [href]="currentUser()?.social?.baiduScholar">
<a class="cursor-pointer custom-light-hover" [href]="currentUser()?.social?.baiduScholar">
<img ngSrc="assets/icons/socials/baidu.png" width="24" height="24" alt="baidu" />
</a>
}
@if (currentUser()?.social?.researchGate) {
<a class="cursor-pointer" [href]="currentUser()?.social?.researchGate">
<a class="cursor-pointer custom-light-hover" [href]="currentUser()?.social?.researchGate">
<img ngSrc="assets/icons/socials/researchGate.svg" width="24" height="24" alt="researchGate" />
</a>
}
@if (currentUser()?.social?.researcherId) {
<a class="cursor-pointer" [href]="currentUser()?.social?.researcherId">
<a class="cursor-pointer custom-light-hover" [href]="currentUser()?.social?.researcherId">
<img ngSrc="assets/icons/socials/researcherID.png" width="24" height="24" alt="researchId" />
</a>
}
@if (currentUser()?.social?.ssrn) {
<a class="cursor-pointer" [href]="currentUser()?.social?.researcherId">
<a class="cursor-pointer custom-light-hover" [href]="currentUser()?.social?.researcherId">
<img ngSrc="assets/icons/socials/ssrn.svg" width="24" height="24" alt="ssrn" />
</a>
}
@if (currentUser()?.social?.academiaProfileID) {
<a class="cursor-pointer" [href]="currentUser()?.social?.academiaProfileID">
<a class="cursor-pointer custom-light-hover" [href]="currentUser()?.social?.academiaProfileID">
<img ngSrc="assets/icons/socials/academia.svg" width="24" height="24" alt="academia" />
</a>
}
Expand All @@ -118,7 +118,7 @@ <h3 class="font-bold">
<p-button
class="ml-auto"
[label]="'myProfile.editProfile' | translate"
(click)="toProfileSettings()"
(onClick)="toProfileSettings()"
></p-button>
</div>
}
Expand Down
1 change: 1 addition & 0 deletions src/app/shared/mappers/search/search.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IndexCardDataJsonApi, ResourceModel } from '@shared/models';
export function MapResources(indexCardData: IndexCardDataJsonApi): ResourceModel {
const resourceMetadata = indexCardData.attributes.resourceMetadata;
const resourceIdentifier = indexCardData.attributes.resourceIdentifier;

return {
absoluteUrl: resourceMetadata['@id'],
resourceType: ResourceType[resourceMetadata.resourceType[0]['@id'] as keyof typeof ResourceType],
Expand Down
1 change: 1 addition & 0 deletions src/app/shared/mocks/addon.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export const MOCK_ADDON: AddonModel = {
supportedFeatures: ['ACCESS', 'UPDATE'],
credentialsFormat: CredentialsFormat.ACCESS_SECRET_KEYS,
providerName: 'Test Provider',
wbKey: 'github',
};
27 changes: 27 additions & 0 deletions src/app/shared/mocks/base-node.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { BaseNodeModel } from '../models';

export const testNode: BaseNodeModel = {
id: 'abc123',
title: 'Long-Term Effects of Climate Change',
description:
'This project collects and analyzes climate change data across multiple regions to understand long-term environmental impacts.',
category: 'project',
customCitation: 'Doe, J. (2024). Long-Term Effects of Climate Change. OSF.',
dateCreated: '2024-05-10T14:23:00Z',
dateModified: '2025-09-01T09:45:00Z',
isRegistration: false,
isPreprint: true,
isFork: false,
isCollection: false,
isPublic: true,
tags: ['climate', 'environment', 'data-analysis'],
accessRequestsEnabled: true,
nodeLicense: {
copyrightHolders: ['CC0 1.0 Universal'],
year: '2025',
},
currentUserPermissions: ['admin', 'read', 'write'],
currentUserIsContributor: true,
wikiEnabled: true,
rootParentId: 'nt29k',
};
1 change: 1 addition & 0 deletions src/app/shared/mocks/data.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const MOCK_USER: User = {
middleNames: '',
suffix: '',
dateRegistered: new Date('2024-01-01'),
acceptedTermsOfService: true,
employment: [
{
title: 'Software Engineer',
Expand Down
1 change: 1 addition & 0 deletions src/app/shared/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { MOCK_ADDON } from './addon.mock';
export * from './analytics.mock';
export * from './base-node.mock';
export { CEDAR_METADATA_DATA_TEMPLATE_JSON_API_MOCK } from './cedar-metadata-data-template-json-api.mock';
export * from './contributors.mock';
export * from './custom-сonfirmation.service.mock';
Expand Down
1 change: 0 additions & 1 deletion src/app/shared/mocks/project-overview.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ export const MOCK_PROJECT_OVERVIEW: ProjectOverview = {
currentUserIsContributor: true,
currentUserIsContributorOrGroupMember: true,
wikiEnabled: false,
subjects: [],
contributors: [],
customCitation: null,
forksCount: 0,
Expand Down
Loading
Loading