From df67da11519ab31518890d2e49a460df3780e1c9 Mon Sep 17 00:00:00 2001 From: Roman Nastyuk Date: Thu, 20 Mar 2025 13:26:40 +0200 Subject: [PATCH 1/3] feat(configure-addons-settings): added settings to the nav menu, added addons settings layout --- .../nav-menu/nav-menu.component.html | 39 +++-- .../nav-menu/nav-menu.component.scss | 25 +-- .../components/nav-menu/nav-menu.component.ts | 28 ++- .../components/sidenav/sidenav.component.html | 6 +- .../components/sidenav/sidenav.component.scss | 2 +- src/app/core/helpers/nav-items.constant.ts | 45 ++++- .../auth/sign-up/sign-up.component.scss | 4 +- src/app/features/home/home.component.html | 14 +- src/app/features/home/home.component.ts | 6 +- .../addon-card-list.component.html | 9 + .../addon-card-list.component.scss | 0 .../addon-card-list.component.spec.ts | 22 +++ .../addon-card-list.component.ts | 13 ++ .../addon-card/addon-card.component.html | 17 ++ .../addon-card/addon-card.component.scss | 17 ++ .../addon-card/addon-card.component.spec.ts | 22 +++ .../addons/addon-card/addon-card.component.ts | 20 +++ .../settings/addons/addons.component.html | 47 +++++ .../settings/addons/addons.component.scss | 11 ++ .../settings/addons/addons.component.spec.ts | 22 +++ .../settings/addons/addons.component.ts | 80 +++++++++ .../connect-addon.component.html | 164 ++++++++++++++++++ .../connect-addon.component.scss | 49 ++++++ .../connect-addon.component.spec.ts | 22 +++ .../connect-addon/connect-addon.component.ts | 96 ++++++++++ .../profile-settings.component.ts | 3 +- .../settings-container.component.scss | 3 + src/app/features/settings/settings.routes.ts | 19 ++ .../password-input-hint.component.ts | 3 +- .../search-input/search-input.component.html | 11 ++ .../search-input/search-input.component.scss | 18 ++ .../search-input.component.spec.ts | 22 +++ .../search-input/search-input.component.ts | 25 +++ .../sub-header/sub-header.component.html | 4 +- .../sub-header/sub-header.component.ts | 10 +- .../shared/entities/addon-card.interface.ts | 4 + src/app/shared/entities/nav-item.interface.ts | 3 + src/assets/images/temp/bitbucket.png | Bin 0 -> 2538 bytes src/assets/images/temp/dropbox.png | Bin 0 -> 3276 bytes src/assets/images/temp/figshare.png | Bin 0 -> 10408 bytes src/assets/images/temp/github.png | Bin 0 -> 2578 bytes src/assets/images/temp/onedrive.png | Bin 0 -> 2281 bytes src/assets/images/temp/owncloud.png | Bin 0 -> 4950 bytes src/assets/images/temp/s3.png | Bin 0 -> 14504 bytes src/assets/styles/overrides/autocomplete.scss | 31 ++++ src/assets/styles/overrides/button.scss | 7 + src/assets/styles/overrides/card.scss | 6 + src/assets/styles/overrides/drawer.scss | 3 +- src/assets/styles/overrides/input.scss | 8 +- src/assets/styles/overrides/panel-menu.scss | 61 +++++++ src/assets/styles/overrides/radio.scss | 14 ++ src/assets/styles/overrides/stepper.scss | 12 ++ src/assets/styles/overrides/table.scss | 14 ++ src/assets/styles/overrides/tabs.scss | 22 +++ src/assets/styles/styles.scss | 5 + 55 files changed, 1022 insertions(+), 66 deletions(-) create mode 100644 src/app/features/settings/addons/addon-card-list/addon-card-list.component.html create mode 100644 src/app/features/settings/addons/addon-card-list/addon-card-list.component.scss create mode 100644 src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts create mode 100644 src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts create mode 100644 src/app/features/settings/addons/addon-card/addon-card.component.html create mode 100644 src/app/features/settings/addons/addon-card/addon-card.component.scss create mode 100644 src/app/features/settings/addons/addon-card/addon-card.component.spec.ts create mode 100644 src/app/features/settings/addons/addon-card/addon-card.component.ts create mode 100644 src/app/features/settings/addons/addons.component.html create mode 100644 src/app/features/settings/addons/addons.component.scss create mode 100644 src/app/features/settings/addons/addons.component.spec.ts create mode 100644 src/app/features/settings/addons/addons.component.ts create mode 100644 src/app/features/settings/addons/connect-addon/connect-addon.component.html create mode 100644 src/app/features/settings/addons/connect-addon/connect-addon.component.scss create mode 100644 src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts create mode 100644 src/app/features/settings/addons/connect-addon/connect-addon.component.ts create mode 100644 src/app/shared/components/search-input/search-input.component.html create mode 100644 src/app/shared/components/search-input/search-input.component.scss create mode 100644 src/app/shared/components/search-input/search-input.component.spec.ts create mode 100644 src/app/shared/components/search-input/search-input.component.ts create mode 100644 src/app/shared/entities/addon-card.interface.ts create mode 100644 src/assets/images/temp/bitbucket.png create mode 100644 src/assets/images/temp/dropbox.png create mode 100644 src/assets/images/temp/figshare.png create mode 100644 src/assets/images/temp/github.png create mode 100644 src/assets/images/temp/onedrive.png create mode 100644 src/assets/images/temp/owncloud.png create mode 100644 src/assets/images/temp/s3.png create mode 100644 src/assets/styles/overrides/autocomplete.scss create mode 100644 src/assets/styles/overrides/panel-menu.scss create mode 100644 src/assets/styles/overrides/radio.scss create mode 100644 src/assets/styles/overrides/stepper.scss create mode 100644 src/assets/styles/overrides/tabs.scss diff --git a/src/app/core/components/nav-menu/nav-menu.component.html b/src/app/core/components/nav-menu/nav-menu.component.html index 1a795209e..629df1400 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.html +++ b/src/app/core/components/nav-menu/nav-menu.component.html @@ -1,19 +1,24 @@ diff --git a/src/app/core/components/nav-menu/nav-menu.component.scss b/src/app/core/components/nav-menu/nav-menu.component.scss index 470ab0834..69ad3b05b 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.scss +++ b/src/app/core/components/nav-menu/nav-menu.component.scss @@ -1,27 +1,14 @@ @use "assets/styles/variables" as var; + :host { .nav-menu { width: 100%; - max-width: 15rem; - - ul { - .nav-link { - display: flex; - justify-content: flex-start; - align-items: center; - padding-left: 1rem; - width: 100%; - height: 3.4rem; - gap: 0.5rem; - color: var.$white; - text-decoration: none; - } + max-width: 15.5rem; - .active { - font-weight: 700; - background-color: var.$dark-blue-2; - border-radius: 0.5rem; - } + .active { + font-weight: 700; + background-color: var.$dark-blue-2; + border-radius: 0.5rem; } } } diff --git a/src/app/core/components/nav-menu/nav-menu.component.ts b/src/app/core/components/nav-menu/nav-menu.component.ts index b711ad7bf..dc9f2a8ed 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.ts +++ b/src/app/core/components/nav-menu/nav-menu.component.ts @@ -1,13 +1,39 @@ import { Component } from '@angular/core'; import { RouterLink, RouterLinkActive } from '@angular/router'; import { NAV_ITEMS } from '@osf/core/helpers/nav-items.constant'; +import { PanelMenuModule } from 'primeng/panelmenu'; +import { MenuItem } from 'primeng/api'; @Component({ selector: 'osf-nav-menu', - imports: [RouterLinkActive, RouterLink], + imports: [RouterLinkActive, RouterLink, PanelMenuModule], templateUrl: './nav-menu.component.html', styleUrl: './nav-menu.component.scss', }) export class NavMenuComponent { navItems = NAV_ITEMS; + + protected menuItems: MenuItem[] = this.navItems.map((item) => + this.convertToMenuItem(item), + ); + + private convertToMenuItem(item: (typeof NAV_ITEMS)[0]): MenuItem { + const menuItem: MenuItem = { + label: item.label, + icon: item.icon ? `osf-icon-${item.icon}` : '', + expanded: false, + }; + + if (!item.isCollapsible) { + menuItem.routerLink = item.path; + } + + if (item.items) { + menuItem.items = item.items.map((subItem) => + this.convertToMenuItem(subItem), + ); + } + + return menuItem; + } } diff --git a/src/app/core/components/sidenav/sidenav.component.html b/src/app/core/components/sidenav/sidenav.component.html index 08714d186..de8c00f50 100644 --- a/src/app/core/components/sidenav/sidenav.component.html +++ b/src/app/core/components/sidenav/sidenav.component.html @@ -1,2 +1,4 @@ -OSF Logo - + diff --git a/src/app/core/components/sidenav/sidenav.component.scss b/src/app/core/components/sidenav/sidenav.component.scss index f39a1f973..fc2d00567 100644 --- a/src/app/core/components/sidenav/sidenav.component.scss +++ b/src/app/core/components/sidenav/sidenav.component.scss @@ -1,6 +1,6 @@ @use "assets/styles/mixins" as mix; -:host { +aside { @include mix.flex-column; gap: 1.8rem; width: 19rem; diff --git a/src/app/core/helpers/nav-items.constant.ts b/src/app/core/helpers/nav-items.constant.ts index f02730bbb..fab14df27 100644 --- a/src/app/core/helpers/nav-items.constant.ts +++ b/src/app/core/helpers/nav-items.constant.ts @@ -1,16 +1,57 @@ import { NavItem } from '@osf/shared/entities/nav-item.interface'; export const NAV_ITEMS: NavItem[] = [ - { path: '/home', label: 'Home', icon: 'home' }, - { path: '', label: 'Search OSF', icon: 'search' }, + { path: '/home', label: 'Home', icon: 'home', useExactMatch: true }, + { path: '/search', label: 'Search OSF', icon: 'search', useExactMatch: true }, { path: '/support', label: 'Support', icon: 'support', + useExactMatch: true, + }, + { + path: '/settings', + label: 'Settings', + icon: 'settings', + isCollapsible: true, + useExactMatch: true, + items: [ + { + path: '/settings/profile-settings', + label: 'Profile Settings', + useExactMatch: true, + }, + { + path: '/settings/account-settings', + label: 'Account Settings', + useExactMatch: true, + }, + { + path: '/settings/addons', + label: 'Addon Accounts', + useExactMatch: false, + }, + { + path: '/settings/notifications', + label: 'Notifications', + useExactMatch: true, + }, + { + path: '/settings/developer-apps', + label: 'Developer Apps', + useExactMatch: true, + }, + { + path: '/settings/personal-access-tokens', + label: 'Personal Access Tokens', + useExactMatch: true, + }, + ], }, { path: '/donate', label: 'Donate', icon: 'donate', + useExactMatch: true, }, ]; diff --git a/src/app/features/auth/sign-up/sign-up.component.scss b/src/app/features/auth/sign-up/sign-up.component.scss index 9fb027757..ba443f861 100644 --- a/src/app/features/auth/sign-up/sign-up.component.scss +++ b/src/app/features/auth/sign-up/sign-up.component.scss @@ -12,7 +12,7 @@ @include mix.flex-column; color: var.$dark-blue-1; width: 32rem; - margin: 4.5rem 0 2rem 0; + margin: 4.5rem 0 1.5rem 0; padding: 2rem; background: white; border-radius: 0.6rem; @@ -89,7 +89,7 @@ } .message-container { - margin-top: auto; + margin: auto; } .mobile { diff --git a/src/app/features/home/home.component.html b/src/app/features/home/home.component.html index fc2219772..b775ce7ca 100644 --- a/src/app/features/home/home.component.html +++ b/src/app/features/home/home.component.html @@ -27,15 +27,11 @@ search OSF

- + + @if (cards().length) { + @for (card of cards(); track card.title) { +
+ +
+ } + } + diff --git a/src/app/features/settings/addons/addon-card-list/addon-card-list.component.scss b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts new file mode 100644 index 000000000..6ce44934b --- /dev/null +++ b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddonCardListComponent } from './addon-card-list.component'; + +describe('AddonCardListComponent', () => { + let component: AddonCardListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AddonCardListComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AddonCardListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts new file mode 100644 index 000000000..1417b38af --- /dev/null +++ b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts @@ -0,0 +1,13 @@ +import { Component, input } from '@angular/core'; +import { AddonCardComponent } from '@osf/features/settings/addons/addon-card/addon-card.component'; +import { AddonCard } from '@shared/entities/addon-card.interface'; + +@Component({ + selector: 'osf-addon-card-list', + imports: [AddonCardComponent], + templateUrl: './addon-card-list.component.html', + styleUrl: './addon-card-list.component.scss', +}) +export class AddonCardListComponent { + cards = input([]); +} diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.html b/src/app/features/settings/addons/addon-card/addon-card.component.html new file mode 100644 index 000000000..dd08a2531 --- /dev/null +++ b/src/app/features/settings/addons/addon-card/addon-card.component.html @@ -0,0 +1,17 @@ +
+
+ Addon card image +
+ +
+

{{ card()?.title }}

+ +
+
diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.scss b/src/app/features/settings/addons/addon-card/addon-card.component.scss new file mode 100644 index 000000000..7e1d195bd --- /dev/null +++ b/src/app/features/settings/addons/addon-card/addon-card.component.scss @@ -0,0 +1,17 @@ +@use "assets/styles/variables" as var; + +:host { + display: block; + + img { + max-height: 8.6rem; + max-width: 100%; + } + + .addon-card { + height: 18.6rem; + text-align: center; + border: 1px solid var.$grey-2; + border-radius: 1rem; + } +} diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.spec.ts b/src/app/features/settings/addons/addon-card/addon-card.component.spec.ts new file mode 100644 index 000000000..2ca38d8f6 --- /dev/null +++ b/src/app/features/settings/addons/addon-card/addon-card.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddonCardComponent } from './addon-card.component'; + +describe('AddonCardComponent', () => { + let component: AddonCardComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AddonCardComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AddonCardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.ts b/src/app/features/settings/addons/addon-card/addon-card.component.ts new file mode 100644 index 000000000..c8db8b763 --- /dev/null +++ b/src/app/features/settings/addons/addon-card/addon-card.component.ts @@ -0,0 +1,20 @@ +import { Component, input } from '@angular/core'; +import { Button } from 'primeng/button'; +import { AddonCard } from '@shared/entities/addon-card.interface'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'osf-addon-card', + imports: [Button], + templateUrl: './addon-card.component.html', + styleUrl: './addon-card.component.scss', +}) +export class AddonCardComponent { + card = input(); + + constructor(private router: Router) {} + + onConnect(): void { + this.router.navigate(['/settings/addons/connect-addon']); + } +} diff --git a/src/app/features/settings/addons/addons.component.html b/src/app/features/settings/addons/addons.component.html new file mode 100644 index 000000000..abe04359f --- /dev/null +++ b/src/app/features/settings/addons/addons.component.html @@ -0,0 +1,47 @@ + +
+ + + All Add-ons + Connected Add-ons + + + + +

+ Sync your projects with external services to help stay connected and + organized. Select a category and browse the options. +

+
+
+ +
+
+ +
+
+ + +
+ + + + + +
+
+
diff --git a/src/app/features/settings/addons/addons.component.scss b/src/app/features/settings/addons/addons.component.scss new file mode 100644 index 000000000..ef94601f7 --- /dev/null +++ b/src/app/features/settings/addons/addons.component.scss @@ -0,0 +1,11 @@ +@use "assets/styles/mixins" as mix; +@use "assets/styles/variables" as var; + +:host { + @include mix.flex-column; + flex: 1; + + .tabs-container { + @include mix.flex-column; + } +} diff --git a/src/app/features/settings/addons/addons.component.spec.ts b/src/app/features/settings/addons/addons.component.spec.ts new file mode 100644 index 000000000..4d1a6de4c --- /dev/null +++ b/src/app/features/settings/addons/addons.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddonsComponent } from './addons.component'; + +describe('AddonsComponent', () => { + let component: AddonsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AddonsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AddonsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/settings/addons/addons.component.ts b/src/app/features/settings/addons/addons.component.ts new file mode 100644 index 000000000..bef6bb02f --- /dev/null +++ b/src/app/features/settings/addons/addons.component.ts @@ -0,0 +1,80 @@ +import { + ChangeDetectionStrategy, + Component, + computed, + signal, + OnInit, + untracked, +} from '@angular/core'; +import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; +import { Tab, TabList, TabPanel, TabPanels, Tabs } from 'primeng/tabs'; +import { SearchInputComponent } from '@shared/components/search-input/search-input.component'; +import { AutoCompleteModule } from 'primeng/autocomplete'; +import { AddonCardListComponent } from '@osf/features/settings/addons/addon-card-list/addon-card-list.component'; +import { AddonCard } from '@shared/entities/addon-card.interface'; + +@Component({ + selector: 'osf-addons', + standalone: true, + imports: [ + SubHeaderComponent, + TabList, + Tabs, + Tab, + TabPanel, + TabPanels, + SearchInputComponent, + AutoCompleteModule, + AddonCardListComponent, + ], + templateUrl: './addons.component.html', + styleUrl: './addons.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AddonsComponent implements OnInit { + defaultTabValue = 0; + searchValue = signal(''); + cards = signal([]); + filteredCards = computed((): AddonCard[] => { + const searchValue = this.searchValue(); + + return untracked(() => + this.cards().filter((card) => + card.title.toLowerCase().includes(searchValue.toLowerCase()), + ), + ); + }); + + ngOnInit(): void { + this.cards.set([ + { + title: 'Bitbucket', + img: 'bitbucket', + }, + { + title: 'Github', + img: 'github', + }, + { + title: 'Dropbox', + img: 'dropbox', + }, + { + title: 'Figshare', + img: 'figshare', + }, + { + title: 'OneDrive', + img: 'onedrive', + }, + { + title: 'S3', + img: 's3', + }, + { + title: 'OwnCloud', + img: 'owncloud', + }, + ]); + } +} diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.html b/src/app/features/settings/addons/connect-addon/connect-addon.component.html new file mode 100644 index 000000000..bb1cd4834 --- /dev/null +++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.html @@ -0,0 +1,164 @@ + +
+ + + + +
+

Figshare Terms

+ + + + + Function + Status + + + + + {{ term.function }} + {{ term.status }} + + + + +
+

+ • This add-on connects your OSF project to an external service. + Use of this service is bound by its terms and conditions. The + OSF is not responsible for the service or for your use thereof. +

+

+ • This add-on allows you to store files using an external + service. Files added to this add-on are not stored within the + OSF. +

+
+ +
+ + +
+
+
+
+ + + +
+

Setup new account

+ +

Account Name

+

+ This will distinguish your account from other using the same + addon. +

+ Google Drive +
+ +
+ + +
+
+
+
+ + + +
+

Configure Google Drive

+
    +
  • + + +
  • +
  • + + +
  • +
+
+ + +
+
+
+
+ + + +
+

Configure Google Drive

+ +
+ @for (folder of folders; track folder.name) { +
+ + + + {{ folder.name }} +
+ } +
+ +
+ + +
+
+
+
+
+
+
diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.scss b/src/app/features/settings/addons/connect-addon/connect-addon.component.scss new file mode 100644 index 000000000..1ace2640a --- /dev/null +++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.scss @@ -0,0 +1,49 @@ +@use "assets/styles/mixins" as mix; +@use "assets/styles/variables" as var; + +:host { + flex: 1; + @include mix.flex-column; + + .stepper-container { + background-color: white; + flex: 1; + + button { + width: 100%; + } + } +} + +.folders-list { + max-height: 400px; + overflow-y: auto; + padding: 1rem; + border: 1px solid #dee2e6; + border-radius: 6px; + background-color: #f8f9fa; +} + +.folder-item { + padding: 0.5rem; + border-radius: 4px; + + &:hover { + background-color: #e9ecef; + } + + i { + color: #6c757d; + } +} + +:host ::ng-deep { + .p-checkbox { + .p-checkbox-box { + &.p-highlight { + border-color: var(--primary-color); + background: var(--primary-color); + } + } + } +} diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts b/src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts new file mode 100644 index 000000000..90ae04365 --- /dev/null +++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConnectAddonComponent } from './connect-addon.component'; + +describe('ConnectAddonComponent', () => { + let component: ConnectAddonComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ConnectAddonComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ConnectAddonComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.ts b/src/app/features/settings/addons/connect-addon/connect-addon.component.ts new file mode 100644 index 000000000..e29dd6116 --- /dev/null +++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.ts @@ -0,0 +1,96 @@ +import { Component } from '@angular/core'; +import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; +import { StepPanel, StepPanels, Stepper } from 'primeng/stepper'; +import { Button } from 'primeng/button'; +import { TableModule } from 'primeng/table'; +import { RouterLink } from '@angular/router'; +import { NgClass } from '@angular/common'; +import { Card } from 'primeng/card'; +import { RadioButton } from 'primeng/radiobutton'; +import { FormsModule } from '@angular/forms'; +import { Checkbox } from 'primeng/checkbox'; + +interface Term { + function: string; + status: string; + type: 'warning' | 'info'; +} + +interface Folder { + name: string; + selected: boolean; +} + +@Component({ + selector: 'osf-connect-addon', + imports: [ + SubHeaderComponent, + StepPanel, + StepPanels, + Stepper, + Button, + TableModule, + RouterLink, + NgClass, + Card, + FormsModule, + RadioButton, + Checkbox, + ], + templateUrl: './connect-addon.component.html', + styleUrl: './connect-addon.component.scss', + standalone: true, +}) +export class ConnectAddonComponent { + radioConfig = ''; + folders: Folder[] = [ + { name: 'folder name example', selected: false }, + { name: 'folder name example', selected: false }, + { name: 'folder name example', selected: false }, + { name: 'folder name example', selected: false }, + { name: 'folder name example', selected: false }, + { name: 'folder name example', selected: false }, + ]; + terms: Term[] = [ + { + function: 'Add / Update Files', + status: 'You cannot add or update files for figshare within OSF.', + type: 'warning', + }, + { + function: 'Delete files', + status: 'You cannot delete files for figshare within OSF.', + type: 'warning', + }, + { + function: 'Forking', + status: + 'Only the user who first authorized the figshare add-on within source project can transfer its authorization to a forked project or component.', + type: 'info', + }, + { + function: 'Logs', + status: + 'OSF tracks changes you make to your figshare content within OSF, but not changes made directly within figshare.', + type: 'info', + }, + { + function: 'Permissions', + status: + 'The OSF does not change permissions for linked figshare files. Privacy changes made to an OSF project or component will not affect those set in figshare.', + type: 'info', + }, + { + function: 'Registering', + status: + 'figshare content will be registered, but version history will not be copied to the registration.', + type: 'info', + }, + { + function: 'View/Download File Versions', + status: + 'figshare files can be viewed/downloaded in OSF, but version history is not supported.', + type: 'warning', + }, + ]; +} diff --git a/src/app/features/settings/profile-settings/profile-settings.component.ts b/src/app/features/settings/profile-settings/profile-settings.component.ts index ed4fdece4..3b11dfa1e 100644 --- a/src/app/features/settings/profile-settings/profile-settings.component.ts +++ b/src/app/features/settings/profile-settings/profile-settings.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; @Component({ @@ -6,5 +6,6 @@ import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.com imports: [SubHeaderComponent], templateUrl: './profile-settings.component.html', styleUrl: './profile-settings.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class ProfileSettingsComponent {} diff --git a/src/app/features/settings/settings-container.component.scss b/src/app/features/settings/settings-container.component.scss index 28d91b04b..ecfe63d1c 100644 --- a/src/app/features/settings/settings-container.component.scss +++ b/src/app/features/settings/settings-container.component.scss @@ -1,3 +1,6 @@ :host { margin-top: 4.5rem; + display: flex; + flex-direction: column; + flex: 1; } diff --git a/src/app/features/settings/settings.routes.ts b/src/app/features/settings/settings.routes.ts index 6922327a2..713b9a278 100644 --- a/src/app/features/settings/settings.routes.ts +++ b/src/app/features/settings/settings.routes.ts @@ -27,6 +27,25 @@ export const settingsRoutes: Routes = [ (mod) => mod.DeveloperAppsComponent, ), }, + { + path: 'addons', + children: [ + { + path: '', + loadComponent: () => + import('./addons/addons.component').then( + (mod) => mod.AddonsComponent, + ), + }, + { + path: 'connect-addon', + loadComponent: () => + import('./addons/connect-addon/connect-addon.component').then( + (mod) => mod.ConnectAddonComponent, + ), + }, + ], + }, ], }, ]; diff --git a/src/app/shared/components/password-input-hint/password-input-hint.component.ts b/src/app/shared/components/password-input-hint/password-input-hint.component.ts index 3d4310988..f35ea5d16 100644 --- a/src/app/shared/components/password-input-hint/password-input-hint.component.ts +++ b/src/app/shared/components/password-input-hint/password-input-hint.component.ts @@ -1,4 +1,4 @@ -import { Component, input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { BooleanOrNullOrUndefined } from '@core/helpers/types.helper'; @Component({ @@ -6,6 +6,7 @@ import { BooleanOrNullOrUndefined } from '@core/helpers/types.helper'; imports: [], templateUrl: './password-input-hint.component.html', styleUrl: './password-input-hint.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, }) export class PasswordInputHintComponent { isError = input(false); diff --git a/src/app/shared/components/search-input/search-input.component.html b/src/app/shared/components/search-input/search-input.component.html new file mode 100644 index 000000000..d1c3a99a5 --- /dev/null +++ b/src/app/shared/components/search-input/search-input.component.html @@ -0,0 +1,11 @@ +
+ + +
diff --git a/src/app/shared/components/search-input/search-input.component.scss b/src/app/shared/components/search-input/search-input.component.scss new file mode 100644 index 000000000..126fc9970 --- /dev/null +++ b/src/app/shared/components/search-input/search-input.component.scss @@ -0,0 +1,18 @@ +@use "assets/styles/variables" as var; + +.search-container { + position: relative; + + i { + position: absolute; + left: 0.61rem; + color: var.$dark-blue-1; + font-size: 1.1rem; + } + + .search-input { + width: 100%; + padding-left: 2.61rem; + color: var.$dark-blue-1; + } +} diff --git a/src/app/shared/components/search-input/search-input.component.spec.ts b/src/app/shared/components/search-input/search-input.component.spec.ts new file mode 100644 index 000000000..b8645dc62 --- /dev/null +++ b/src/app/shared/components/search-input/search-input.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SearchInputComponent } from './search-input.component'; + +describe('SearchInputComponent', () => { + let component: SearchInputComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SearchInputComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SearchInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/components/search-input/search-input.component.ts b/src/app/shared/components/search-input/search-input.component.ts new file mode 100644 index 000000000..ba50cde32 --- /dev/null +++ b/src/app/shared/components/search-input/search-input.component.ts @@ -0,0 +1,25 @@ +import { + ChangeDetectionStrategy, + Component, + input, + model, +} from '@angular/core'; +import { InputText } from 'primeng/inputtext'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'osf-search-input', + standalone: true, + imports: [InputText, FormsModule], + templateUrl: './search-input.component.html', + styleUrl: './search-input.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SearchInputComponent { + placeholder = input(''); + searchValue = model.required(); + + onSearchChange(value: string): void { + this.searchValue.set(value); + } +} diff --git a/src/app/shared/components/sub-header/sub-header.component.html b/src/app/shared/components/sub-header/sub-header.component.html index 6efbdd61c..17cd23819 100644 --- a/src/app/shared/components/sub-header/sub-header.component.html +++ b/src/app/shared/components/sub-header/sub-header.component.html @@ -1,6 +1,8 @@
- + @if (icon()) { + + }

{{ title() }}

@if (showButton()) { diff --git a/src/app/shared/components/sub-header/sub-header.component.ts b/src/app/shared/components/sub-header/sub-header.component.ts index 96f7efcf3..edf768bd9 100644 --- a/src/app/shared/components/sub-header/sub-header.component.ts +++ b/src/app/shared/components/sub-header/sub-header.component.ts @@ -1,4 +1,10 @@ -import { Component, inject, input, output } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + input, + inject, + output, +} from '@angular/core'; import { Button } from 'primeng/button'; import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; import { toSignal } from '@angular/core/rxjs-interop'; @@ -8,7 +14,7 @@ import { toSignal } from '@angular/core/rxjs-interop'; imports: [Button], templateUrl: './sub-header.component.html', styleUrl: './sub-header.component.scss', - standalone: true, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class SubHeaderComponent { showButton = input(false); diff --git a/src/app/shared/entities/addon-card.interface.ts b/src/app/shared/entities/addon-card.interface.ts new file mode 100644 index 000000000..0887fb260 --- /dev/null +++ b/src/app/shared/entities/addon-card.interface.ts @@ -0,0 +1,4 @@ +export interface AddonCard { + title: string; + img: string; +} diff --git a/src/app/shared/entities/nav-item.interface.ts b/src/app/shared/entities/nav-item.interface.ts index ecc524998..668860766 100644 --- a/src/app/shared/entities/nav-item.interface.ts +++ b/src/app/shared/entities/nav-item.interface.ts @@ -2,4 +2,7 @@ export interface NavItem { path: string; label: string; icon?: string; + items?: NavItem[]; + isCollapsible?: boolean; + useExactMatch: boolean; } diff --git a/src/assets/images/temp/bitbucket.png b/src/assets/images/temp/bitbucket.png new file mode 100644 index 0000000000000000000000000000000000000000..d41abaccff3e4979110cb87962312bac6ac7ca65 GIT binary patch literal 2538 zcma);_ct31AH_q7iWRlDqP9{yW@7}kXVMmp^+;c%5)~s$_ zRe$TnT-y}O5Gd7SQ2mk=ES(uwRTrlh(Uj|+z#9MRh z0?a|?w}Sxy7OsCx2gobnzj)FGJD3>(Du;!?UmOg+P-`dvP?O4X;=y=Ph_*0+I)%|~ zzjVWJnSseT7w)MvCQPiNOkz}QA{Rg=5fXC^A3BHwDadpb0JLMWxNh2{4Ka&5GiS)v z;dgSg>N4QE^R>-gE`Q(@qZdbB*t3Jt@%dVS?6abOr~UJ;@)OPKqpP{1Po7GiD0LH9 zQ~8bAm63IX7|aR+WVM_(zyiS8p(BOU%jbP_M{g7$E_*CNeHe)edBRJSN1wd@fLZnP zIFO2GQoBejZZ5nevFa!J_xHCp17AgQM#>$7+j{YP4 zLm6){xJa8}TeXX6LQ$OB=0tJ!Ls5xO@3!Il$|MK@q>wGJ`+^8mh{#>`9XIs_@f_F9 z92wjCRmZ~-m3_ng=>Cewhl=>ntUvoT8`Ytgi)Y1N%gKzZ70?6oIXdjg3*q8i+I@tZ zuNF_nBDyP#^rfZo<80m8Bb#C^jL9O<6k}xg`XPxzNUqJ7QDOffVEsr?k6j2q_-q)j z^|_wNz!t2YZQ&3HJQpTjE%QS&`vG^{(Q;ODnq6@4Kf28O0bxMCa9BG4ZFNqFJEWX58aFnU4uBeM{*FXTT_&wZJl0OnU zS9e|3CeS_cHqzH(6cuP`3B4ue4dWGSr*zM>qxlt*nVx}?a@=$&)OY1~5Ahoo zKg27D5kH(dN7610pOz}$8${X3e+x3AY^d1^lcvu|Gm=DD)+NGFo~eHQdLM?bXQEl_ zeLc9UMc=jkZ{HQw;kMG&#c##GEc@>Uoz4Pbyh*q<53ee&m%AWp zMVrd7tc3vC!*q>9=(%O`q3wM2Kx~0h_Z3v!cvj7W@U_mls;#|o`+hyGV3CU0Dbcwz z>{zE+0qIbA`R{5C|5(WZkwD5m@~9W1T(UMZyQS8Kkq2HwnZMBXj~o$$pUPD-HM4va zrW1@h?`trcJ!w+st5>?-xsyKi97i}l6OO19IB!%0DRNHgZ&Jk*U6Ik9tWA$AHx>6_ z-kb5Tvej(c+n&>RQ+Wwy-J4MnD|1?8Jsn;9EH`dn>}~~f&L)cXq2g9k zTpbbRjb53hDcCCCPw~bk`mJ}hP>-)Mh zaZFP=#n929QCt!unK?qyf#!Tj*Z52L?7$`dzE{dskm77@bTc1>4px%j#*}!^>t?Alq&3IJE$quL*%9ED!=JmAQ(qEbeseLCX}8!q%9s% zUzy3Qqct%j-+;(@hj$!Bn|$GYY*-0brBs#nh=B8&>9E(fw^HY+IWc^C3i~;`e7Uhw z=AawpG9hG*F8U3gpLy{Z+PYsbc-sd%3d{0n^EW=a32cOP;w{gQnMOT z>Ow*js+=PBl4Wm;u*qpns%q0tfI{LUNsjNU{sa};{@&3{Qo4LtcVZI)WSC~ziu*a- z%Zr|2@ZNN*B*qO;y?a(I57648C)TFJ_nO@}LZE?p_<#i)gL?j%d3%4Yr3j*4o4q-B z4UP*{R$kmS(Ii#4NDNu~9;G124j@(m`MX0pol7Ahn>PK18tD(>B)w^0BZmXGF>e3}`@WpAw z)tp1>_Hg(bep1>(y0hQjig5e6xXmKO9Yu>k^QdPk0wF=y190t*Utl{5-Uh1NQ&T&; z@CHh72oB|FG;7I1fU3m|Xi27X9VhPP#SgYht0a7@v1p&~Z&r9oI8;+A6+JHJz|su9 z1Z=%;<8#)7Kn9D)1f%BqSr+7X>)RS4LZtl77Ol1J)m`t$zWJHeYCB1dRtcfx^W;7g w$x0QR(0n!7np_gbPEBDlwdekS%G{xG_yl6#;psEkE~Xj4!qmp3(g+#*KUNjT3jhEB literal 0 HcmV?d00001 diff --git a/src/assets/images/temp/dropbox.png b/src/assets/images/temp/dropbox.png new file mode 100644 index 0000000000000000000000000000000000000000..ee29bbf65ae29313fd45a027f942c6847f4e0fb0 GIT binary patch literal 3276 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 literal 0 HcmV?d00001 diff --git a/src/assets/images/temp/github.png b/src/assets/images/temp/github.png new file mode 100644 index 0000000000000000000000000000000000000000..a4b97bbb92573464a28c8a0426bdd9cb6c3ab0d6 GIT binary patch literal 2578 zcmV+t3hniYP)kP~Emf{+s!PGG3AtLM6BwT$>l^SI3+<+- zcVx6Xy&sZR^P!nvm5O4Q1xkPQOixdDkAMhK`~y4qba63;Dupzu?cl!>@HAw{c#64UQ9>1s7N7K#k$w+T{%2i9_)cZX2Kb z6Q3Kw6^gxGxq%;uw&!dUo&`_PdaGo3ty3iNJO11Vty|#GJWBy@!C`{3;0EhGoX0CB zOWuIb@VJa}+KR)n;Bjp!B;a=5B@bD00T-2v1?+*Ngf>K1=T8h?z#=4?wLSw!2}@uB zKUcorsLiK+8EMaLfujVuOj;IZ?VEz5gl552Ve-W+51qC8a~U{FSOVwpwRUA27Ga_} zYZ*97Xcl~3`~HZr#y&i+e3!!~aFnnFE}f&+pd{Dg&zH0|c9ihE=qI?y~##zGM4}MSc=;9j%1#bAi zT53Pp2w`V*lp6=qlG8aJlTO-+FKIi$=VVE6{yj#EJc2Hv3tSF8fju8mb*4mtXP^YU z9A6volZtZfKy{wZ&~l%GM?`^JK*2w1*_JY$$0G}ln+eoG^zFn+ z6nK5uWCTuXd&-Uz1#Tx}u0;!87s#0sniZ$u5mDeSa?R1&QlU;Shyr(!uz6ci;58x& zyw;2ufrUSwbQHJ)6}8GL7YlzZcm$Dd-0x94tzV0zTy;y#=0b(q=1KNNn zC{3r$YRl!6EHmANTeSKN93cwafJz9EX*?;+OM-jF8`J?H1It8#Yfxg{1HA3ov!jg+ z9O&l}1)f0-$5$|9t&xB;@zP#mUKChCPE&h+^K_p5LX`{!rQoJ&$t36JwDCzCvc(HH z1Perg;d_?l=Z4!-*mGQOT()iWceZ1)WXZ8lR$2Amlz4Z6Uh&yH862(7SpoZ|o2f;C zKcGWZFGZmni>Jl5WNY5z2j;2Cl%Jp@S*jG}K)#iK(WqV*-wDgqD>Y>OsiNqWHwtO5{O|+U^?!WxWU5 zjTHn`nh4v;D%dyrH3>5W6Lw!XGua(JY=OC^XwsV=!_~4oKGaw(XGa60Ssg(OOpZ>% zZ0X4yt1*0D)RNnd?J=EY8T>j}yr2}C3+5K3^2{?Q%e*YrxaEwwo!Mx67?bWm?F~PW zFipEW8NO2&c>lIurb5q-ad{=Elx3&kdATIowNc^{1y=MBd6rCl-Gc52m^E=FQQ#Ul z#9pGvz1j=+pgeTTV!PoOi>_0^4=@A;&bOQJi|t<0v3zs}ipF@`=3Ji^hR!Rv%b_`~{fvwmTdC(A8fOJwfv|PT6IS#c zAMYKjS)3G@&M3;sqcxxD=)qAr^Wc=g%4)BAH9(Q=6s9c|rcTxOUqqP{DO%_=6ON4TkI@&2{zq>qa;{czRIakeMC+0FOOyPtDx12;)QVM&%Y^n>T z#U>=Q+)PLt{?;4(J14LvSQofMT)7W>ICKt^68cODt+PfVTNSutSiZ(}>)|S6yJ`(A z+Z5^_F3bI`hQhAE{2J8gxqz+5+ga#}Gc?xrgVFJkmXqo?%pUqU(=a8|Z^GNbQfMt< zMPQW>e$3puuyLZN3To^F*A?esi4#g4z>Wo%^KKov20aE#sGKR?u_`E2atA!cxWLM> zNwpj0FliOqJ>sh!PQg=bSg`(P$$8h6*zXe>Me0pcytQJZ0;@MEtzFJsI!T+BWd)lE z@1+%+wO~H5;NDuZtXa~n_{zfO$C8*8nCne7aWDR>f-Cx5Y_LP!P#6=K9iB19?Cid) zU;-S8fAg?O3)WL?3Uh_f^TKwh1KfV}408et_RXjP#_YzzC-4ko0@D_pen?d6Y^DUacP;!&p*6h@ zzG1F8mnnh6^EOieXS7S~P?!~XL@g2A&vzQLd1GECz#A2~F;l`JD{kzSm9u$T&SO4Q zIcdmu`paHU^!I=dU3o}yYE?Q|SI)kV3{f%A6=!cOr*;8{rQO>~}e5?m(5O%KVK o^^7yBoR~H%l?O5hB{OZC3&Nf#8x#P*d?vo_CISG6J-}F?uG|w? zq;(LLhvYtYQ6Zn{P^%Yu1rkzFS*2GPN<+JgluzH_=rEU4V0z9eSlc`ACc%4%wtyGN zE3UHUB|LXX3TWrof&EODtXtnl`>TKWV33g+T0w$Zms#{|XS_6N+!E3WL|dze-s`Mf z_7xU;tp5M-(5#_F@53y+oc37M!48x*r#DlUC%&zyI!nziFfyK#$?tw@XD{EH_4JVL z_rM*>vBS_P3$e`2HENFNJV6Bix={mYjuCRWDGSz1WR#7{d(Zeccb*;zu3R6oud*H>N>M)zYP8Op_X%C9cn1e zz4u5m_aAvykvL4sI>lp|GG16`7cW2jj9^(fP~=$Kgz`UG5jtpLf`iG<_Yobxs7l5w zq|t}a9nHY%85|7!O{G0~I}lkZahb~vptCuQMLl=_LO~&zwQFrsAYqalqA1I063bM) zqpYY=A}y$m9RUK#Diw9Ha^(WC7yW-(*|WYRU+rVsoB<0; z_(j9tc|=EPfn>|F-_4O7#}-GMVDxIl2CDfs$E%%9yEW#plWM?z-}i^1LxDj>*YFO0Epo%_Te>A&khTEz<$B2WXWVB1&7)o>F^=-}*1=I_htZ#^s*dzVFA9?Qq=GRz zeqH+x1twgZfJHJB78_X}hlSCe5;r!_*cykPEB^&cO>bi3#;M>Em+cvPE1cYBH?WAU z*}psjb>)%8-@TCe?o_%i*lanXwLP4b9uv|eC93oG?D>b8sy(9q`jXl+(vI#2c&Mf| zj$q`vm#P!qT#)>UdzBrXT;Ak{JgDPiy`YOXG0|k>*}9Ysbn2Jh@t)Juvy>A;9Q{70 zi8tC;M&~@B`_!PE@O`ZE1emKmO*{pikg%Cn7ED4*dst`_ynTHBa5>UBkD^-@fx#ec5i$Q<|JqjGk6lJ)!EKhJdUP5@$}uBQ8EN;2dz-W1EEYPwyY3RtO?RVc|{8emr9bKQBT=&239;+wh| zcIfSVxyFW?t=-p=h{!u-zIUi@&9+HfrG$FDW6N@hKPUytCd$o6RyY$tR{sF(nuUC+ z3#x0AN0>d{l9-sTzyqzW1tkzKh35Z4D=rL@ZfR}%q!&!0D7=fBY@7FN&gaVo%9b^> z9C9Z_Kt4d$=9>(*%?IuUXIrVfXOny$Y^fMIT^~7d^oUYqBI^^X>2}d=R&}X^B}-|} z`g!Zerua|?v!`YAmFM?I_H^2qD#o(8ojoeoAy)-WMp_r~c@aZ8hU#o_XG=#j99J0T-8uFUs#iKbkx*0%B3zMcP z8b03Btn|lAzVe0TH2gwNaYvLK?^K(G1pA&|0JIcizk$!yiXYYzoevzIb;pE(Nf(ls zw8xhXax)~}JjY(@Iq4-Bx7hmiIjy;>{sS4{^)c1c<=&+8_juGr%&*qA{;!OpFZGt- zB=`p2##M^RFybH5;TH`IZVNmUfVG4Ux#daowh_0EpMK-==byf1f(c#L=491W+t;R1;c~f%B~^3b41+2+n(t!b{-Uz3GtB6R2+R-SP)ztTKzUN$H1R~l0t&$KDSx6H#e#dqIDM8S*y zWY4EzX+}kiy6K89)>)svGF+Pf?{m$nTuJ`!0vk93NslpD>Qg2X*|Fim$BXK4*IFs+rm9Z4)`Di6fzBR`;F>0Aq=@C^U0@{4d!$PU!#u literal 0 HcmV?d00001 diff --git a/src/assets/images/temp/owncloud.png b/src/assets/images/temp/owncloud.png new file mode 100644 index 0000000000000000000000000000000000000000..e05e37c50d0655890d8c0a5c691c899e442b1159 GIT binary patch literal 4950 zcmcIo)k6~w)J3`(-7P(E2uOo4Vu*B0ZFC5X(!uB$B_Jp%NT)QYLppvmqeHq=xM{bz6;m9&(wuxb-Y?qPUXSXBEODoTckCkNSG#$3~EH&j8i&*K;D6~{T0(YQV6_@y7fv|Zx_2w%+|Hpo@br-}2 zL!<>Y|H=#d)y}3#SLD0$(C^=j=pNuZ!rQ*DlUy3z^tQ)2Rp(-P;*Rtj6CU;C(Q5>P zLjI-l4-a^%<>A=wVjN3j+yo2K?)B#)k8G+IqUXv#8LN*Llj0B(%~XYYPWH_oZ;6|ZD6*r_*<_h-j2-fuY(f-bwHI zDD1ZI(OBqeSkvTXhw$EiX;(t}A}q@0q}|9MpKd3k5)pi1P=&`=up?3PZw9=NUU?zH z_?Xg2l0)%_gVPipY()yWIyG9NiW;`gPpAK;^I-C|t#71`y%X0(MKW8XjQRCNXljaH zw!?;&nu{D9J%%4i_f0pB%_iyt%({+htcLP@Jx62g1x$mL;)S^PM0svS!8U|6AtB z=k9%EnWt0VWs-6-v&+1w@hB(B8?A(-$N=oJ|br{=NBHk_PAV#&$d85VTz~@`NbvC z?_xl=d_|z!XNadlqI^rd_Zq2gI~{e(2SvXV@b}%{tY?bg5PA%&LH$qn#>NA+%d;BrW~XQ^iH2?40fwMnX4qkdrM>{=-kf4HtYJ;r1Oqhf^QgyHHJs^ z1LtMoGjn2XpnR0Wti24H{4G)XnM3Vov78q&-=Kc0_kuXWHbWkNt+wcco2<#CS^JWx zOQz1%O-RgoTnPSc#~Na!pr9xNR(MYP{nZvS^+FU3qO7)TRseI@b@(zIx?uk-_3dPm zf#MOfJ)3s52RNanH&nEKx>iQ8Q&{v!n7b16p>)p7661H#w6w47h*^+kM@g?Y&uEpy zfl~G9OnD)$n4XKoa$q~>(unCZ1-FI_BvIab)khm;dGyNYWP1muXoy|_OSE5(EsM#D$*D9KTbL4my zR17_bw-;x%N1wDSew*)de}<0TpNmxIgltMLqBB|xPQ&wjWKl5vb{fr7~iw$YK&ocft#m7D6b~n%m~cnDGj{C$KENOH&L*n zhJ3BJO()7QRa{R}ok0$Bx)8_2`EqmzVD9Ta#Ob4@R3Phnxd^2taSZw<=JoVpZWKa3 z+j2B|zx)wJ|3eH+`#J~!d~RKcuBocx{{BYvOAH+vS{0ZntO78;*69M1<%`A8+qA(k zn$fez*C8fDm~AP|H9{fk41uYf^_~?;F1bq?qWlUkW&4*;=wFMmL8a{89ehYQi*!0k zR^nn-`L&498u)H=4GcHH_o`46C?P4%lTN*O!*iaDEt2lAouk)rdu?ud9Ck0SVnE=S z3KWQ2_5PZn??qAC`q2pM?bSH3ZaZGMVWR_%Xy^D=KDNqhC9+anGke{>>pN<)vysY? z4SIR|x;kU?7pt%`!lEyz5}uU4*Ln|{^?STg0r?L>i4+K%h&eCh=^?q z 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�(-B56Wih^ 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 .p-panelmenu-item-content:hover { + background-color: transparent; + } + + .p-panelmenu-header:not(.p-disabled):focus-visible + .p-panelmenu-header-content, + .p-panelmenu-item.p-focus > .p-panelmenu-item-content { + background-color: var.$dark-blue-2; + border-radius: 0.5rem; + } + + .p-panelmenu-submenu { + padding: 0; + margin-left: 1.5rem; + margin-top: 0.5rem; + + .p-panelmenu-item { + border-left: 1px solid var.$grey-2; + height: 3rem; + padding: 0 0 1rem 0.5rem; + + .nav-link { + height: 2.5rem; + } + } + } +} diff --git a/src/assets/styles/overrides/radio.scss b/src/assets/styles/overrides/radio.scss new file mode 100644 index 000000000..6228cc469 --- /dev/null +++ b/src/assets/styles/overrides/radio.scss @@ -0,0 +1,14 @@ +@use "../variables" as var; + +.radio-label { + color: var.$dark-blue-1; +} + +.p-radiobutton-checked .p-radiobutton-box { + border-color: var.$pr-blue-1; + background: transparent; +} + +.p-radiobutton-checked .p-radiobutton-box .p-radiobutton-icon { + background: var.$pr-blue-1; +} diff --git a/src/assets/styles/overrides/stepper.scss b/src/assets/styles/overrides/stepper.scss new file mode 100644 index 000000000..f7535856f --- /dev/null +++ b/src/assets/styles/overrides/stepper.scss @@ -0,0 +1,12 @@ +@use "../variables" as var; +@use "../mixins" as mix; + +.p-stepper { + .p-steppanels { + padding: 0; + } + + .p-stepper-separator { + display: none; + } +} diff --git a/src/assets/styles/overrides/table.scss b/src/assets/styles/overrides/table.scss index 005f3f16a..801648d80 100644 --- a/src/assets/styles/overrides/table.scss +++ b/src/assets/styles/overrides/table.scss @@ -171,4 +171,18 @@ } } } + + tr.background-warning { + td { + background-color: var.$red-2; + white-space: wrap; + } + } + + tr.background-success { + td { + background-color: var.$green-2; + white-space: wrap; + } + } } diff --git a/src/assets/styles/overrides/tabs.scss b/src/assets/styles/overrides/tabs.scss new file mode 100644 index 000000000..e9cbad9a6 --- /dev/null +++ b/src/assets/styles/overrides/tabs.scss @@ -0,0 +1,22 @@ +@use "assets/styles/variables" as var; + +.p-tablist-tab-list { + background: transparent; + border: none; + + .p-tab { + color: var.$dark-blue-1; + border: none; + } + + .p-tab-active { + background: white; + border-top-left-radius: 0.7rem; + border-top-right-radius: 0.7rem; + border-color: transparent; + } + + .p-tablist-active-bar { + display: none; + } +} diff --git a/src/assets/styles/styles.scss b/src/assets/styles/styles.scss index 86c2abb63..7f9214ce4 100644 --- a/src/assets/styles/styles.scss +++ b/src/assets/styles/styles.scss @@ -10,6 +10,11 @@ @use "./overrides/drawer"; @use "./overrides/card"; @use "./overrides/carousel"; +@use "./overrides/tabs"; +@use "./overrides/autocomplete"; +@use "./overrides/panel-menu"; +@use "./overrides/stepper"; +@use "./overrides/radio"; @layer base, primeng, reset; From 877ae3c6db1724a750c0bb53b3a3826dfc98abcf Mon Sep 17 00:00:00 2001 From: Roman Nastyuk Date: Thu, 20 Mar 2025 17:46:06 +0200 Subject: [PATCH 2/3] feat(configure-addons-settings): added mobile layout to the addon settings --- src/app/features/home/home.component.html | 1 + src/app/features/home/home.component.scss | 45 +---- .../addon-card-list.component.html | 2 +- .../addon-card-list.component.ts | 1 + .../addon-card/addon-card.component.html | 7 +- .../addon-card/addon-card.component.scss | 13 ++ .../addons/addon-card/addon-card.component.ts | 6 +- .../settings/addons/addons.component.html | 26 ++- .../settings/addons/addons.component.ts | 20 ++ .../connect-addon.component.html | 177 ++++++++++-------- .../connect-addon.component.scss | 34 +--- .../connect-addon/connect-addon.component.ts | 29 +-- .../settings-container.component.html | 4 +- .../settings-container.component.scss | 12 +- .../settings/settings-container.component.ts | 8 +- .../shared/entities/addon-terms.interface.ts | 5 + .../entities/google-drive-folder.interface.ts | 4 + src/assets/styles/overrides/autocomplete.scss | 1 - src/assets/styles/overrides/dropdown.scss | 22 +++ src/assets/styles/overrides/table.scss | 129 ++++++++----- src/assets/styles/styles.scss | 1 + 21 files changed, 319 insertions(+), 228 deletions(-) create mode 100644 src/app/shared/entities/addon-terms.interface.ts create mode 100644 src/app/shared/entities/google-drive-folder.interface.ts create mode 100644 src/assets/styles/overrides/dropdown.scss diff --git a/src/app/features/home/home.component.html b/src/app/features/home/home.component.html index b775ce7ca..c3cff8964 100644 --- a/src/app/features/home/home.component.html +++ b/src/app/features/home/home.component.html @@ -34,6 +34,7 @@ /> - + } } diff --git a/src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts index 1417b38af..fe43facbb 100644 --- a/src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts +++ b/src/app/features/settings/addons/addon-card-list/addon-card-list.component.ts @@ -10,4 +10,5 @@ import { AddonCard } from '@shared/entities/addon-card.interface'; }) export class AddonCardListComponent { cards = input([]); + cardButtonLabel = input(''); } diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.html b/src/app/features/settings/addons/addon-card/addon-card.component.html index dd08a2531..a5779d782 100644 --- a/src/app/features/settings/addons/addon-card/addon-card.component.html +++ b/src/app/features/settings/addons/addon-card/addon-card.component.html @@ -1,4 +1,7 @@ -
+
Addon card image

{{ card()?.title }}

diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.scss b/src/app/features/settings/addons/addon-card/addon-card.component.scss index 7e1d195bd..9deebed59 100644 --- a/src/app/features/settings/addons/addon-card/addon-card.component.scss +++ b/src/app/features/settings/addons/addon-card/addon-card.component.scss @@ -13,5 +13,18 @@ text-align: center; border: 1px solid var.$grey-2; border-radius: 1rem; + padding: 2rem; + } + + .mobile { + img { + max-height: 4.5rem; + } + } + + .addon-card.mobile { + height: 12.5rem; + gap: 0.5rem; + padding: 1rem; } } diff --git a/src/app/features/settings/addons/addon-card/addon-card.component.ts b/src/app/features/settings/addons/addon-card/addon-card.component.ts index c8db8b763..ae9229105 100644 --- a/src/app/features/settings/addons/addon-card/addon-card.component.ts +++ b/src/app/features/settings/addons/addon-card/addon-card.component.ts @@ -1,7 +1,9 @@ -import { Component, input } from '@angular/core'; +import { Component, inject, input } from '@angular/core'; import { Button } from 'primeng/button'; import { AddonCard } from '@shared/entities/addon-card.interface'; import { Router } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; @Component({ selector: 'osf-addon-card', @@ -11,6 +13,8 @@ import { Router } from '@angular/router'; }) export class AddonCardComponent { card = input(); + cardButtonLabel = input(''); + isMobile = toSignal(inject(IS_XSMALL)); constructor(private router: Router) {} diff --git a/src/app/features/settings/addons/addons.component.html b/src/app/features/settings/addons/addons.component.html index abe04359f..9665e5762 100644 --- a/src/app/features/settings/addons/addons.component.html +++ b/src/app/features/settings/addons/addons.component.html @@ -1,12 +1,23 @@
- - - All Add-ons - Connected Add-ons - + + @if (!isMobile()) { + + All Add-ons + Connected Add-ons + + } + @if (isMobile()) { + + }

Sync your projects with external services to help stay connected and @@ -40,7 +51,10 @@ placeholder="Search add-ons" /> - + diff --git a/src/app/features/settings/addons/addons.component.ts b/src/app/features/settings/addons/addons.component.ts index bef6bb02f..53e0f7103 100644 --- a/src/app/features/settings/addons/addons.component.ts +++ b/src/app/features/settings/addons/addons.component.ts @@ -5,6 +5,7 @@ import { signal, OnInit, untracked, + inject, } from '@angular/core'; import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; import { Tab, TabList, TabPanel, TabPanels, Tabs } from 'primeng/tabs'; @@ -12,6 +13,15 @@ import { SearchInputComponent } from '@shared/components/search-input/search-inp import { AutoCompleteModule } from 'primeng/autocomplete'; import { AddonCardListComponent } from '@osf/features/settings/addons/addon-card-list/addon-card-list.component'; import { AddonCard } from '@shared/entities/addon-card.interface'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; +import { DropdownModule } from 'primeng/dropdown'; +import { FormsModule } from '@angular/forms'; + +interface TabOption { + label: string; + value: number; +} @Component({ selector: 'osf-addons', @@ -26,6 +36,8 @@ import { AddonCard } from '@shared/entities/addon-card.interface'; SearchInputComponent, AutoCompleteModule, AddonCardListComponent, + DropdownModule, + FormsModule, ], templateUrl: './addons.component.html', styleUrl: './addons.component.scss', @@ -33,8 +45,16 @@ import { AddonCard } from '@shared/entities/addon-card.interface'; }) export class AddonsComponent implements OnInit { defaultTabValue = 0; + isMobile = toSignal(inject(IS_XSMALL)); searchValue = signal(''); cards = signal([]); + selectedTab = this.defaultTabValue; + + tabOptions: TabOption[] = [ + { label: 'All Add-ons', value: 0 }, + { label: 'Connected Add-ons', value: 1 }, + ]; + filteredCards = computed((): AddonCard[] => { const searchValue = this.searchValue(); diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.html b/src/app/features/settings/addons/connect-addon/connect-addon.component.html index bb1cd4834..e9ababd24 100644 --- a/src/app/features/settings/addons/connect-addon/connect-addon.component.html +++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.html @@ -7,7 +7,7 @@

Figshare Terms

- + Function @@ -40,11 +40,12 @@

Figshare Terms

-
+
Account Name Google Drive -
+
-
-
- - - - - -
-

Configure Google Drive

-
    -
  • - - -
  • -
  • - - -
  • -
-
- -
+ - - -
-

Configure Google Drive

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -
- @for (folder of folders; track folder.name) { -
- - - - {{ folder.name }} -
- } -
+ + + + -
- - -
-
-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.scss b/src/app/features/settings/addons/connect-addon/connect-addon.component.scss index 1ace2640a..bbed0f544 100644 --- a/src/app/features/settings/addons/connect-addon/connect-addon.component.scss +++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.scss @@ -13,37 +13,9 @@ width: 100%; } } -} - -.folders-list { - max-height: 400px; - overflow-y: auto; - padding: 1rem; - border: 1px solid #dee2e6; - border-radius: 6px; - background-color: #f8f9fa; -} - -.folder-item { - padding: 0.5rem; - border-radius: 4px; - - &:hover { - background-color: #e9ecef; - } - i { - color: #6c757d; - } -} - -:host ::ng-deep { - .p-checkbox { - .p-checkbox-box { - &.p-highlight { - border-color: var(--primary-color); - background: var(--primary-color); - } - } + .folders-list { + border: 1px solid var.$grey-2; + border-radius: 0.57rem; } } diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.ts b/src/app/features/settings/addons/connect-addon/connect-addon.component.ts index e29dd6116..2ed8b71bb 100644 --- a/src/app/features/settings/addons/connect-addon/connect-addon.component.ts +++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, signal } from '@angular/core'; import { SubHeaderComponent } from '@shared/components/sub-header/sub-header.component'; import { StepPanel, StepPanels, Stepper } from 'primeng/stepper'; import { Button } from 'primeng/button'; @@ -9,17 +9,9 @@ import { Card } from 'primeng/card'; import { RadioButton } from 'primeng/radiobutton'; import { FormsModule } from '@angular/forms'; import { Checkbox } from 'primeng/checkbox'; - -interface Term { - function: string; - status: string; - type: 'warning' | 'info'; -} - -interface Folder { - name: string; - selected: boolean; -} +import { GoogleDriveFolder } from '@shared/entities/google-drive-folder.interface'; +import { AddonTerm } from '@shared/entities/addon-terms.interface'; +import { Divider } from 'primeng/divider'; @Component({ selector: 'osf-connect-addon', @@ -36,6 +28,7 @@ interface Folder { FormsModule, RadioButton, Checkbox, + Divider, ], templateUrl: './connect-addon.component.html', styleUrl: './connect-addon.component.scss', @@ -43,7 +36,8 @@ interface Folder { }) export class ConnectAddonComponent { radioConfig = ''; - folders: Folder[] = [ + selectedFolders = signal([]); + folders: GoogleDriveFolder[] = [ { name: 'folder name example', selected: false }, { name: 'folder name example', selected: false }, { name: 'folder name example', selected: false }, @@ -51,7 +45,7 @@ export class ConnectAddonComponent { { name: 'folder name example', selected: false }, { name: 'folder name example', selected: false }, ]; - terms: Term[] = [ + terms: AddonTerm[] = [ { function: 'Add / Update Files', status: 'You cannot add or update files for figshare within OSF.', @@ -93,4 +87,11 @@ export class ConnectAddonComponent { type: 'warning', }, ]; + + toggleFolderSelection(folder: GoogleDriveFolder): void { + folder.selected = !folder.selected; + this.selectedFolders.set( + this.folders.filter((f) => f.selected).map((f) => f.name), + ); + } } diff --git a/src/app/features/settings/settings-container.component.html b/src/app/features/settings/settings-container.component.html index 67e7bd4cd..f58acf390 100644 --- a/src/app/features/settings/settings-container.component.html +++ b/src/app/features/settings/settings-container.component.html @@ -1 +1,3 @@ - +
+ +
diff --git a/src/app/features/settings/settings-container.component.scss b/src/app/features/settings/settings-container.component.scss index ecfe63d1c..4bafbf07a 100644 --- a/src/app/features/settings/settings-container.component.scss +++ b/src/app/features/settings/settings-container.component.scss @@ -1,6 +1,12 @@ +@use "assets/styles/mixins" as mix; + :host { - margin-top: 4.5rem; - display: flex; - flex-direction: column; + @include mix.flex-column; flex: 1; + + .desktop { + @include mix.flex-column; + margin-top: 4.5rem; + flex: 1; + } } diff --git a/src/app/features/settings/settings-container.component.ts b/src/app/features/settings/settings-container.component.ts index ee731c455..e5245d081 100644 --- a/src/app/features/settings/settings-container.component.ts +++ b/src/app/features/settings/settings-container.component.ts @@ -1,5 +1,7 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { RouterOutlet } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { IS_WEB } from '@shared/utils/breakpoints.tokens'; @Component({ selector: 'osf-settings-container', @@ -8,4 +10,6 @@ import { RouterOutlet } from '@angular/router'; styleUrl: './settings-container.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SettingsContainerComponent {} +export class SettingsContainerComponent { + isDesktop = toSignal(inject(IS_WEB)); +} diff --git a/src/app/shared/entities/addon-terms.interface.ts b/src/app/shared/entities/addon-terms.interface.ts new file mode 100644 index 000000000..1787afb95 --- /dev/null +++ b/src/app/shared/entities/addon-terms.interface.ts @@ -0,0 +1,5 @@ +export interface AddonTerm { + function: string; + status: string; + type: 'warning' | 'info'; +} diff --git a/src/app/shared/entities/google-drive-folder.interface.ts b/src/app/shared/entities/google-drive-folder.interface.ts new file mode 100644 index 000000000..21900cc26 --- /dev/null +++ b/src/app/shared/entities/google-drive-folder.interface.ts @@ -0,0 +1,4 @@ +export interface GoogleDriveFolder { + name: string; + selected: boolean; +} diff --git a/src/assets/styles/overrides/autocomplete.scss b/src/assets/styles/overrides/autocomplete.scss index 9ed8ca58d..e3fbd660f 100644 --- a/src/assets/styles/overrides/autocomplete.scss +++ b/src/assets/styles/overrides/autocomplete.scss @@ -11,7 +11,6 @@ outline: none; height: 3rem; width: 100%; - background-color: transparent; box-shadow: none; } diff --git a/src/assets/styles/overrides/dropdown.scss b/src/assets/styles/overrides/dropdown.scss new file mode 100644 index 000000000..bbd7a0baf --- /dev/null +++ b/src/assets/styles/overrides/dropdown.scss @@ -0,0 +1,22 @@ +@use "assets/styles/variables" as var; +@use "assets/styles/mixins" as mix; + +.p-dropdown { + height: 3.125rem; + border: 1px solid var(--grey-2); + border-radius: 0.57rem; + outline: none; + font-size: 16px; + color: var.$dark-blue-1; + + .p-select-label { + @include mix.flex-align-center; + } + + .p-select-option { + color: var.$dark-blue-1; + &.p-select-option-selected.p-focus { + background: var.$bg-blue-2; + } + } +} diff --git a/src/assets/styles/overrides/table.scss b/src/assets/styles/overrides/table.scss index 801648d80..8bdf63328 100644 --- a/src/assets/styles/overrides/table.scss +++ b/src/assets/styles/overrides/table.scss @@ -8,6 +8,7 @@ border-radius: 8px; padding: 24px; } + table { th, td { @@ -118,71 +119,103 @@ } } - @media (max-width: 1279.99px) { - p-table { - .p-datatable { - padding: 1.71rem 0.85rem 1.71rem 0.85rem; + .addon-table { + tr { + &.background-warning td { + background-color: var.$red-2; } - table { - th, - td { - max-width: calc((100vw - 6.85rem - 2px) / 3); - } + &.background-success td { + background-color: var.$green-2; } } } +} - @media (max-width: 599.99px) { - p-table { - .p-datatable { - padding: 0.4rem; +@media (max-width: 1279.99px) { + .home-table { + .p-datatable { + padding: 1.71rem 0.85rem 1.71rem 0.85rem; + } + + table { + th, + td { + max-width: calc((100vw - 6.85rem - 2px) / 3); } + } + } - table { - thead { - display: none; - } + .addon-table { + .p-datatable { + padding: 0.85rem; + overflow-x: auto; + } + } +} - tr { - max-width: calc(100vw - 3.14rem - 2px); - display: flex; - flex-direction: column; - padding: 0.85rem; - background: var.$bg-blue-3; +@media (max-width: 599.99px) { + .home-table { + .p-datatable { + padding: 0.4rem; + } - td { - max-width: calc(100vw - 3.14rem - 2px); - height: 2rem; - line-height: 2rem; - padding: 0; - border: 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - } + table { + thead { + display: none; + } + + tr { + max-width: calc(100vw - 3.14rem - 2px); + display: flex; + flex-direction: column; + padding: 0.85rem; + background: var.$bg-blue-3; - tbody { - display: flex; - flex-direction: column; - row-gap: 0.42rem; + td { + max-width: calc(100vw - 3.14rem - 2px); + height: 2rem; + line-height: 2rem; + padding: 0; + border: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } } - } - } - tr.background-warning { - td { - background-color: var.$red-2; - white-space: wrap; + tbody { + display: flex; + flex-direction: column; + row-gap: 0.42rem; + } } } - tr.background-success { - td { - background-color: var.$green-2; - white-space: wrap; + .addon-table { + width: 100%; + overflow-x: auto; + display: block; + + .p-datatable { + padding: 0.85rem; + width: 100%; + + table { + width: 100%; + table-layout: auto; + } + + td { + white-space: wrap; + overflow: visible; + text-overflow: unset; + max-width: max-content; + } + + td:nth-child(2) { + white-space: nowrap; + } } } } diff --git a/src/assets/styles/styles.scss b/src/assets/styles/styles.scss index 7f9214ce4..454a8357d 100644 --- a/src/assets/styles/styles.scss +++ b/src/assets/styles/styles.scss @@ -15,6 +15,7 @@ @use "./overrides/panel-menu"; @use "./overrides/stepper"; @use "./overrides/radio"; +@use "./overrides/dropdown"; @layer base, primeng, reset; From 6f5095911b0296e9c7d60304d35b919002ec29bb Mon Sep 17 00:00:00 2001 From: Roman Nastyuk Date: Thu, 20 Mar 2025 18:31:45 +0200 Subject: [PATCH 3/3] feat(configure-addons-settings): fixed review comments --- .../components/nav-menu/nav-menu.component.ts | 26 +-- .../settings/addons/addons.component.ts | 16 +- .../connect-addon.component.html | 188 +++++++++--------- .../connect-addon/connect-addon.component.ts | 8 +- .../search-input/search-input.component.ts | 4 +- 5 files changed, 115 insertions(+), 127 deletions(-) diff --git a/src/app/core/components/nav-menu/nav-menu.component.ts b/src/app/core/components/nav-menu/nav-menu.component.ts index dc9f2a8ed..c11d6efb9 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.ts +++ b/src/app/core/components/nav-menu/nav-menu.component.ts @@ -13,27 +13,17 @@ import { MenuItem } from 'primeng/api'; export class NavMenuComponent { navItems = NAV_ITEMS; - protected menuItems: MenuItem[] = this.navItems.map((item) => - this.convertToMenuItem(item), - ); - - private convertToMenuItem(item: (typeof NAV_ITEMS)[0]): MenuItem { - const menuItem: MenuItem = { + private convertToMenuItem(item: (typeof NAV_ITEMS)[number]): MenuItem { + return { label: item.label, icon: item.icon ? `osf-icon-${item.icon}` : '', expanded: false, + routerLink: item.isCollapsible ? undefined : item.path, + items: item.items?.map((subItem) => this.convertToMenuItem(subItem)), }; - - if (!item.isCollapsible) { - menuItem.routerLink = item.path; - } - - if (item.items) { - menuItem.items = item.items.map((subItem) => - this.convertToMenuItem(subItem), - ); - } - - return menuItem; } + + protected menuItems: MenuItem[] = this.navItems.map((item) => + this.convertToMenuItem(item), + ); } diff --git a/src/app/features/settings/addons/addons.component.ts b/src/app/features/settings/addons/addons.component.ts index 53e0f7103..90d7dc18f 100644 --- a/src/app/features/settings/addons/addons.component.ts +++ b/src/app/features/settings/addons/addons.component.ts @@ -44,18 +44,16 @@ interface TabOption { changeDetection: ChangeDetectionStrategy.OnPush, }) export class AddonsComponent implements OnInit { - defaultTabValue = 0; - isMobile = toSignal(inject(IS_XSMALL)); - searchValue = signal(''); - cards = signal([]); - selectedTab = this.defaultTabValue; - - tabOptions: TabOption[] = [ + protected readonly defaultTabValue = 0; + protected readonly isMobile = toSignal(inject(IS_XSMALL)); + protected readonly searchValue = signal(''); + protected readonly cards = signal([]); + protected readonly selectedTab = this.defaultTabValue; + protected readonly tabOptions: TabOption[] = [ { label: 'All Add-ons', value: 0 }, { label: 'Connected Add-ons', value: 1 }, ]; - - filteredCards = computed((): AddonCard[] => { + protected readonly filteredCards = computed((): AddonCard[] => { const searchValue = this.searchValue(); return untracked(() => diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.html b/src/app/features/settings/addons/connect-addon/connect-addon.component.html index e9ababd24..ae967b61d 100644 --- a/src/app/features/settings/addons/connect-addon/connect-addon.component.html +++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.html @@ -89,103 +89,103 @@

Account Name

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/src/app/features/settings/addons/connect-addon/connect-addon.component.ts b/src/app/features/settings/addons/connect-addon/connect-addon.component.ts index 2ed8b71bb..85fc54c05 100644 --- a/src/app/features/settings/addons/connect-addon/connect-addon.component.ts +++ b/src/app/features/settings/addons/connect-addon/connect-addon.component.ts @@ -35,9 +35,9 @@ import { Divider } from 'primeng/divider'; standalone: true, }) export class ConnectAddonComponent { - radioConfig = ''; - selectedFolders = signal([]); - folders: GoogleDriveFolder[] = [ + protected radioConfig = ''; + protected readonly selectedFolders = signal([]); + protected readonly folders: GoogleDriveFolder[] = [ { name: 'folder name example', selected: false }, { name: 'folder name example', selected: false }, { name: 'folder name example', selected: false }, @@ -45,7 +45,7 @@ export class ConnectAddonComponent { { name: 'folder name example', selected: false }, { name: 'folder name example', selected: false }, ]; - terms: AddonTerm[] = [ + protected readonly terms: AddonTerm[] = [ { function: 'Add / Update Files', status: 'You cannot add or update files for figshare within OSF.', diff --git a/src/app/shared/components/search-input/search-input.component.ts b/src/app/shared/components/search-input/search-input.component.ts index ba50cde32..78d5a7c5a 100644 --- a/src/app/shared/components/search-input/search-input.component.ts +++ b/src/app/shared/components/search-input/search-input.component.ts @@ -16,8 +16,8 @@ import { FormsModule } from '@angular/forms'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class SearchInputComponent { - placeholder = input(''); - searchValue = model.required(); + protected readonly placeholder = input(''); + protected readonly searchValue = model.required(); onSearchChange(value: string): void { this.searchValue.set(value);