From 8e9b05ebae84e5d753ac33874f4661eaffdab87f Mon Sep 17 00:00:00 2001
From: Roman Nastyuk
Date: Tue, 2 Sep 2025 13:29:12 +0300
Subject: [PATCH 01/11] feat(verified-links): fixed minor linked resources bugs
---
.../linked-resources/linked-resources.component.html | 9 +++++++--
.../linked-resources/linked-resources.component.ts | 6 +++---
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/src/app/features/project/overview/components/linked-resources/linked-resources.component.html b/src/app/features/project/overview/components/linked-resources/linked-resources.component.html
index c2d44da4c..68da00521 100644
--- a/src/app/features/project/overview/components/linked-resources/linked-resources.component.html
+++ b/src/app/features/project/overview/components/linked-resources/linked-resources.component.html
@@ -34,15 +34,20 @@
-
+
+
{{ 'common.labels.contributors' | translate }}:
@for (contributor of linkedResource.contributors; track contributor.id) {
}
+
+
diff --git a/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts b/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts
index bc3d43d62..9a3e1e76b 100644
--- a/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts
+++ b/src/app/features/project/overview/components/linked-resources/linked-resources.component.ts
@@ -31,9 +31,9 @@ export class LinkedResourcesComponent {
isCollectionsRoute = input(false);
canWrite = input.required();
- protected linkedResources = select(NodeLinksSelectors.getLinkedResources);
- protected isLinkedResourcesLoading = select(NodeLinksSelectors.getLinkedResourcesLoading);
- protected isMedium = toSignal(inject(IS_MEDIUM));
+ linkedResources = select(NodeLinksSelectors.getLinkedResources);
+ isLinkedResourcesLoading = select(NodeLinksSelectors.getLinkedResourcesLoading);
+ isMedium = toSignal(inject(IS_MEDIUM));
openLinkProjectModal() {
const dialogWidth = this.isMedium() ? '850px' : '95vw';
From 1b1f5b0dae1c0c145e22d76a47442c087ac664c3 Mon Sep 17 00:00:00 2001
From: Roman Nastyuk
Date: Fri, 5 Sep 2025 13:25:56 +0300
Subject: [PATCH 02/11] feat(linked-services): added link addons configuration
logic to the project settings
---
.husky/pre-push | 28 +-
.../files/pages/files/files.component.ts | 4 +-
src/app/features/files/store/files.model.ts | 4 +-
.../features/files/store/files.selectors.ts | 4 +-
.../project/addons/addons.component.ts | 130 +++++---
.../configure-addon.component.html | 18 +-
.../configure-addon.component.ts | 121 ++++----
.../connect-configured-addon.component.html | 14 +-
.../connect-configured-addon.component.ts | 115 +++++---
.../disconnect-addon-modal.component.html | 2 +-
.../disconnect-addon-modal.component.ts | 24 +-
.../settings/addons/addons.component.ts | 11 +-
.../addon-card-list.component.ts | 4 +-
.../addons/addon-card/addon-card.component.ts | 4 +-
.../folder-selector.component.ts | 169 -----------
src/app/shared/components/addons/index.ts | 2 +-
.../resource-type-info-dialog.component.html | 9 +
.../resource-type-info-dialog.component.scss | 0
...esource-type-info-dialog.component.spec.ts | 22 ++
.../resource-type-info-dialog.component.ts | 17 ++
.../google-file-picker.component.html | 0
.../google-file-picker.component.scss | 0
.../google-file-picker.component.spec.ts | 0
.../google-file-picker.component.ts | 0
...oogle-file-picker.download.service.spec.ts | 0
.../google-file-picker.download.service.ts | 0
.../storage-item-selector.component.html} | 28 +-
.../storage-item-selector.component.scss} | 0
.../storage-item-selector.component.spec.ts} | 14 +-
.../storage-item-selector.component.ts | 260 ++++++++++++++++
.../addons-category-options.const.ts | 4 +
.../constants/addons-tab-options.const.ts | 4 +
.../shared/enums/addon-service-names.enum.ts | 16 +
src/app/shared/enums/addon-tab.enum.ts | 1 +
src/app/shared/enums/addon-type.enum.ts | 17 ++
src/app/shared/enums/addons-category.enum.ts | 1 +
src/app/shared/enums/index.ts | 1 +
src/app/shared/helpers/addon-type.helper.ts | 65 ++--
src/app/shared/mappers/addon.mapper.ts | 43 +--
src/app/shared/models/addons/addons.models.ts | 174 +++--------
.../models/addons/configured-addon.model.ts | 15 +
.../addons/configured-storage-addon.model.ts | 52 ----
src/app/shared/models/addons/index.ts | 2 +-
.../addons/operation-invocation.models.ts | 16 +
.../services/addons/addon-dialog.service.ts | 4 +-
.../services/addons/addon-form.service.ts | 28 +-
.../addon-operation-invocation.service.ts | 4 +-
.../services/addons/addons.service.spec.ts | 4 +-
.../shared/services/addons/addons.service.ts | 41 +--
src/app/shared/services/files.service.ts | 10 +-
.../shared/stores/addons/addons.actions.ts | 39 +--
src/app/shared/stores/addons/addons.models.ts | 153 ++++++----
.../shared/stores/addons/addons.selectors.ts | 93 +++---
src/app/shared/stores/addons/addons.state.ts | 277 ++++++++----------
src/assets/i18n/en.json | 9 +-
src/assets/icons/addons/link_dataverse.svg | 30 ++
src/environments/environment.development.ts | 79 -----
src/environments/environment.ts | 63 ----
58 files changed, 1157 insertions(+), 1092 deletions(-)
delete mode 100644 src/app/shared/components/addons/folder-selector/folder-selector.component.ts
create mode 100644 src/app/shared/components/addons/resource-type-info-dialog/resource-type-info-dialog.component.html
create mode 100644 src/app/shared/components/addons/resource-type-info-dialog/resource-type-info-dialog.component.scss
create mode 100644 src/app/shared/components/addons/resource-type-info-dialog/resource-type-info-dialog.component.spec.ts
create mode 100644 src/app/shared/components/addons/resource-type-info-dialog/resource-type-info-dialog.component.ts
rename src/app/shared/components/addons/{folder-selector => storage-item-selector}/google-file-picker/google-file-picker.component.html (100%)
rename src/app/shared/components/addons/{folder-selector => storage-item-selector}/google-file-picker/google-file-picker.component.scss (100%)
rename src/app/shared/components/addons/{folder-selector => storage-item-selector}/google-file-picker/google-file-picker.component.spec.ts (100%)
rename src/app/shared/components/addons/{folder-selector => storage-item-selector}/google-file-picker/google-file-picker.component.ts (100%)
rename src/app/shared/components/addons/{folder-selector => storage-item-selector}/google-file-picker/service/google-file-picker.download.service.spec.ts (100%)
rename src/app/shared/components/addons/{folder-selector => storage-item-selector}/google-file-picker/service/google-file-picker.download.service.ts (100%)
rename src/app/shared/components/addons/{folder-selector/folder-selector.component.html => storage-item-selector/storage-item-selector.component.html} (79%)
rename src/app/shared/components/addons/{folder-selector/folder-selector.component.scss => storage-item-selector/storage-item-selector.component.scss} (100%)
rename src/app/shared/components/addons/{folder-selector/folder-selector.component.spec.ts => storage-item-selector/storage-item-selector.component.spec.ts} (88%)
create mode 100644 src/app/shared/components/addons/storage-item-selector/storage-item-selector.component.ts
create mode 100644 src/app/shared/enums/addon-service-names.enum.ts
create mode 100644 src/app/shared/enums/addon-type.enum.ts
create mode 100644 src/app/shared/models/addons/configured-addon.model.ts
delete mode 100644 src/app/shared/models/addons/configured-storage-addon.model.ts
create mode 100644 src/assets/icons/addons/link_dataverse.svg
diff --git a/.husky/pre-push b/.husky/pre-push
index 5a281490a..79d818ffd 100644
--- a/.husky/pre-push
+++ b/.husky/pre-push
@@ -1,19 +1,19 @@
# npm run build
-npm run test:coverage || {
- printf "\n\nERROR: Testing errors or coverage issues are found."
- printf "\n\nIn the future this will block your ability to push to github until it is resolved."
- printf "\n\nThe same pipeline runs via GitHub actions."
- printf "\n\nYou are seeing this error because code was added without test coverage."
+# npm run test:coverage || {
+# printf "\n\nERROR: Testing errors or coverage issues are found."
+# printf "\n\nIn the future this will block your ability to push to github until it is resolved."
+# printf "\n\nThe same pipeline runs via GitHub actions."
+# printf "\n\nYou are seeing this error because code was added without test coverage."
# printf "\n\n Please address them before proceeding.\n\n\n\n"
# exit 1
-}
-
-npm run test:check-coverage-thresholds || {
- printf "\n\nERROR: Coverage thresholds are not met."
- printf "\n\nIn the future this will block your ability to push to github until it is resolved."
- printf "\n\nThe same pipeline runs via GitHub actions."
- printf "\n\nYou are seeing this error because test coverage increased without updating the jest.config.js thresholds."
- #printf "\n\nPlease address them before proceeding.\n\n\n\n"
+# }
+#
+# npm run test:check-coverage-thresholds || {
+# printf "\n\nERROR: Coverage thresholds are not met."
+# printf "\n\nIn the future this will block your ability to push to github until it is resolved."
+# printf "\n\nThe same pipeline runs via GitHub actions."
+# printf "\n\nYou are seeing this error because test coverage increased without updating the jest.config.js thresholds."
+ # printf "\n\nPlease address them before proceeding.\n\n\n\n"
# exit 1
-}
+# }
diff --git a/src/app/features/files/pages/files/files.component.ts b/src/app/features/files/pages/files/files.component.ts
index e10087877..af734b33c 100644
--- a/src/app/features/files/pages/files/files.component.ts
+++ b/src/app/features/files/pages/files/files.component.ts
@@ -53,7 +53,7 @@ import {
SubHeaderComponent,
} from '@shared/components';
import { ViewOnlyLinkMessageComponent } from '@shared/components/view-only-link-message/view-only-link-message.component';
-import { ConfiguredStorageAddonModel, FilesTreeActions, OsfFile } from '@shared/models';
+import { ConfiguredAddonModel, FilesTreeActions, OsfFile } from '@shared/models';
import { FilesService } from '@shared/services';
import { CreateFolderDialogComponent, FileBrowserInfoComponent } from '../../components';
@@ -370,7 +370,7 @@ export class FilesComponent {
this.router.navigate([file.guid], { relativeTo: this.activeRoute });
}
- getAddonName(addons: ConfiguredStorageAddonModel[], provider: string): string {
+ getAddonName(addons: ConfiguredAddonModel[], provider: string): string {
if (provider === 'osfstorage') {
return 'Osf Storage';
} else {
diff --git a/src/app/features/files/store/files.model.ts b/src/app/features/files/store/files.model.ts
index 1d21983ff..fb41b3838 100644
--- a/src/app/features/files/store/files.model.ts
+++ b/src/app/features/files/store/files.model.ts
@@ -1,5 +1,5 @@
import { ContributorModel, OsfFile, ResourceMetadata } from '@shared/models';
-import { ConfiguredStorageAddonModel } from '@shared/models/addons';
+import { ConfiguredAddonModel } from '@shared/models/addons';
import { AsyncStateModel } from '@shared/models/store';
import { FileProvider } from '../constants';
@@ -20,7 +20,7 @@ export interface FilesStateModel {
fileRevisions: AsyncStateModel;
tags: AsyncStateModel;
rootFolders: AsyncStateModel;
- configuredStorageAddons: AsyncStateModel;
+ configuredStorageAddons: AsyncStateModel;
isAnonymous: boolean;
}
diff --git a/src/app/features/files/store/files.selectors.ts b/src/app/features/files/store/files.selectors.ts
index b7f8b8f47..ec27d9f2a 100644
--- a/src/app/features/files/store/files.selectors.ts
+++ b/src/app/features/files/store/files.selectors.ts
@@ -1,6 +1,6 @@
import { Selector } from '@ngxs/store';
-import { ConfiguredStorageAddonModel, ContributorModel, OsfFile, ResourceMetadata } from '@shared/models';
+import { ConfiguredAddonModel, ContributorModel, OsfFile, ResourceMetadata } from '@shared/models';
import { OsfFileCustomMetadata, OsfFileRevision } from '../models';
@@ -119,7 +119,7 @@ export class FilesSelectors {
}
@Selector([FilesState])
- static getConfiguredStorageAddons(state: FilesStateModel): ConfiguredStorageAddonModel[] | null {
+ static getConfiguredStorageAddons(state: FilesStateModel): ConfiguredAddonModel[] | null {
return state.configuredStorageAddons.data;
}
diff --git a/src/app/features/project/addons/addons.component.ts b/src/app/features/project/addons/addons.component.ts
index 0b6606d1d..43656c346 100644
--- a/src/app/features/project/addons/addons.component.ts
+++ b/src/app/features/project/addons/addons.component.ts
@@ -33,7 +33,9 @@ import {
GetAddonsUserReference,
GetCitationAddons,
GetConfiguredCitationAddons,
+ GetConfiguredLinkAddons,
GetConfiguredStorageAddons,
+ GetLinkAddons,
GetStorageAddons,
} from '@shared/stores/addons';
@@ -60,84 +62,126 @@ import {
export class AddonsComponent implements OnInit {
private route = inject(ActivatedRoute);
private destroyRef = inject(DestroyRef);
- protected readonly tabOptions = ADDON_TAB_OPTIONS;
- protected readonly categoryOptions = ADDON_CATEGORY_OPTIONS;
- protected readonly AddonTabValue = AddonTabValue;
- protected readonly defaultTabValue = AddonTabValue.ALL_ADDONS;
- protected searchControl = new FormControl('');
- protected searchValue = signal('');
- protected selectedCategory = signal(AddonCategory.EXTERNAL_STORAGE_SERVICES);
- protected selectedTab = signal(this.defaultTabValue);
-
- protected currentUser = select(UserSelectors.getCurrentUser);
- protected addonsResourceReference = select(AddonsSelectors.getAddonsResourceReference);
- protected addonsUserReference = select(AddonsSelectors.getAddonsUserReference);
- protected storageAddons = select(AddonsSelectors.getStorageAddons);
- protected citationAddons = select(AddonsSelectors.getCitationAddons);
- protected configuredStorageAddons = select(AddonsSelectors.getConfiguredStorageAddons);
- protected configuredCitationAddons = select(AddonsSelectors.getConfiguredCitationAddons);
-
- protected isCurrentUserLoading = select(UserSelectors.getCurrentUserLoading);
- protected isUserReferenceLoading = select(AddonsSelectors.getAddonsUserReferenceLoading);
- protected isResourceReferenceLoading = select(AddonsSelectors.getAddonsResourceReferenceLoading);
- protected isStorageAddonsLoading = select(AddonsSelectors.getStorageAddonsLoading);
- protected isCitationAddonsLoading = select(AddonsSelectors.getCitationAddonsLoading);
- protected isConfiguredStorageAddonsLoading = select(AddonsSelectors.getConfiguredStorageAddonsLoading);
- protected isConfiguredCitationAddonsLoading = select(AddonsSelectors.getConfiguredCitationAddonsLoading);
- protected isAddonsLoading = computed(() => {
+ readonly tabOptions = ADDON_TAB_OPTIONS;
+ readonly categoryOptions = ADDON_CATEGORY_OPTIONS;
+ readonly AddonTabValue = AddonTabValue;
+ readonly defaultTabValue = AddonTabValue.ALL_ADDONS;
+ searchControl = new FormControl('');
+ searchValue = signal('');
+ selectedCategory = signal(AddonCategory.EXTERNAL_STORAGE_SERVICES);
+ selectedTab = signal(this.defaultTabValue);
+
+ currentUser = select(UserSelectors.getCurrentUser);
+ addonsResourceReference = select(AddonsSelectors.getAddonsResourceReference);
+ addonsUserReference = select(AddonsSelectors.getAddonsUserReference);
+ storageAddons = select(AddonsSelectors.getStorageAddons);
+ citationAddons = select(AddonsSelectors.getCitationAddons);
+ linkAddons = select(AddonsSelectors.getLinkAddons);
+ configuredStorageAddons = select(AddonsSelectors.getConfiguredStorageAddons);
+ configuredCitationAddons = select(AddonsSelectors.getConfiguredCitationAddons);
+ configuredLinkAddons = select(AddonsSelectors.getConfiguredLinkAddons);
+
+ isCurrentUserLoading = select(UserSelectors.getCurrentUserLoading);
+ isUserReferenceLoading = select(AddonsSelectors.getAddonsUserReferenceLoading);
+ isResourceReferenceLoading = select(AddonsSelectors.getAddonsResourceReferenceLoading);
+ isStorageAddonsLoading = select(AddonsSelectors.getStorageAddonsLoading);
+ isCitationAddonsLoading = select(AddonsSelectors.getCitationAddonsLoading);
+ isConfiguredStorageAddonsLoading = select(AddonsSelectors.getConfiguredStorageAddonsLoading);
+ isConfiguredCitationAddonsLoading = select(AddonsSelectors.getConfiguredCitationAddonsLoading);
+ isConfiguredLinkAddonsLoading = select(AddonsSelectors.getConfiguredLinkAddonsLoading);
+ isAddonsLoading = computed(() => {
return (
this.isStorageAddonsLoading() ||
this.isCitationAddonsLoading() ||
+ this.isLinkAddonsLoading() ||
this.isUserReferenceLoading() ||
this.isCurrentUserLoading()
);
});
- protected isConfiguredAddonsLoading = computed(() => {
+ isConfiguredAddonsLoading = computed(() => {
return (
this.isConfiguredStorageAddonsLoading() ||
this.isConfiguredCitationAddonsLoading() ||
+ this.isConfiguredLinkAddonsLoading() ||
this.isResourceReferenceLoading() ||
this.isCurrentUserLoading()
);
});
- protected actions = createDispatchMap({
+ isLinkAddonsLoading = select(AddonsSelectors.getLinkAddonsLoading);
+
+ currentAddonsLoading = computed(() => {
+ switch (this.selectedCategory()) {
+ case AddonCategory.EXTERNAL_STORAGE_SERVICES:
+ return this.isStorageAddonsLoading();
+ case AddonCategory.EXTERNAL_CITATION_SERVICES:
+ return this.isCitationAddonsLoading();
+ case AddonCategory.EXTERNAL_LINK_SERVICES:
+ return this.isLinkAddonsLoading();
+ default:
+ return this.isStorageAddonsLoading();
+ }
+ });
+
+ actions = createDispatchMap({
getStorageAddons: GetStorageAddons,
getCitationAddons: GetCitationAddons,
+ getLinkAddons: GetLinkAddons,
getConfiguredStorageAddons: GetConfiguredStorageAddons,
getConfiguredCitationAddons: GetConfiguredCitationAddons,
+ getConfiguredLinkAddons: GetConfiguredLinkAddons,
getAddonsUserReference: GetAddonsUserReference,
getAddonsResourceReference: GetAddonsResourceReference,
deleteAuthorizedAddon: DeleteAuthorizedAddon,
clearConfiguredAddons: ClearConfiguredAddons,
});
- protected readonly userReferenceId = computed(() => {
+ readonly userReferenceId = computed(() => {
return this.addonsUserReference()[0]?.id;
});
- protected allConfiguredAddons = computed(() => {
- const authorizedAddons = [...this.configuredStorageAddons(), ...this.configuredCitationAddons()];
+ allConfiguredAddons = computed(() => {
+ const authorizedAddons = [
+ ...this.configuredStorageAddons(),
+ ...this.configuredCitationAddons(),
+ ...this.configuredLinkAddons(),
+ ];
const searchValue = this.searchValue().toLowerCase();
return authorizedAddons.filter((card) => card.displayName.toLowerCase().includes(searchValue));
});
- protected resourceReferenceId = computed(() => {
+ resourceReferenceId = computed(() => {
return this.addonsResourceReference()[0]?.id;
});
- protected currentAction = computed(() =>
- this.selectedCategory() === AddonCategory.EXTERNAL_STORAGE_SERVICES
- ? this.actions.getStorageAddons
- : this.actions.getCitationAddons
- );
+ currentAction = computed(() => {
+ switch (this.selectedCategory()) {
+ case AddonCategory.EXTERNAL_STORAGE_SERVICES:
+ return this.actions.getStorageAddons;
+ case AddonCategory.EXTERNAL_CITATION_SERVICES:
+ return this.actions.getCitationAddons;
+ case AddonCategory.EXTERNAL_LINK_SERVICES:
+ return this.actions.getLinkAddons;
+ default:
+ return this.actions.getStorageAddons;
+ }
+ });
- protected currentAddonsState = computed(() =>
- this.selectedCategory() === AddonCategory.EXTERNAL_STORAGE_SERVICES ? this.storageAddons() : this.citationAddons()
- );
+ currentAddonsState = computed(() => {
+ switch (this.selectedCategory()) {
+ case AddonCategory.EXTERNAL_STORAGE_SERVICES:
+ return this.storageAddons();
+ case AddonCategory.EXTERNAL_CITATION_SERVICES:
+ return this.citationAddons();
+ case AddonCategory.EXTERNAL_LINK_SERVICES:
+ return this.linkAddons();
+ default:
+ return this.storageAddons();
+ }
+ });
- protected filteredAddonCards = computed(() => {
+ filteredAddonCards = computed(() => {
const searchValue = this.searchValue().toLowerCase();
return this.currentAddonsState().filter(
(card) =>
@@ -146,7 +190,7 @@ export class AddonsComponent implements OnInit {
);
});
- protected onCategoryChange(value: Primitive): void {
+ onCategoryChange(value: Primitive): void {
if (typeof value === 'string') {
this.selectedCategory.set(value);
}
@@ -163,8 +207,9 @@ export class AddonsComponent implements OnInit {
if (this.currentUser()) {
const action = this.currentAction();
const addons = this.currentAddonsState();
+ const isLoading = this.currentAddonsLoading();
- if (!addons?.length) {
+ if (!addons?.length && !isLoading) {
action();
}
}
@@ -199,5 +244,6 @@ export class AddonsComponent implements OnInit {
private fetchAllConfiguredAddons(resourceReferenceId: string): void {
this.actions.getConfiguredStorageAddons(resourceReferenceId);
this.actions.getConfiguredCitationAddons(resourceReferenceId);
+ this.actions.getConfiguredLinkAddons(resourceReferenceId);
}
}
diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.html b/src/app/features/project/addons/components/configure-addon/configure-addon.component.html
index ab72be55c..24cf9f229 100644
--- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.html
+++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.html
@@ -5,7 +5,7 @@
- {{ 'settings.addons.connectAddon.configure' | translate }} {{ addon()?.externalServiceName }}
+ {{ 'settings.addons.connectAddon.configure' | translate }} {{ addonServiceName() }}
>
- @if (selectedFolder()) {
+ @if (selectedStorageItem()) {
@@ -41,8 +41,8 @@
- {{ 'settings.addons.configureAddon.selectedFolder' | translate }}
- {{ selectedFolder()?.itemName }}
+ {{ selectedItemLabel() | translate }}
+ {{ selectedStorageItem()?.itemName }}
@@ -54,7 +54,7 @@
- {{ 'settings.addons.connectAddon.configure' | translate }} {{ addon()?.externalServiceName }}
+ {{ 'settings.addons.connectAddon.configure' | translate }} {{ addonServiceName() }}
(keydown.enter)="toggleEditMode()"
>
-
diff --git a/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts b/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts
index 20d622702..0d4aa8a11 100644
--- a/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts
+++ b/src/app/features/project/addons/components/configure-addon/configure-addon.component.ts
@@ -24,13 +24,16 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { OperationNames } from '@osf/features/project/addons/enums';
import { getAddonTypeString } from '@osf/shared/helpers';
import { SubHeaderComponent } from '@shared/components';
-import { FolderSelectorComponent } from '@shared/components/addons/folder-selector/folder-selector.component';
-import { AddonModel, ConfiguredStorageAddonModel } from '@shared/models';
+import { StorageItemSelectorComponent } from '@shared/components/addons/storage-item-selector/storage-item-selector.component';
+import { AddonType } from '@shared/enums';
+import { AddonServiceNames } from '@shared/enums/addon-service-names.enum';
+import { AddonModel, ConfiguredAddonModel } from '@shared/models';
import { AddonDialogService, AddonFormService, AddonOperationInvocationService, ToastService } from '@shared/services';
import {
AddonsSelectors,
ClearOperationInvocations,
CreateAddonOperationInvocation,
+ GetLinkAddons,
UpdateConfiguredAddon,
} from '@shared/stores/addons';
@@ -48,7 +51,7 @@ import { environment } from 'src/environments/environment';
FormsModule,
Skeleton,
BreadcrumbModule,
- FolderSelectorComponent,
+ StorageItemSelectorComponent,
],
templateUrl: './configure-addon.component.html',
styleUrl: './configure-addon.component.scss',
@@ -63,36 +66,25 @@ export class ConfigureAddonComponent implements OnInit {
private addonDialogService = inject(AddonDialogService);
private addonFormService = inject(AddonFormService);
private operationInvocationService = inject(AddonOperationInvocationService);
- /**
- * Injected NGXS store used to access and dispatch state actions and selectors.
- */
private store = inject(Store);
-
- /**
- * Form control for capturing or displaying the user’s selected account name.
- */
- public accountNameControl = new FormControl('');
- /**
- * Signal representing the currently selected `Addon` from the list of available storage addons.
- * This value updates reactively as the selection changes.
- */
- public storageAddon = signal(null);
- /**
- * Signal representing the currently selected and configured storage addon model.
- * This may be `null` if no addon has been configured.
- */
- public addon = signal(null);
-
- public readonly isGoogleDrive = computed(() => {
+ accountNameControl = new FormControl('');
+ storageAddon = signal(null);
+ addon = signal(null);
+ readonly isGoogleDrive = computed(() => {
return this.storageAddon()?.wbKey === 'googledrive';
});
-
- protected isEditMode = signal(false);
- public selectedRootFolderId = signal('');
- protected addonsUserReference = select(AddonsSelectors.getAddonsUserReference);
- public operationInvocation = select(AddonsSelectors.getOperationInvocation);
- protected selectedFolderOperationInvocation = select(AddonsSelectors.getSelectedFolderOperationInvocation);
- protected selectedFolder = select(AddonsSelectors.getSelectedFolder);
+ isEditMode = signal(false);
+ selectedStorageItemId = signal('');
+ selectedStorageItemUrl = signal('');
+ selectedResourceType = signal('');
+ addonsUserReference = select(AddonsSelectors.getAddonsUserReference);
+ operationInvocation = select(AddonsSelectors.getOperationInvocation);
+ linkAddons = select(AddonsSelectors.getLinkAddons);
+ selectedStorageItem = select(AddonsSelectors.getSelectedStorageItem);
+
+ addonServiceName = computed(() => {
+ return AddonServiceNames[this.addon()?.externalServiceName as keyof typeof AddonServiceNames];
+ });
readonly baseUrl = computed(() => {
const currentUrl = this.router.url;
@@ -105,10 +97,24 @@ export class ConfigureAddonComponent implements OnInit {
readonly addonTypeString = computed(() => {
return getAddonTypeString(this.addon());
});
- protected readonly actions = createDispatchMap({
+ readonly selectedItemLabel = computed(() => {
+ const addonType = this.addonTypeString();
+ return addonType === AddonType.LINK
+ ? 'settings.addons.configureAddon.linkedItem'
+ : 'settings.addons.configureAddon.selectedFolder';
+ });
+ readonly supportedResourceTypes = computed(() => {
+ if (this.linkAddons().length && this.addonTypeString() === AddonType.LINK) {
+ const addon = this.linkAddons().find((a) => this.addon()?.externalServiceName === a.externalServiceName);
+ return addon?.supportedResourceTypes || [];
+ }
+ return [];
+ });
+ readonly actions = createDispatchMap({
createAddonOperationInvocation: CreateAddonOperationInvocation,
updateConfiguredAddon: UpdateConfiguredAddon,
clearOperationInvocations: ClearOperationInvocations,
+ getLinkAddons: GetLinkAddons,
});
constructor() {
@@ -119,12 +125,18 @@ export class ConfigureAddonComponent implements OnInit {
this.actions.clearOperationInvocations();
});
});
+
+ effect(() => {
+ if (this.addonTypeString() === AddonType.LINK) {
+ this.actions.getLinkAddons();
+ }
+ });
}
private initializeAddon(): void {
// TODO this should be reviewed to have the addon be retrieved from the store
// I have limited my testing because it will create a false/positive test based on the required data
- const addon = this.router.getCurrentNavigation()?.extras.state?.['addon'] as ConfiguredStorageAddonModel;
+ const addon = this.router.getCurrentNavigation()?.extras.state?.['addon'] as ConfiguredAddonModel;
if (addon) {
this.storageAddon.set(
@@ -132,14 +144,16 @@ export class ConfigureAddonComponent implements OnInit {
);
this.addon.set(addon);
- this.selectedRootFolderId.set(addon.selectedFolderId);
+ this.selectedStorageItemId.set(addon.selectedStorageItemId);
+ this.selectedStorageItemUrl.set(addon.targetUrl || '');
+ this.selectedResourceType.set(addon.resourceType || '');
this.accountNameControl.setValue(addon.displayName);
} else {
this.router.navigate([`${this.baseUrl()}/addons`]);
}
}
- protected handleCreateOperationInvocation(operationName: OperationNames, folderId: string): void {
+ handleCreateOperationInvocation(operationName: OperationNames, folderId: string): void {
const addon = this.addon();
if (!addon) return;
@@ -149,17 +163,17 @@ export class ConfigureAddonComponent implements OnInit {
}
ngOnInit(): void {
- this.handleCreateOperationInvocation(OperationNames.GET_ITEM_INFO, this.selectedRootFolderId());
+ this.handleCreateOperationInvocation(OperationNames.GET_ITEM_INFO, this.selectedStorageItemId());
}
- protected handleDisconnectAccount(): void {
+ handleDisconnectAccount(): void {
const currentAddon = this.addon();
if (!currentAddon) return;
this.openDisconnectDialog(currentAddon);
}
- private openDisconnectDialog(addon: ConfiguredStorageAddonModel): void {
+ private openDisconnectDialog(addon: ConfiguredAddonModel): void {
const dialogRef = this.addonDialogService.openDisconnectDialog(addon);
dialogRef.subscribe((result) => {
@@ -172,19 +186,16 @@ export class ConfigureAddonComponent implements OnInit {
});
}
- protected toggleEditMode(): void {
- const operationResult = this.selectedFolderOperationInvocation()?.operationResult[0];
- const hasRootCandidates = operationResult?.mayContainRootCandidates ?? false;
- const itemId = operationResult?.itemId || '/';
+ toggleEditMode(): void {
+ if (!this.isEditMode()) {
+ this.resetConfigurationForm();
+ }
- this.handleCreateOperationInvocation(
- hasRootCandidates ? OperationNames.LIST_CHILD_ITEMS : OperationNames.GET_ITEM_INFO,
- itemId
- );
+ this.handleCreateOperationInvocation(OperationNames.LIST_ROOT_ITEMS, this.selectedStorageItemId());
this.isEditMode.set(!this.isEditMode());
}
- protected handleUpdateAddonConfiguration(): void {
+ handleUpdateAddonConfiguration(): void {
const currentAddon = this.addon();
if (!currentAddon) return;
@@ -193,17 +204,29 @@ export class ConfigureAddonComponent implements OnInit {
this.addonsUserReference()[0].id || '',
this.resourceUri(),
this.accountNameControl.value || '',
- this.selectedRootFolderId() || '',
- this.addonTypeString()
+ this.selectedStorageItemId() || '',
+ this.addonTypeString(),
+ this.selectedResourceType(),
+ this.selectedStorageItemUrl()
);
this.actions.updateConfiguredAddon(payload, this.addonTypeString(), currentAddon.id).subscribe({
complete: () => {
this.router.navigate([`${this.baseUrl()}/addons`]);
this.toastService.showSuccess('settings.addons.toast.updateSuccess', {
- addonName: currentAddon.externalServiceName,
+ addonName: this.addonServiceName(),
});
},
});
}
+
+ private resetConfigurationForm(): void {
+ this.selectedStorageItemId.set('');
+ this.selectedStorageItemUrl.set('');
+ const currentAddon = this.addon();
+ if (currentAddon) {
+ this.selectedResourceType.set(currentAddon.resourceType || '');
+ this.accountNameControl.setValue(currentAddon.displayName);
+ }
+ }
}
diff --git a/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.html b/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.html
index a44822703..594e8eea4 100644
--- a/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.html
+++ b/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.html
@@ -32,12 +32,12 @@
>
@@ -113,16 +113,20 @@ {{ 'settings.addons.connectAddon.chooseExistingAccount' | trans
{{ 'settings.addons.connectAddon.configure' | translate }} {{ addon()?.displayName }}
-
diff --git a/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.ts b/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.ts
index 3996f97e8..8dab8d789 100644
--- a/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.ts
+++ b/src/app/features/project/addons/components/connect-configured-addon/connect-configured-addon.component.ts
@@ -22,8 +22,9 @@ import { AuthorizedAccountModel } from '@osf/shared/models/addons/authorized-acc
import {
AddonSetupAccountFormComponent,
AddonTermsComponent,
- FolderSelectorComponent,
+ StorageItemSelectorComponent,
} from '@shared/components/addons';
+import { AddonType } from '@shared/enums';
import { AddonModel, AddonTerm, AuthorizedAddonRequestJsonApi } from '@shared/models';
import { AddonDialogService, AddonFormService, AddonOperationInvocationService, ToastService } from '@shared/services';
import {
@@ -32,6 +33,7 @@ import {
CreateAuthorizedAddon,
CreateConfiguredAddon,
GetAuthorizedCitationAddons,
+ GetAuthorizedLinkAddons,
GetAuthorizedStorageAddons,
UpdateAuthorizedAddon,
UpdateConfiguredAddon,
@@ -53,7 +55,7 @@ import { environment } from 'src/environments/environment';
ReactiveFormsModule,
TranslatePipe,
RadioButtonModule,
- FolderSelectorComponent,
+ StorageItemSelectorComponent,
AddonTermsComponent,
AddonSetupAccountFormComponent,
DialogModule,
@@ -71,31 +73,43 @@ export class ConnectConfiguredAddonComponent {
private operationInvocationService = inject(AddonOperationInvocationService);
private router = inject(Router);
private route = inject(ActivatedRoute);
- protected readonly AddonStepperValue = ProjectAddonsStepperValue;
- protected readonly stepper = viewChild(Stepper);
- protected accountNameControl = new FormControl('');
- protected terms = signal([]);
- protected addon = signal(null);
- protected addonAuthUrl = signal('/settings/addons');
- protected currentAuthorizedAddonAccounts = signal([]);
- protected chosenAccountId = signal('');
- protected chosenAccountName = signal('');
- protected selectedRootFolderId = signal('');
-
- protected addonsUserReference = select(AddonsSelectors.getAddonsUserReference);
- protected createdAuthorizedAddon = select(AddonsSelectors.getCreatedOrUpdatedAuthorizedAddon);
- protected createdConfiguredAddon = select(AddonsSelectors.getCreatedOrUpdatedConfiguredAddon);
- protected authorizedStorageAddons = select(AddonsSelectors.getAuthorizedStorageAddons);
- protected authorizedCitationAddons = select(AddonsSelectors.getAuthorizedCitationAddons);
- protected operationInvocation = select(AddonsSelectors.getOperationInvocation);
-
- protected isAuthorizedStorageAddonsLoading = select(AddonsSelectors.getAuthorizedStorageAddonsLoading);
- protected isAuthorizedCitationAddonsLoading = select(AddonsSelectors.getAuthorizedCitationAddonsLoading);
- protected isCreatingAuthorizedAddon = select(AddonsSelectors.getCreatedOrUpdatedStorageAddonSubmitting);
-
- protected actions = createDispatchMap({
+ readonly AddonStepperValue = ProjectAddonsStepperValue;
+ readonly stepper = viewChild(Stepper);
+ accountNameControl = new FormControl('');
+ terms = signal([]);
+ addon = signal(null);
+ addonAuthUrl = signal('/settings/addons');
+ currentAuthorizedAddonAccounts = signal([]);
+ chosenAccountId = signal('');
+ chosenAccountName = signal('');
+ selectedStorageItemId = signal('');
+ selectedStorageItemUrl = signal('');
+ selectedResourceType = signal('');
+
+ addonsUserReference = select(AddonsSelectors.getAddonsUserReference);
+ createdAuthorizedAddon = select(AddonsSelectors.getCreatedOrUpdatedAuthorizedAddon);
+ createdConfiguredAddon = select(AddonsSelectors.getCreatedOrUpdatedConfiguredAddon);
+ authorizedStorageAddons = select(AddonsSelectors.getAuthorizedStorageAddons);
+ authorizedCitationAddons = select(AddonsSelectors.getAuthorizedCitationAddons);
+ authorizedLinkAddons = select(AddonsSelectors.getAuthorizedLinkAddons);
+ operationInvocation = select(AddonsSelectors.getOperationInvocation);
+
+ isAuthorizedStorageAddonsLoading = select(AddonsSelectors.getAuthorizedStorageAddonsLoading);
+ isAuthorizedCitationAddonsLoading = select(AddonsSelectors.getAuthorizedCitationAddonsLoading);
+ isAuthorizedLinkAddonsLoading = select(AddonsSelectors.getAuthorizedLinkAddonsLoading);
+ isAuthorizedAddonsLoading = computed(() => {
+ return (
+ this.isAuthorizedStorageAddonsLoading() ||
+ this.isAuthorizedCitationAddonsLoading() ||
+ this.isAuthorizedLinkAddonsLoading()
+ );
+ });
+ isCreatingAuthorizedAddon = select(AddonsSelectors.getCreatedOrUpdatedStorageAddonSubmitting);
+
+ actions = createDispatchMap({
getAuthorizedStorageAddons: GetAuthorizedStorageAddons,
getAuthorizedCitationAddons: GetAuthorizedCitationAddons,
+ getAuthorizedLinkAddons: GetAuthorizedLinkAddons,
createAuthorizedAddon: CreateAuthorizedAddon,
createConfiguredAddon: CreateConfiguredAddon,
updateConfiguredAddon: UpdateConfiguredAddon,
@@ -103,30 +117,35 @@ export class ConnectConfiguredAddonComponent {
createAddonOperationInvocation: CreateAddonOperationInvocation,
});
- protected readonly userReferenceId = computed(() => {
+ readonly userReferenceId = computed(() => {
return this.addonsUserReference()[0]?.id;
});
- protected loginOrChooseAccountText = computed(() => {
+ loginOrChooseAccountText = computed(() => {
return this.translateService.instant('settings.addons.connectAddon.loginToOrSelectAccount', {
addonName: this.addon()?.displayName,
});
});
- protected resourceUri = computed(() => {
+ resourceUri = computed(() => {
const id = this.route.parent?.parent?.snapshot.params['id'];
return `${environment.webUrl}/${id}`;
});
- protected addonTypeString = computed(() => {
+ addonTypeString = computed(() => {
return getAddonTypeString(this.addon());
});
- protected readonly baseUrl = computed(() => {
+ readonly baseUrl = computed(() => {
const currentUrl = this.router.url;
return currentUrl.split('/addons')[0];
});
+ readonly supportedResourceTypes = computed(() => {
+ const addon = this.addon();
+ return addon && 'supportedResourceTypes' in addon ? addon.supportedResourceTypes || [] : [];
+ });
+
constructor() {
const addon = this.router.getCurrentNavigation()?.extras.state?.['addon'] as AddonModel | AuthorizedAccountModel;
if (!addon) {
@@ -135,7 +154,7 @@ export class ConnectConfiguredAddonComponent {
this.addon.set(addon);
}
- protected handleCreateConfiguredAddon() {
+ handleCreateConfiguredAddon() {
const addon = this.addon();
const selectedAccount = this.currentAuthorizedAddonAccounts().find(
(account) => account.id === this.chosenAccountId()
@@ -148,8 +167,10 @@ export class ConnectConfiguredAddonComponent {
this.userReferenceId(),
this.resourceUri(),
this.accountNameControl.value || '',
- this.selectedRootFolderId(),
- this.addonTypeString()
+ this.selectedStorageItemId(),
+ this.addonTypeString(),
+ this.selectedResourceType(),
+ this.selectedStorageItemUrl()
);
this.actions.createConfiguredAddon(payload, this.addonTypeString()).subscribe({
@@ -165,7 +186,7 @@ export class ConnectConfiguredAddonComponent {
});
}
- protected handleCreateAuthorizedAddon(payload: AuthorizedAddonRequestJsonApi): void {
+ handleCreateAuthorizedAddon(payload: AuthorizedAddonRequestJsonApi): void {
if (!this.addon()) return;
this.actions.createAuthorizedAddon(payload, this.addonTypeString()).subscribe({
@@ -180,7 +201,7 @@ export class ConnectConfiguredAddonComponent {
});
}
- protected handleConfirmAccountConnection(): void {
+ handleConfirmAccountConnection(): void {
const selectedAccount = this.currentAuthorizedAddonAccounts().find(
(account) => account.id === this.chosenAccountId()
);
@@ -194,11 +215,12 @@ export class ConnectConfiguredAddonComponent {
this.stepper()?.value.set(ProjectAddonsStepperValue.CONFIGURE_ROOT_FOLDER);
this.chosenAccountName.set(selectedAccount.displayName);
this.accountNameControl.setValue(selectedAccount.displayName);
+ this.resetConfigurationForm();
}
});
}
- protected handleAuthorizedAccountsPresenceCheck() {
+ handleAuthorizedAccountsPresenceCheck() {
const requiredData = this.getDataForAccountCheck();
if (!requiredData) return;
@@ -228,14 +250,18 @@ export class ConnectConfiguredAddonComponent {
private getAddonConfig(addonType: string, referenceId: string) {
const addonConfigMap: AddonConfigMap = {
- storage: {
+ [AddonType.STORAGE]: {
getAddons: () => this.actions.getAuthorizedStorageAddons(referenceId),
getAuthorizedAddons: () => this.authorizedStorageAddons(),
},
- citation: {
+ [AddonType.CITATION]: {
getAddons: () => this.actions.getAuthorizedCitationAddons(referenceId),
getAuthorizedAddons: () => this.authorizedCitationAddons(),
},
+ [AddonType.LINK]: {
+ getAddons: () => this.actions.getAuthorizedLinkAddons(referenceId),
+ getAuthorizedAddons: () => this.authorizedLinkAddons(),
+ },
};
return addonConfigMap[addonType] || null;
@@ -267,7 +293,7 @@ export class ConnectConfiguredAddonComponent {
return authorizedAddons.filter((addon) => addon.externalServiceName === currentAddon.externalServiceName);
}
- protected handleCreateOperationInvocation(operationName: OperationNames, itemId: string): void {
+ handleCreateOperationInvocation(operationName: OperationNames, itemId: string): void {
const selectedAccount = this.currentAuthorizedAddonAccounts().find(
(account) => account.id === this.chosenAccountId()
);
@@ -282,4 +308,15 @@ export class ConnectConfiguredAddonComponent {
this.actions.createAddonOperationInvocation(payload);
}
+
+ handleNavigateToAccountSelection(): void {
+ this.resetConfigurationForm();
+ this.stepper()?.value.set(ProjectAddonsStepperValue.CHOOSE_ACCOUNT);
+ }
+
+ private resetConfigurationForm(): void {
+ this.selectedStorageItemId.set('');
+ this.selectedStorageItemUrl.set('');
+ this.selectedResourceType.set('');
+ }
}
diff --git a/src/app/features/project/addons/components/disconnect-addon-modal/disconnect-addon-modal.component.html b/src/app/features/project/addons/components/disconnect-addon-modal/disconnect-addon-modal.component.html
index e665b6e3d..e4447ba44 100644
--- a/src/app/features/project/addons/components/disconnect-addon-modal/disconnect-addon-modal.component.html
+++ b/src/app/features/project/addons/components/disconnect-addon-modal/disconnect-addon-modal.component.html
@@ -4,7 +4,7 @@
{{ 'settings.addons.configureAddon.account' | translate }} {{ addon.displayName }}
- {{ 'settings.addons.configureAddon.selectedFolder' | translate }}
+ {{ selectedItemLabel() | translate }}
{{ selectedFolder()?.itemName }}
diff --git a/src/app/features/project/addons/components/disconnect-addon-modal/disconnect-addon-modal.component.ts b/src/app/features/project/addons/components/disconnect-addon-modal/disconnect-addon-modal.component.ts
index 2821f7432..ad0cc5ead 100644
--- a/src/app/features/project/addons/components/disconnect-addon-modal/disconnect-addon-modal.component.ts
+++ b/src/app/features/project/addons/components/disconnect-addon-modal/disconnect-addon-modal.component.ts
@@ -5,8 +5,10 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Button } from 'primeng/button';
import { DynamicDialogConfig, DynamicDialogRef } from 'primeng/dynamicdialog';
-import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
+import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
+import { getAddonTypeString } from '@osf/shared/helpers';
+import { AddonType } from '@shared/enums';
import { AddonsSelectors, DeleteConfiguredAddon } from '@shared/stores/addons';
@Component({
@@ -18,16 +20,22 @@ import { AddonsSelectors, DeleteConfiguredAddon } from '@shared/stores/addons';
})
export class DisconnectAddonModalComponent {
private dialogConfig = inject(DynamicDialogConfig);
- protected dialogRef = inject(DynamicDialogRef);
- protected addon = this.dialogConfig.data.addon;
- protected dialogMessage = this.dialogConfig.data.message || '';
- protected isSubmitting = select(AddonsSelectors.getDeleteStorageAddonSubmitting);
- protected selectedFolder = select(AddonsSelectors.getSelectedFolder);
- protected actions = createDispatchMap({
+ dialogRef = inject(DynamicDialogRef);
+ addon = this.dialogConfig.data.addon;
+ dialogMessage = this.dialogConfig.data.message || '';
+ isSubmitting = select(AddonsSelectors.getDeleteStorageAddonSubmitting);
+ selectedFolder = select(AddonsSelectors.getSelectedStorageItem);
+ selectedItemLabel = computed(() => {
+ const addonType = getAddonTypeString(this.addon);
+ return addonType === AddonType.LINK
+ ? 'settings.addons.configureAddon.linkedItem'
+ : 'settings.addons.configureAddon.selectedFolder';
+ });
+ actions = createDispatchMap({
deleteConfiguredAddon: DeleteConfiguredAddon,
});
- protected handleDisconnectAddonAccount(): void {
+ handleDisconnectAddonAccount(): void {
if (!this.addon) return;
this.actions.deleteConfiguredAddon(this.addon.id, this.addon.type).subscribe({
diff --git a/src/app/features/settings/addons/addons.component.ts b/src/app/features/settings/addons/addons.component.ts
index 2b47a827b..0b907f1e8 100644
--- a/src/app/features/settings/addons/addons.component.ts
+++ b/src/app/features/settings/addons/addons.component.ts
@@ -71,18 +71,22 @@ export class AddonsComponent {
protected citationAddons = select(AddonsSelectors.getCitationAddons);
protected authorizedStorageAddons = select(AddonsSelectors.getAuthorizedStorageAddons);
protected authorizedCitationAddons = select(AddonsSelectors.getAuthorizedCitationAddons);
+ protected authorizedLinkAddons = select(AddonsSelectors.getAuthorizedLinkAddons);
protected isCurrentUserLoading = select(UserSelectors.getCurrentUserLoading);
protected isUserReferenceLoading = select(AddonsSelectors.getAddonsUserReferenceLoading);
protected isStorageAddonsLoading = select(AddonsSelectors.getStorageAddonsLoading);
protected isCitationAddonsLoading = select(AddonsSelectors.getCitationAddonsLoading);
+ protected isLinkAddonsLoading = select(AddonsSelectors.getLinkAddonsLoading);
protected isAuthorizedStorageAddonsLoading = select(AddonsSelectors.getAuthorizedStorageAddonsLoading);
protected isAuthorizedCitationAddonsLoading = select(AddonsSelectors.getAuthorizedCitationAddonsLoading);
+ protected isAuthorizedLinkAddonsLoading = select(AddonsSelectors.getAuthorizedLinkAddonsLoading);
protected isAddonsLoading = computed(() => {
return (
this.isStorageAddonsLoading() ||
this.isCitationAddonsLoading() ||
+ this.isLinkAddonsLoading() ||
this.isUserReferenceLoading() ||
this.isCurrentUserLoading()
);
@@ -91,6 +95,7 @@ export class AddonsComponent {
return (
this.isAuthorizedStorageAddonsLoading() ||
this.isAuthorizedCitationAddonsLoading() ||
+ this.isAuthorizedLinkAddonsLoading() ||
this.isUserReferenceLoading() ||
this.isCurrentUserLoading()
);
@@ -108,7 +113,11 @@ export class AddonsComponent {
});
protected readonly allAuthorizedAddons = computed(() => {
- const authorizedAddons = [...this.authorizedStorageAddons(), ...this.authorizedCitationAddons()];
+ const authorizedAddons = [
+ ...this.authorizedStorageAddons(),
+ ...this.authorizedCitationAddons(),
+ ...this.authorizedLinkAddons(),
+ ];
const searchValue = this.searchValue().toLowerCase();
return authorizedAddons.filter((card) => card.displayName.includes(searchValue));
diff --git a/src/app/shared/components/addons/addon-card-list/addon-card-list.component.ts b/src/app/shared/components/addons/addon-card-list/addon-card-list.component.ts
index aaebeeaba..88922ed4a 100644
--- a/src/app/shared/components/addons/addon-card-list/addon-card-list.component.ts
+++ b/src/app/shared/components/addons/addon-card-list/addon-card-list.component.ts
@@ -3,7 +3,7 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Component, input } from '@angular/core';
import { AddonCardComponent } from '@shared/components/addons';
-import { AddonModel, AuthorizedAccountModel, ConfiguredStorageAddonModel } from '@shared/models';
+import { AddonModel, AuthorizedAccountModel, ConfiguredAddonModel } from '@shared/models';
@Component({
selector: 'osf-addon-card-list',
@@ -12,7 +12,7 @@ import { AddonModel, AuthorizedAccountModel, ConfiguredStorageAddonModel } from
styleUrl: './addon-card-list.component.scss',
})
export class AddonCardListComponent {
- cards = input<(AddonModel | AuthorizedAccountModel | ConfiguredStorageAddonModel)[]>([]);
+ cards = input<(AddonModel | AuthorizedAccountModel | ConfiguredAddonModel)[]>([]);
cardButtonLabel = input
('');
showDangerButton = input(false);
}
diff --git a/src/app/shared/components/addons/addon-card/addon-card.component.ts b/src/app/shared/components/addons/addon-card/addon-card.component.ts
index eddb6a78f..4177deea1 100644
--- a/src/app/shared/components/addons/addon-card/addon-card.component.ts
+++ b/src/app/shared/components/addons/addon-card/addon-card.component.ts
@@ -9,7 +9,7 @@ import { Router } from '@angular/router';
import { getAddonTypeString, isConfiguredAddon } from '@osf/shared/helpers';
import { CustomConfirmationService, LoaderService } from '@osf/shared/services';
-import { AddonModel, AuthorizedAccountModel, ConfiguredStorageAddonModel } from '@shared/models';
+import { AddonModel, AuthorizedAccountModel, ConfiguredAddonModel } from '@shared/models';
import { DeleteAuthorizedAddon } from '@shared/stores/addons';
@Component({
@@ -24,7 +24,7 @@ export class AddonCardComponent {
private readonly loaderService = inject(LoaderService);
private readonly actions = createDispatchMap({ deleteAuthorizedAddon: DeleteAuthorizedAddon });
- readonly card = input(null);
+ readonly card = input(null);
readonly cardButtonLabel = input('');
readonly showDangerButton = input(false);
diff --git a/src/app/shared/components/addons/folder-selector/folder-selector.component.ts b/src/app/shared/components/addons/folder-selector/folder-selector.component.ts
deleted file mode 100644
index 92d2f6896..000000000
--- a/src/app/shared/components/addons/folder-selector/folder-selector.component.ts
+++ /dev/null
@@ -1,169 +0,0 @@
-import { select } from '@ngxs/store';
-
-import { TranslatePipe, TranslateService } from '@ngx-translate/core';
-
-import { MenuItem } from 'primeng/api';
-import { BreadcrumbModule } from 'primeng/breadcrumb';
-import { Button } from 'primeng/button';
-import { Card } from 'primeng/card';
-import { InputText } from 'primeng/inputtext';
-import { RadioButton } from 'primeng/radiobutton';
-import { Skeleton } from 'primeng/skeleton';
-
-import {
- ChangeDetectionStrategy,
- Component,
- computed,
- DestroyRef,
- effect,
- inject,
- input,
- model,
- OnInit,
- output,
- signal,
-} from '@angular/core';
-import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
-import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
-
-import { OperationNames } from '@osf/features/project/addons/enums';
-import { OperationInvokeData, StorageItem } from '@shared/models';
-import { AddonsSelectors } from '@shared/stores/addons';
-
-import { GoogleFilePickerComponent } from './google-file-picker/google-file-picker.component';
-
-@Component({
- selector: 'osf-folder-selector',
- templateUrl: './folder-selector.component.html',
- styleUrl: './folder-selector.component.scss',
- imports: [
- BreadcrumbModule,
- Button,
- Card,
- FormsModule,
- GoogleFilePickerComponent,
- InputText,
- RadioButton,
- ReactiveFormsModule,
- Skeleton,
- TranslatePipe,
- ],
- changeDetection: ChangeDetectionStrategy.OnPush,
-})
-export class FolderSelectorComponent implements OnInit {
- private destroyRef = inject(DestroyRef);
- private translateService = inject(TranslateService);
-
- isGoogleFilePicker = input.required();
- accountName = input.required();
- operationInvocationResult = input.required();
- accountNameControl = input(new FormControl());
- isCreateMode = input(false);
-
- selectedRootFolderId = model('/');
- operationInvoke = output();
- save = output();
- cancelSelection = output();
- protected readonly OperationNames = OperationNames;
- protected hasInputChanged = signal(false);
- protected hasFolderChanged = signal(false);
- protected selectedRootFolder = signal(null);
- protected breadcrumbItems = signal