diff --git a/app/helpers/instance-of.ts b/app/helpers/instance-of.ts
new file mode 100644
index 00000000000..c4aa98e2a86
--- /dev/null
+++ b/app/helpers/instance-of.ts
@@ -0,0 +1,20 @@
+import { getOwner } from '@ember/application';
+import { assert } from '@ember/debug';
+
+import Helper from '@ember/component/helper';
+
+export default class InstanceOf extends Helper {
+ compute([object, className]: [any, string]) {
+ if (!object || typeof className !== 'string') {
+ return false;
+ }
+ // Look up the class from the container
+ const owner = getOwner(this);
+ const klass = owner.factoryFor(`model:${className}`)?.class;
+ if (!klass) {
+ assert(`Class "${className}" not found`);
+ return false;
+ }
+ return object instanceof klass;
+ }
+}
diff --git a/app/models/addon-operation-invocation.ts b/app/models/addon-operation-invocation.ts
index 19eaef0173a..245120b0ca7 100644
--- a/app/models/addon-operation-invocation.ts
+++ b/app/models/addon-operation-invocation.ts
@@ -17,6 +17,12 @@ export enum ConnectedCitationOperationNames {
GetItemInfo = 'get_item_info',
}
+export enum ConnectedLinkOperationNames {
+ ListRootItems = 'list_root_items',
+ ListChildItems = 'list_child_items',
+ GetItemInfo = 'get_item_info',
+}
+
export interface OperationKwargs {
itemId?: string;
itemType?: ItemType;
diff --git a/app/models/authorized-link-account.ts b/app/models/authorized-link-account.ts
new file mode 100644
index 00000000000..b83863211a4
--- /dev/null
+++ b/app/models/authorized-link-account.ts
@@ -0,0 +1,47 @@
+import { AsyncBelongsTo, belongsTo } from '@ember-data/model';
+import { waitFor } from '@ember/test-waiters';
+import { task } from 'ember-concurrency';
+import { ConnectedLinkOperationNames, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
+
+import ExternalLinkServiceModel from 'ember-osf-web/models/external-link-service';
+import AuthorizedAccountModel from './authorized-account';
+import UserReferenceModel from './user-reference';
+
+export default class AuthorizedLinkAccountModel extends AuthorizedAccountModel {
+ @belongsTo('user-reference', { inverse: 'authorizedLinkAccounts' })
+ readonly accountOwner!: AsyncBelongsTo
& UserReferenceModel;
+
+ @belongsTo('external-link-service')
+ externalLinkService!: AsyncBelongsTo & ExternalLinkServiceModel;
+
+ @task
+ @waitFor
+ async getFolderItems(this: AuthorizedAccountModel, kwargs?: OperationKwargs) {
+ const operationKwargs = kwargs || {};
+ const operationName = operationKwargs.itemId ? ConnectedLinkOperationNames.ListChildItems :
+ ConnectedLinkOperationNames.ListRootItems;
+ const newInvocation = this.store.createRecord('addon-operation-invocation', {
+ operationName,
+ operationKwargs,
+ thruAccount: this,
+ });
+ return await newInvocation.save();
+ }
+
+ @task
+ @waitFor
+ async getItemInfo(this: AuthorizedAccountModel, itemId: string) {
+ const newInvocation = this.store.createRecord('addon-operation-invocation', {
+ operationName: ConnectedLinkOperationNames.GetItemInfo,
+ operationKwargs: { itemId },
+ thruAccount: this,
+ });
+ return await newInvocation.save();
+ }
+}
+
+declare module 'ember-data/types/registries/model' {
+ export default interface ModelRegistry {
+ 'authorized-link-account': AuthorizedLinkAccountModel;
+ } // eslint-disable-line semi
+}
diff --git a/app/models/configured-addon.ts b/app/models/configured-addon.ts
index a0e9d738724..fca82844a36 100644
--- a/app/models/configured-addon.ts
+++ b/app/models/configured-addon.ts
@@ -1,10 +1,8 @@
import Model, { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model';
-import { waitFor } from '@ember/test-waiters';
-import { task } from 'ember-concurrency';
import UserReferenceModel from 'ember-osf-web/models/user-reference';
import { tracked } from 'tracked-built-ins';
-import { taskFor } from 'ember-concurrency-ts';
+import { SupportedResourceTypes } from 'ember-osf-web/models/external-link-service';
import { ConnectedStorageOperationNames, OperationKwargs } from './addon-operation-invocation';
import { ConnectedCapabilities } from './authorized-account';
@@ -12,6 +10,8 @@ import { ConnectedCapabilities } from './authorized-account';
export interface ConfiguredAddonEditableAttrs {
displayName: string;
rootFolder: string;
+ targetId: string;
+ resourceType: SupportedResourceTypes;
}
export default class ConfiguredAddonModel extends Model {
@@ -50,11 +50,5 @@ export default class ConfiguredAddonModel extends Model {
}
@tracked rootFolderName = '';
-
- @task
- @waitFor
- async getRootFolderName(this: ConfiguredAddonModel) {
- const response = await taskFor(this.getItemInfo).perform(this.rootFolder);
- this.rootFolderName = response.operationResult.itemName;
- }
+ @tracked targetItemName = '';
}
diff --git a/app/models/configured-citation-addon.ts b/app/models/configured-citation-addon.ts
index 25764947aa9..3556e8ace19 100644
--- a/app/models/configured-citation-addon.ts
+++ b/app/models/configured-citation-addon.ts
@@ -3,7 +3,10 @@ import { AsyncBelongsTo, belongsTo } from '@ember-data/model';
import ResourceReferenceModel from 'ember-osf-web/models/resource-reference';
import { task } from 'ember-concurrency';
import { waitFor } from '@ember/test-waiters';
-import { ConnectedCitationOperationNames, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
+import {
+ ConnectedCitationOperationNames, Item, OperationKwargs,
+} from 'ember-osf-web/models/addon-operation-invocation';
+import { taskFor } from 'ember-concurrency-ts';
import AuthorizedCitationAccountModel from './authorized-citation-account';
import ExternalCitationServiceModel from './external-citation-service';
import ConfiguredAddonModel from './configured-addon';
@@ -48,6 +51,13 @@ export default class ConfiguredCitationAddonModel extends ConfiguredAddonModel {
});
return await newInvocation.save();
}
+
+ @task
+ @waitFor
+ async getSelectedItemName(this: ConfiguredCitationAddonModel) {
+ const response = await taskFor(this.getItemInfo).perform(this.rootFolder);
+ this.rootFolderName = (response.operationResult as Item).itemName;
+ }
}
declare module 'ember-data/types/registries/model' {
diff --git a/app/models/configured-link-addon.ts b/app/models/configured-link-addon.ts
new file mode 100644
index 00000000000..17ce68003e7
--- /dev/null
+++ b/app/models/configured-link-addon.ts
@@ -0,0 +1,72 @@
+import { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model';
+import { waitFor } from '@ember/test-waiters';
+import { task } from 'ember-concurrency';
+import { ConnectedLinkOperationNames, Item, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
+import ResourceReferenceModel from 'ember-osf-web/models/resource-reference';
+
+import ExternalLinkServiceModel, { SupportedResourceTypes } from 'ember-osf-web/models/external-link-service';
+import AuthorizedLinkAccountModel from 'ember-osf-web/models/authorized-link-account';
+import { taskFor } from 'ember-concurrency-ts';
+import ConfiguredAddonModel from './configured-addon';
+
+
+export default class ConfiguredLinkAddonModel extends ConfiguredAddonModel {
+ @attr('string') targetId!: string;
+ @attr('string') targetUrl!: string;
+ @attr('string') resourceType!: SupportedResourceTypes;
+
+ @belongsTo('external-link-service', { inverse: null })
+ externalLinkService!: AsyncBelongsTo & ExternalLinkServiceModel;
+
+ @belongsTo('authorized-link-account')
+ baseAccount!: AsyncBelongsTo & AuthorizedLinkAccountModel;
+
+ @belongsTo('resource-reference', { inverse: 'configuredLinkAddons' })
+ authorizedResource!: AsyncBelongsTo & ResourceReferenceModel;
+
+ get externalServiceId() {
+ return (this as ConfiguredLinkAddonModel).belongsTo('externalLinkService').id();
+ }
+
+ get hasRootFolder() {
+ return false;
+ }
+
+ @task
+ @waitFor
+ async getFolderItems(this: ConfiguredAddonModel, kwargs?: OperationKwargs) {
+ const operationKwargs = kwargs || {};
+ const operationName = operationKwargs.itemId ? ConnectedLinkOperationNames.ListChildItems :
+ ConnectedLinkOperationNames.ListRootItems;
+ const newInvocation = this.store.createRecord('addon-operation-invocation', {
+ operationName,
+ operationKwargs,
+ thruAddon: this,
+ });
+ return await newInvocation.save();
+ }
+
+ @task
+ @waitFor
+ async getItemInfo(this: ConfiguredAddonModel, itemId: string) {
+ const newInvocation = this.store.createRecord('addon-operation-invocation', {
+ operationName: ConnectedLinkOperationNames.GetItemInfo,
+ operationKwargs: { itemId },
+ thruAddon: this,
+ });
+ return await newInvocation.save();
+ }
+
+ @task
+ @waitFor
+ async getSelectedItemName(this: ConfiguredLinkAddonModel) {
+ const response = await taskFor(this.getItemInfo).perform(this.targetId);
+ this.targetItemName = (response.operationResult as Item).itemName;
+ }
+}
+
+declare module 'ember-data/types/registries/model' {
+ export default interface ModelRegistry {
+ 'configured-link-addon': ConfiguredLinkAddonModel;
+ } // eslint-disable-line semi
+}
diff --git a/app/models/configured-storage-addon.ts b/app/models/configured-storage-addon.ts
index 31defc9bbdd..81c36de4f57 100644
--- a/app/models/configured-storage-addon.ts
+++ b/app/models/configured-storage-addon.ts
@@ -1,9 +1,10 @@
import { AsyncBelongsTo, attr, belongsTo } from '@ember-data/model';
import { waitFor } from '@ember/test-waiters';
import { task } from 'ember-concurrency';
-import { ConnectedStorageOperationNames, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
+import { ConnectedStorageOperationNames, Item, OperationKwargs } from 'ember-osf-web/models/addon-operation-invocation';
import ResourceReferenceModel from 'ember-osf-web/models/resource-reference';
+import { taskFor } from 'ember-concurrency-ts';
import AuthorizedStorageAccountModel from './authorized-storage-account';
import ConfiguredAddonModel from './configured-addon';
import ExternalStorageServiceModel from './external-storage-service';
@@ -49,6 +50,13 @@ export default class ConfiguredStorageAddonModel extends ConfiguredAddonModel {
});
return await newInvocation.save();
}
+
+ @task
+ @waitFor
+ async getSelectedItemName(this: ConfiguredStorageAddonModel) {
+ const response = await taskFor(this.getItemInfo).perform(this.rootFolder);
+ this.rootFolderName = (response.operationResult as Item).itemName;
+ }
}
declare module 'ember-data/types/registries/model' {
diff --git a/app/models/external-link-service.ts b/app/models/external-link-service.ts
new file mode 100644
index 00000000000..7c66b163704
--- /dev/null
+++ b/app/models/external-link-service.ts
@@ -0,0 +1,48 @@
+import { attr } from '@ember-data/model';
+
+import ExternalServiceModel from './external-service';
+
+export enum SupportedResourceTypes {
+ AUDIOVISUAL = 'Audiovisual',
+ AWARD = 'Award',
+ BOOK = 'Book',
+ BOOK_CHAPTER = 'BookChapter',
+ COLLECTION = 'Collection',
+ COMPUTATIONAL_NOTEBOOK = 'ComputationalNotebook',
+ CONFERENCE_PAPER = 'ConferencePaper',
+ CONFERENCE_PROCEEDING = 'ConferenceProceeding',
+ DATA_PAPER = 'DataPaper',
+ DATASET = 'Dataset',
+ DISSERTATION = 'Dissertation',
+ EVENT = 'Event',
+ IMAGE = 'Image',
+ INSTRUMENT = 'Instrument',
+ INTERACTIVE_RESOURCE = 'InteractiveResource',
+ JOURNAL = 'Journal',
+ JOURNAL_ARTICLE = 'JournalArticle',
+ MODEL = 'Model',
+ OUTPUT_MANAGEMENT_PLAN = 'OutputManagementPlan',
+ PEER_REVIEW = 'PeerReview',
+ PHYSICAL_OBJECT = 'PhysicalObject',
+ PREPRINT = 'Preprint',
+ PROJECT = 'Project',
+ REPORT = 'Report',
+ SERVICE = 'Service',
+ SOFTWARE = 'Software',
+ SOUND = 'Sound',
+ STANDARD = 'Standard',
+ STUDY_REGISTRATION = 'StudyRegistration',
+ TEXT = 'Text',
+ WORKFLOW = 'Workflow',
+ OTHER = 'Other',
+}
+
+export default class ExternalLinkServiceModel extends ExternalServiceModel {
+ @attr('array') supportedResourceTypes!: SupportedResourceTypes[];
+}
+
+declare module 'ember-data/types/registries/model' {
+ export default interface ModelRegistry {
+ 'external-link-service': ExternalLinkServiceModel;
+ } // eslint-disable-line semi
+}
diff --git a/app/models/resource-reference.ts b/app/models/resource-reference.ts
index 9adf8334abf..93d4c87c1b3 100644
--- a/app/models/resource-reference.ts
+++ b/app/models/resource-reference.ts
@@ -1,11 +1,11 @@
import Model, { AsyncHasMany, attr, hasMany } from '@ember-data/model';
+import ConfiguredLinkAddonModel from 'ember-osf-web/models/configured-link-addon';
import ConfiguredStorageAddonModel from './configured-storage-addon';
import ConfiguredCitationAddonModel from './configured-citation-addon';
import ConfiguredComputingAddonModel from './configured-computing-addon';
export default class ResourceReferenceModel extends Model {
-
@attr('fixstring') resourceUri!: string;
@hasMany('configured-storage-addon', { inverse: 'authorizedResource' })
@@ -18,6 +18,9 @@ export default class ResourceReferenceModel extends Model {
@hasMany('configured-computing-addon', { inverse: 'authorizedResource' })
configuredComputingAddons!: AsyncHasMany
& ConfiguredComputingAddonModel[];
+
+ @hasMany('configured-link-addon', { inverse: 'authorizedResource' })
+ configuredLinkAddons!: AsyncHasMany & ConfiguredLinkAddonModel[];
}
declare module 'ember-data/types/registries/model' {
diff --git a/app/models/user-reference.ts b/app/models/user-reference.ts
index 4cf3d589ee6..7efa721118e 100644
--- a/app/models/user-reference.ts
+++ b/app/models/user-reference.ts
@@ -1,5 +1,6 @@
import Model, { AsyncHasMany, attr, hasMany } from '@ember-data/model';
+import AuthorizedLinkAccountModel from 'ember-osf-web/models/authorized-link-account';
import AuthorizedStorageAccountModel from './authorized-storage-account';
import AuthorizedCitationAccountModel from './authorized-citation-account';
import AuthorizedComputingAccountModel from './authorized-computing-account';
@@ -19,6 +20,10 @@ export default class UserReferenceModel extends Model {
authorizedComputingAccounts!: AsyncHasMany
& AuthorizedComputingAccountModel[];
+ @hasMany('authorized-link-account', { inverse: 'accountOwner' })
+ authorizedLinkAccounts!: AsyncHasMany
+ & AuthorizedLinkAccountModel[];
+
@hasMany('resource-reference')
configuredResources!: AsyncHasMany & ResourceReferenceModel[];
}
diff --git a/app/packages/addons-service/provider.ts b/app/packages/addons-service/provider.ts
index edb62595a39..25e07b6d88f 100644
--- a/app/packages/addons-service/provider.ts
+++ b/app/packages/addons-service/provider.ts
@@ -25,19 +25,25 @@ import ExternalComputingServiceModel from 'ember-osf-web/models/external-computi
import ExternalCitationServiceModel from 'ember-osf-web/models/external-citation-service';
import { notifyPropertyChange } from '@ember/object';
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
+import ExternalLinkServiceModel from 'ember-osf-web/models/external-link-service';
+import AuthorizedLinkAccountModel from 'ember-osf-web/models/authorized-link-account';
+import ConfiguredLinkAddonModel from 'ember-osf-web/models/configured-link-addon';
export type AllProviderTypes =
ExternalStorageServiceModel |
ExternalComputingServiceModel |
- ExternalCitationServiceModel;
+ ExternalCitationServiceModel |
+ ExternalLinkServiceModel;
export type AllAuthorizedAccountTypes =
AuthorizedStorageAccountModel |
AuthorizedCitationAccountModel |
- AuthorizedComputingAccountModel;
+ AuthorizedComputingAccountModel |
+ AuthorizedLinkAccountModel;
export type AllConfiguredAddonTypes =
ConfiguredStorageAddonModel |
ConfiguredCitationAddonModel |
- ConfiguredComputingAddonModel;
+ ConfiguredComputingAddonModel |
+ ConfiguredLinkAddonModel;
interface ProviderTypeMapper {
getAuthorizedAccounts: Task;
@@ -82,6 +88,11 @@ export default class Provider {
createAuthorizedAccount: taskFor(this.createAuthorizedCitationAccount),
createConfiguredAddon: taskFor(this.createConfiguredCitationAddon),
},
+ externalLinkService: {
+ getAuthorizedAccounts: taskFor(this.getAuthorizedLinkAccounts),
+ createAuthorizedAccount: taskFor(this.createAuthorizedLinkAccount),
+ createConfiguredAddon: taskFor(this.createConfiguredLinkAddon),
+ },
};
@tracked configuredAddon?: AllConfiguredAddonTypes;
@@ -134,6 +145,8 @@ export default class Provider {
this.providerMap = this.providerTypeMapper.externalComputingService;
} else if (provider instanceof ExternalCitationServiceModel) {
this.providerMap = this.providerTypeMapper.externalCitationService;
+ } else if (provider instanceof ExternalLinkServiceModel) {
+ this.providerMap = this.providerTypeMapper.externalLinkService;
}
taskFor(this.initialize).perform();
}
@@ -211,6 +224,14 @@ export default class Provider {
.filterBy('externalComputingService.id', this.provider.id).toArray();
}
+ @task
+ @waitFor
+ async getAuthorizedLinkAccounts() {
+ const authorizedLinkAccounts = await this.userReference.authorizedLinkAccounts;
+ this.authorizedAccounts = authorizedLinkAccounts
+ .filterBy('externalLinkService.id', this.provider.id).toArray();
+ }
+
@task
@waitFor
async getAuthorizedAccounts() {
@@ -279,6 +300,25 @@ export default class Provider {
return newAccount;
}
+ @task
+ @waitFor
+ private async createAuthorizedLinkAccount(arg: AccountCreationArgs) {
+ const { credentials, apiBaseUrl, displayName, initiateOauth } = arg;
+ const newAccount = this.store.createRecord('authorized-link-account', {
+ credentials,
+ apiBaseUrl,
+ initiateOauth,
+ externalUserId: this.currentUser.user?.id,
+ authorizedCapabilities: ['ACCESS', 'UPDATE'],
+ scopes: [],
+ externalLinkService: this.provider,
+ accountOwner: this.userReference,
+ displayName,
+ });
+ await newAccount.save();
+ return newAccount;
+ }
+
@task
@waitFor
public async createAuthorizedAccount(arg: AccountCreationArgs) {
@@ -340,6 +380,20 @@ export default class Provider {
return await configuredComputingAddon.save();
}
+ @task
+ @waitFor
+ private async createConfiguredLinkAddon(account: AuthorizedComputingAccountModel) {
+ const configuredLinkAddon = this.store.createRecord('configured-link-addon', {
+ externalLinkService: this.provider,
+ accountOwner: this.userReference,
+ authorizedResourceUri: this.node!.links.iri,
+ baseAccount: account,
+ connectedCapabilities: ['ACCESS', 'UPDATE'],
+ });
+ return await configuredLinkAddon.save();
+ }
+
+
@task
@waitFor
public async createConfiguredAddon(account: AllAuthorizedAccountTypes) {
diff --git a/app/router.ts b/app/router.ts
index 1133ca47537..83ed6beefb1 100644
--- a/app/router.ts
+++ b/app/router.ts
@@ -106,6 +106,7 @@ Router.map(function() {
this.route('configure');
});
});
+ this.route('links');
});
this.route(
diff --git a/app/serializers/authorized-link-account.ts b/app/serializers/authorized-link-account.ts
new file mode 100644
index 00000000000..c658155e5a7
--- /dev/null
+++ b/app/serializers/authorized-link-account.ts
@@ -0,0 +1,10 @@
+import GravyValetSerializer from './gravy-valet-serializer';
+
+export default class AuthorizedLinkAccountSerializer extends GravyValetSerializer {
+}
+
+declare module 'ember-data/types/registries/serializer' {
+ export default interface SerializerRegistry {
+ 'authorized-link-account': AuthorizedLinkAccountSerializer;
+ } // eslint-disable-line semi
+}
diff --git a/app/serializers/configured-link-addon.ts b/app/serializers/configured-link-addon.ts
new file mode 100644
index 00000000000..5b97c9f2ee0
--- /dev/null
+++ b/app/serializers/configured-link-addon.ts
@@ -0,0 +1,10 @@
+import ConfiguredAddonSerializer from './configured-addon';
+
+export default class ConfiguredLinkAddonSerializer extends ConfiguredAddonSerializer {
+}
+
+declare module 'ember-data/types/registries/serializer' {
+ export default interface SerializerRegistry {
+ 'configured-link-addon': ConfiguredLinkAddonSerializer;
+ } // eslint-disable-line semi
+}
diff --git a/app/serializers/external-link-service.ts b/app/serializers/external-link-service.ts
new file mode 100644
index 00000000000..3306c07224e
--- /dev/null
+++ b/app/serializers/external-link-service.ts
@@ -0,0 +1,10 @@
+import GravyValetSerializer from './gravy-valet-serializer';
+
+export default class ExternalLinkServiceSerializer extends GravyValetSerializer {
+}
+
+declare module 'ember-data/types/registries/serializer' {
+ export default interface SerializerRegistry {
+ 'external-link-service': ExternalLinkServiceSerializer;
+ } // eslint-disable-line semi
+}
diff --git a/lib/osf-components/addon/components/addons-service/configured-addon-edit/component.ts b/lib/osf-components/addon/components/addons-service/configured-addon-edit/component.ts
index 28bec3d385e..fc5e7a3dfcd 100644
--- a/lib/osf-components/addon/components/addons-service/configured-addon-edit/component.ts
+++ b/lib/osf-components/addon/components/addons-service/configured-addon-edit/component.ts
@@ -10,10 +10,12 @@ import { Item, ItemType } from 'ember-osf-web/models/addon-operation-invocation'
import AuthorizedAccountModel from 'ember-osf-web/models/authorized-account';
import AuthorizedCitationAccountModel from 'ember-osf-web/models/authorized-citation-account';
import AuthorizedComputingAccountModel from 'ember-osf-web/models/authorized-computing-account';
+import AuthorizedLinkAccountModel from 'ember-osf-web/models/authorized-link-account';
import AuthorizedStorageAccountModel from 'ember-osf-web/models/authorized-storage-account';
import ConfiguredAddonModel from 'ember-osf-web/models/configured-addon';
import ConfiguredCitationAddonModel from 'ember-osf-web/models/configured-citation-addon';
import ConfiguredComputingAddonModel from 'ember-osf-web/models/configured-computing-addon';
+import ConfiguredLinkAddonModel from 'ember-osf-web/models/configured-link-addon';
import ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';
import ExternalStorageServiceModel from 'ember-osf-web/models/external-storage-service';
@@ -29,11 +31,17 @@ export default class ConfiguredAddonEdit extends Component {
@tracked selectedFolder = this.args.configuredAddon?.rootFolder;
@tracked selectedFolderDisplayName = this.args.configuredAddon?.rootFolderName;
@tracked currentItems: Item[] = [];
+ @tracked selectedItem = '';
+ @tracked selectedItemDisplayName = '';
+ @tracked selectedResourceType = '';
@tracked isWBGoogleDrive = false;
@tracked accountId!: string;
originalName = this.displayName;
originalRootFolder = this.selectedFolder;
+ originalSelectedItem = this.selectedItem;
+ originalResourceType = this.selectedResourceType;
+
defaultKwargs: any = {};
constructor(owner: unknown, args: Args) {
@@ -46,6 +54,11 @@ export default class ConfiguredAddonEdit extends Component {
if (this.args.configuredAddon instanceof ConfiguredCitationAddonModel) {
this.defaultKwargs['filterItems'] = ItemType.Collection;
}
+ if (this.args.configuredAddon instanceof ConfiguredLinkAddonModel) {
+ this.selectedItem = (this.args.configuredAddon as ConfiguredLinkAddonModel).targetId;
+ this.selectedItemDisplayName = (this.args.configuredAddon as ConfiguredLinkAddonModel).targetItemName;
+ this.selectedResourceType = (this.args.configuredAddon as ConfiguredLinkAddonModel).resourceType;
+ }
}
if (this.args.authorizedAccount) {
if (this.args.authorizedAccount instanceof AuthorizedStorageAccountModel) {
@@ -58,10 +71,23 @@ export default class ConfiguredAddonEdit extends Component {
}
}
+ get isLinkAddon() {
+ return this.args.configuredAddon instanceof ConfiguredLinkAddonModel ||
+ this.args.authorizedAccount instanceof AuthorizedLinkAccountModel;
+ }
+
+ get requiresFilesWidget() {
/**
* This is called only to authorize because the current implementation will throw an
* error because the "root folder" is not yet set.
*/
+ return !(
+ this.args.authorizedAccount instanceof AuthorizedComputingAccountModel
+ ||
+ this.args.configuredAddon instanceof ConfiguredComputingAddonModel
+ );
+ }
+
@task
@waitFor
async loadExternalStorageService() {
@@ -80,20 +106,12 @@ export default class ConfiguredAddonEdit extends Component {
this.isWBGoogleDrive = external?.wbKey === 'googledrive';
}
- get requiresRootFolder() {
- return !(
- this.args.authorizedAccount instanceof AuthorizedComputingAccountModel
- ||
- this.args.configuredAddon instanceof ConfiguredComputingAddonModel
- );
- }
-
get isGoogleDrive(): boolean {
return this.isWBGoogleDrive;
}
get displayFileManager(): boolean {
- return this.requiresRootFolder && !this.isGoogleDrive;
+ return this.requiresFilesWidget && !this.isGoogleDrive;
}
get invalidDisplayName() {
@@ -102,33 +120,40 @@ export default class ConfiguredAddonEdit extends Component {
get disableSave() {
const displayNameUnchanged = this.displayName === this.originalName;
- const rootFolderUnchanged = this.requiresRootFolder && this.selectedFolder === this.originalRootFolder;
- const needsRootFolder = this.requiresRootFolder && !this.selectedFolder;
-
- if (this.invalidDisplayName || needsRootFolder) {
+ const rootFolderUnchanged = this.requiresFilesWidget && !this.isLinkAddon
+ && this.selectedFolder === this.originalRootFolder;
+ const targetIdUnchanged = this.isLinkAddon && this.originalSelectedItem === this.selectedItem;
+ const resourceTypeUnchanged = this.originalResourceType === this.selectedResourceType;
+ const needsResourceType = this.isLinkAddon && !this.selectedResourceType;
+ const needsTargetId = this.isLinkAddon && !this.selectedItem;
+ const needsRootFolder = this.requiresFilesWidget && !this.isLinkAddon && !this.selectedFolder;
+
+ if (this.invalidDisplayName || needsRootFolder || needsResourceType || needsTargetId) {
return true;
}
+ if (this.isLinkAddon) {
+ return targetIdUnchanged && resourceTypeUnchanged || this.args.onSave.isRunning;
+ }
return (rootFolderUnchanged && displayNameUnchanged) || this.args.onSave.isRunning;
}
- get nameValid() {
- return !this.invalidDisplayName && this.displayName !== this.originalName;
- }
-
- get folderValid() {
- return !this.requiresRootFolder && this.selectedFolder && this.selectedFolder !== this.originalRootFolder;
- }
-
get onSaveArgs() {
return {
displayName: this.displayName,
rootFolder: this.selectedFolder,
+ targetId: this.selectedItem,
+ resourceType: this.selectedResourceType,
};
}
@action
- selectFolder(folder: Item) {
- this.selectedFolder = folder.itemId;
- this.selectedFolderDisplayName = folder.itemName;
+ selectItem(item: Item) {
+ if (this.isLinkAddon) {
+ this.selectedItem = item.itemId;
+ this.selectedItemDisplayName = item.itemName;
+ } else {
+ this.selectedFolder = item.itemId;
+ this.selectedFolderDisplayName = item.itemName;
+ }
}
}
diff --git a/lib/osf-components/addon/components/addons-service/configured-addon-edit/styles.scss b/lib/osf-components/addon/components/addons-service/configured-addon-edit/styles.scss
index f362d65404e..83a44de0e6b 100644
--- a/lib/osf-components/addon/components/addons-service/configured-addon-edit/styles.scss
+++ b/lib/osf-components/addon/components/addons-service/configured-addon-edit/styles.scss
@@ -19,6 +19,7 @@
width: 100%;
border: solid 1px $color-border-gray;
border-collapse: collapse;
+ margin-bottom: 1rem;
thead {
border-bottom: solid 1px $color-border-gray;
diff --git a/lib/osf-components/addon/components/addons-service/configured-addon-edit/template.hbs b/lib/osf-components/addon/components/addons-service/configured-addon-edit/template.hbs
index 44737b94b70..e1279582bcd 100644
--- a/lib/osf-components/addon/components/addons-service/configured-addon-edit/template.hbs
+++ b/lib/osf-components/addon/components/addons-service/configured-addon-edit/template.hbs
@@ -24,11 +24,19 @@
{{#if this.displayFileManager}}
- {{t 'addons.configure.selected-folder'}}
+ {{#if this.isLinkAddon}}
+ {{t 'addons.configure.linked-item'}}
+ {{else}}
+ {{t 'addons.configure.selected-folder'}}
+ {{/if}}
{{#if this.selectedFolderDisplayName}}
{{this.selectedFolderDisplayName}}
+ {{else if this.selectedItemDisplayName}}
+ {{this.selectedItemDisplayName}}
+ {{else if this.isLinkAddon}}
+ {{t 'addons.configure.no-item-selected'}}
{{else}}
{{t 'addons.configure.no-folder-selected'}}
{{/if}}
@@ -81,48 +89,48 @@
| {{t 'addons.configure.error-loading-items'}} |
{{else}}
- {{#each fileManager.currentItems as |folder|}}
+ {{#each fileManager.currentItems as |item|}}
|
- {{#if folder.mayContainRootCandidates}}
+ {{#if (or item.mayContainRootCandidates fileManager.isLinkAddon)}}
{{else}}
- {{#if (or (eq folder.itemType 'FOLDER') (eq folder.itemType 'COLLECTION'))}}
+ {{#if (or (eq item.itemType 'FOLDER') (eq item.itemType 'COLLECTION'))}}
{{else}}
{{/if}}
- {{folder.itemName}}
+ {{item.itemName}}
{{/if}}
|
- {{#if folder.canBeRoot}}
+ {{#if (or item.canBeRoot fileManager.isLinkAddon)}}
{{/if}}
|
@@ -153,11 +161,25 @@
{{else}}
{{/if}}
+ {{#if this.isLinkAddon}}
+
+ {{/if}}