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
5 changes: 5 additions & 0 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ export const routes: Routes = [
loadComponent: () =>
import('./features/project/settings/settings.component').then((mod) => mod.SettingsComponent),
},
{
path: 'addons',
loadComponent: () =>
import('./features/project/addons/addons.component').then((mod) => mod.AddonsComponent),
},
],
},
{
Expand Down
4 changes: 4 additions & 0 deletions src/app/core/constants/nav-items.constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ export const PROJECT_MENU_ITEMS: MenuItem[] = [
label: 'navigation.project.settings',
routerLink: 'settings',
},
{
label: 'navigation.project.addons',
routerLink: 'addons',
},
],
},
];
78 changes: 78 additions & 0 deletions src/app/features/project/addons/addons.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<osf-sub-header [showButton]="false" [title]="'settings.addons.header.title' | translate" [icon]="'settings'" />
<section class="flex-column flex flex-1">
<p-tabs [value]="selectedTab()" class="hidden md:flex md:flex-1">
<p-tablist class="pr-5 pl-5">
<p-tab [value]="AddonTabValue.ALL_ADDONS">
{{ 'settings.addons.tabs.allAddons' | translate }}
</p-tab>
<p-tab [value]="AddonTabValue.CONNECTED_ADDONS">
{{ 'settings.addons.tabs.connectedAddons' | translate }}
</p-tab>
</p-tablist>

<p-tabpanels class="p-3 sm:p-5 flex-1">
<p-select
class="w-full mb-4 md:hidden"
[options]="tabOptions"
optionValue="value"
[ngModel]="selectedTab()"
(ngModelChange)="selectedTab.set($event)"
>
<ng-template #selectedItem let-selectedOption>
{{ selectedOption.label | translate }}
</ng-template>
<ng-template #item let-item>
{{ item.label | translate }}
</ng-template>
</p-select>
<p-tabpanel [value]="AddonTabValue.ALL_ADDONS" class="flex flex-column gap-5">
<p>
{{ 'settings.addons.description' | translate }}
</p>
<section class="grid">
<div class="col-12 md:col-6 lg:col-3">
<p-select
[placeholder]="'settings.addons.filters.addonType' | translate"
[options]="categoryOptions"
optionValue="value"
[ngModel]="selectedCategory()"
(ngModelChange)="onCategoryChange($event)"
class="w-full"
>
<ng-template #selectedItem let-selectedOption>
{{ selectedOption.label | translate }}
</ng-template>
<ng-template #item let-item>
{{ item.label | translate }}
</ng-template>
</p-select>
</div>
<div class="col-12 md:col-6 lg:col-9">
<osf-search-input
class="w-full"
[searchValue]="searchValue()"
(searchValueChange)="searchValue.set($event || '')"
[placeholder]="'settings.addons.filters.search' | translate"
/>
</div>
</section>

<osf-addon-card-list [cards]="filteredAddonCards()" [showDangerButton]="false" />
</p-tabpanel>
<p-tabpanel [value]="AddonTabValue.CONNECTED_ADDONS" class="flex flex-column gap-5">
<osf-search-input
class="flex-1"
[searchValue]="searchValue()"
(searchValueChange)="searchValue.set($event || '')"
[placeholder]="'settings.addons.filters.search' | translate"
/>

<osf-addon-card-list
[cards]="allAuthorizedAddons()"
[cardButtonLabel]="'settings.addons.form.buttons.reconnect' | translate"
[showDangerButton]="true"
/>
</p-tabpanel>
</p-tabpanels>
</p-tabs>
</section>
7 changes: 7 additions & 0 deletions src/app/features/project/addons/addons.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@use "assets/styles/mixins" as mix;
@use "assets/styles/variables" as var;

:host {
@include mix.flex-column;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Use prime flex instead of this mix

flex: 1;
}
22 changes: 22 additions & 0 deletions src/app/features/project/addons/addons.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { AddonsComponent } from './addons.component';

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

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

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

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

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

import { Select } from 'primeng/select';
import { Tab, TabList, TabPanel, TabPanels, Tabs } from 'primeng/tabs';

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

import { UserSelectors } from '@core/store/user';
import {
AddonsSelectors,
GetAddonsUserReference,
GetAuthorizedCitationAddons,
GetAuthorizedStorageAddons,
GetCitationAddons,
GetStorageAddons,
} from '@osf/features/settings/addons/store';
import { SearchInputComponent, SubHeaderComponent } from '@shared/components';
import { AddonCardListComponent } from '@shared/components/addons';
import { IS_XSMALL } from '@shared/utils';

import { ADDON_CATEGORY_OPTIONS, ADDON_TAB_OPTIONS, AddonCategoryValue, AddonTabValue } from './addons.constants';

@Component({
selector: 'osf-addons',
imports: [
AddonCardListComponent,
SearchInputComponent,
Select,
SubHeaderComponent,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
TranslatePipe,
FormsModule,
],
templateUrl: './addons.component.html',
styleUrl: './addons.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddonsComponent {
protected readonly AddonTabValue = AddonTabValue;
#store = inject(Store);
protected readonly defaultTabValue = AddonTabValue.ALL_ADDONS;
protected readonly isMobile = toSignal(inject(IS_XSMALL));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove this and make it with primeflex.

protected readonly searchValue = signal('');
protected readonly selectedCategory = signal<string>(AddonCategoryValue.EXTERNAL_STORAGE_SERVICES);
protected readonly selectedTab = signal<number>(this.defaultTabValue);
protected readonly currentUser = select(UserSelectors.getCurrentUser);
protected readonly addonsUserReference = select(AddonsSelectors.getAddonUserReference);
protected readonly storageAddons = select(AddonsSelectors.getStorageAddons);
protected readonly citationAddons = select(AddonsSelectors.getCitationAddons);
protected readonly authorizedStorageAddons = select(AddonsSelectors.getAuthorizedStorageAddons);
protected readonly authorizedCitationAddons = this.#store.selectSignal(AddonsSelectors.getAuthorizedCitationAddons);
protected readonly allAuthorizedAddons = computed(() => {
const authorizedAddons = [...this.authorizedStorageAddons(), ...this.authorizedCitationAddons()];

const searchValue = this.searchValue().toLowerCase();
return authorizedAddons.filter((card) => card.displayName.includes(searchValue));
});

protected readonly userReferenceId = computed(() => {
return this.addonsUserReference()[0]?.id;
});

protected readonly currentAction = computed(() =>
this.selectedCategory() === AddonCategoryValue.EXTERNAL_STORAGE_SERVICES ? GetStorageAddons : GetCitationAddons
);

protected readonly currentAddonsState = computed(() =>
this.selectedCategory() === AddonCategoryValue.EXTERNAL_STORAGE_SERVICES
? this.storageAddons()
: this.citationAddons()
);

protected readonly filteredAddonCards = computed(() => {
const searchValue = this.searchValue().toLowerCase();
return this.currentAddonsState().filter((card) => card.externalServiceName.includes(searchValue));
});

protected readonly tabOptions = ADDON_TAB_OPTIONS;
protected readonly categoryOptions = ADDON_CATEGORY_OPTIONS;

protected onCategoryChange(value: string): void {
this.selectedCategory.set(value);
}

constructor() {
effect(() => {
// Only proceed if we have the current user
if (this.currentUser()) {
this.#store.dispatch(GetAddonsUserReference);
}
});

effect(() => {
// Only proceed if we have both current user and user reference
if (this.currentUser() && this.userReferenceId()) {
this.#loadAddonsIfNeeded(this.userReferenceId());
}
});
}

#loadAddonsIfNeeded(userReferenceId: string): void {
const action = this.currentAction();
const addons = this.currentAddonsState();

if (!addons?.length) {
this.#store.dispatch(action);
this.#store.dispatch(new GetAuthorizedStorageAddons(userReferenceId));
this.#store.dispatch(new GetAuthorizedCitationAddons(userReferenceId));
}
}
}
33 changes: 33 additions & 0 deletions src/app/features/project/addons/addons.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { SelectOption } from '@shared/models';

export enum AddonTabValue {
ALL_ADDONS = 0,
CONNECTED_ADDONS = 1,
}

export enum AddonCategoryValue {
EXTERNAL_STORAGE_SERVICES = 'external-storage-services',
EXTERNAL_CITATION_SERVICES = 'external-citation-services',
}

export const ADDON_TAB_OPTIONS: SelectOption[] = [
{
label: 'settings.addons.tabs.allAddons',
value: AddonTabValue.ALL_ADDONS,
},
{
label: 'settings.addons.tabs.connectedAddons',
value: AddonTabValue.CONNECTED_ADDONS,
},
];

export const ADDON_CATEGORY_OPTIONS: SelectOption[] = [
{
label: 'settings.addons.categories.additionalService',
value: AddonCategoryValue.EXTERNAL_STORAGE_SERVICES,
},
{
label: 'settings.addons.categories.citationManager',
value: AddonCategoryValue.EXTERNAL_CITATION_SERVICES,
},
];
2 changes: 1 addition & 1 deletion src/app/features/settings/addons/addons.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UserSelectors } from '@osf/core/store/user';
import { SearchInputComponent, SubHeaderComponent } from '@osf/shared/components';
import { IS_XSMALL } from '@osf/shared/utils';
import { AddonCardListComponent } from '@shared/components/addons';

import { AddonsComponent } from './addons.component';
import { AddonCardListComponent } from './components';
import { AddonsSelectors } from './store';

describe('AddonsComponent', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/features/settings/addons/addons.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { UserSelectors } from '@osf/core/store/user';
import { SearchInputComponent, SubHeaderComponent } from '@osf/shared/components';
import { SelectOption } from '@osf/shared/models';
import { IS_XSMALL } from '@osf/shared/utils';
import { AddonCardListComponent } from '@shared/components/addons';

import { AddonCardListComponent } from './components';
import {
AddonsSelectors,
GetAddonsUserReference,
Expand Down
2 changes: 0 additions & 2 deletions src/app/features/settings/addons/components/index.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { MockComponent } from 'ng-mocks';

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { AddonCardComponent } from '../addon-card/addon-card.component';
import { AddonCardComponent } from '@shared/components/addons/addon-card/addon-card.component';

import { AddonCardListComponent } from './addon-card-list.component';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, input } from '@angular/core';

import { Addon, AuthorizedAddon } from '../../models';
import { AddonCardComponent } from '../addon-card/addon-card.component';
import { Addon, AuthorizedAddon } from '@osf/features/settings/addons/models';
import { AddonCardComponent } from '@shared/components/addons';

@Component({
selector: 'osf-addon-card-list',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { MockPipe, MockProvider } from 'ng-mocks';

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CredentialsFormat } from '../../enums';
import { Addon } from '../../models';
import { CredentialsFormat } from '@osf/features/settings/addons/enums';
import { Addon } from '@osf/features/settings/addons/models';

import { AddonCardComponent } from './addon-card.component';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import { Component, computed, inject, input, signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { Router } from '@angular/router';

import { IS_XSMALL } from '@osf/shared/utils';

import { Addon, AuthorizedAddon } from '../../models';
import { DeleteAuthorizedAddon } from '../../store';
import { Addon, AuthorizedAddon } from '@osf/features/settings/addons/models';
import { DeleteAuthorizedAddon } from '@osf/features/settings/addons/store';
import { IS_XSMALL } from '@shared/utils';

@Component({
selector: 'osf-addon-card',
Expand Down
2 changes: 2 additions & 0 deletions src/app/shared/components/addons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { AddonCardComponent } from '@shared/components/addons/addon-card/addon-card.component';
export { AddonCardListComponent } from '@shared/components/addons/addon-card-list/addon-card-list.component';
3 changes: 2 additions & 1 deletion src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"registrations": "Registrations",
"settings": "Settings",
"contributors": "Contributors",
"analytics": "Analytics"
"analytics": "Analytics",
"addons": "Add-ons"
}
},
"auth": {
Expand Down