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)}}