diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09ce9c92a78..b8ca3a95b4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
+## [25.12.0] - 2025-06-13
+### Added
+- Google File Picker workflow
+- Misc. improvements
+
+## [25.11.0] - 2025-06-11
+### Added
+- Manual GUID and DOI assignment during Preprint and Registration Creation
+
## [25.10.0] - 2025-05-13
### Added
- Re-enable view and download counts for preprints
diff --git a/app/config/environment.d.ts b/app/config/environment.d.ts
index d29e42e6b8d..863cd9add0e 100644
--- a/app/config/environment.d.ts
+++ b/app/config/environment.d.ts
@@ -141,6 +141,12 @@ declare const config: {
doiUrlPrefix: string;
dataciteTrackerRepoId: string;
dataCiteTrackerUrl: string;
+ googleFilePicker: {
+ GOOGLE_FILE_PICKER_SCOPES: string;
+ GOOGLE_FILE_PICKER_CLIENT_ID: string;
+ GOOGLE_FILE_PICKER_API_KEY: string;
+ GOOGLE_FILE_PICKER_APP_ID: number;
+ }
};
social: {
twitter: {
diff --git a/app/guid-node/files/provider/template.hbs b/app/guid-node/files/provider/template.hbs
index 8f1e23912c7..0deaedcd1de 100644
--- a/app/guid-node/files/provider/template.hbs
+++ b/app/guid-node/files/provider/template.hbs
@@ -7,6 +7,7 @@
>
{
// To be implemented in child classes
return;
diff --git a/app/models/authorized-storage-account.ts b/app/models/authorized-storage-account.ts
index 45ea1d06685..f23a509b2d7 100644
--- a/app/models/authorized-storage-account.ts
+++ b/app/models/authorized-storage-account.ts
@@ -1,13 +1,16 @@
-import { AsyncBelongsTo, belongsTo } from '@ember-data/model';
+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 ExternalStorageServiceModel from 'ember-osf-web/models/external-storage-service';
-import ExternalStorageServiceModel from './external-storage-service';
import AuthorizedAccountModel from './authorized-account';
import UserReferenceModel from './user-reference';
export default class AuthorizedStorageAccountModel extends AuthorizedAccountModel {
+ @attr('fixstring') serializeOauthToken!: string;
+ @attr('fixstring') oauthToken!: string;
+
@belongsTo('user-reference', { inverse: 'authorizedStorageAccounts' })
readonly accountOwner!: AsyncBelongsTo & UserReferenceModel;
diff --git a/app/models/index-card-search.ts b/app/models/index-card-search.ts
index fc5250db51c..5421da16dcd 100644
--- a/app/models/index-card-search.ts
+++ b/app/models/index-card-search.ts
@@ -10,7 +10,7 @@ export interface SearchFilter {
filterType?: string;
}
-export const ShareMoreThanTenThousand = 'https://share.osf.io/vocab/2023/trove/ten-thousands-and-more';
+export const ShareMoreThanTenThousand = 'trove:ten-thousands-and-more';
export default class IndexCardSearchModel extends Model {
@attr('string') cardSearchText!: string;
diff --git a/app/models/preprint.ts b/app/models/preprint.ts
index 942c04ac82f..7695225d50e 100644
--- a/app/models/preprint.ts
+++ b/app/models/preprint.ts
@@ -82,6 +82,8 @@ export default class PreprintModel extends AbstractNodeModel {
@attr('string') preregLinkInfo!: PreprintPreregLinkInfoEnum;
@attr('number') version!: number;
@attr('boolean') isLatestVersion!: boolean;
+ @attr('string') manualDoi!: string;
+ @attr('string') manualGuid!: string;
@belongsTo('node', { inverse: 'preprints' })
node!: AsyncBelongsTo & NodeModel;
diff --git a/app/models/registration.ts b/app/models/registration.ts
index 5ec13834004..1262ee16963 100644
--- a/app/models/registration.ts
+++ b/app/models/registration.ts
@@ -115,6 +115,8 @@ export default class RegistrationModel extends NodeModel.extend(Validations) {
@attr('boolean') hasAnalyticCode!: boolean;
@attr('boolean') hasPapers!: boolean;
@attr('boolean') hasSupplements!: boolean;
+ @attr('string') manualDoi!: string;
+ @attr('string') manualGuid!: string;
// Write-only attributes
@attr('array') includedNodeIds?: string[];
diff --git a/app/models/search-result.ts b/app/models/search-result.ts
index f4c7dae9032..f7279cb0a6e 100644
--- a/app/models/search-result.ts
+++ b/app/models/search-result.ts
@@ -314,7 +314,7 @@ export default class SearchResultModel extends Model {
}
get isWithdrawn() {
- return this.resourceMetadata.dateWithdrawn || this.resourceMetadata['https://osf.io/vocab/2022/withdrawal'];
+ return this.resourceMetadata.dateWithdrawn || this.resourceMetadata['osf:withdrawal'];
}
get configuredAddonNames() {
diff --git a/app/packages/addons-service/provider.ts b/app/packages/addons-service/provider.ts
index 602aa683fbe..edb62595a39 100644
--- a/app/packages/addons-service/provider.ts
+++ b/app/packages/addons-service/provider.ts
@@ -237,6 +237,7 @@ export default class Provider {
accountOwner: this.userReference,
});
await newAccount.save();
+ newAccount.initiateOauth = null;
return newAccount;
}
diff --git a/app/preprints/-components/submit/title-and-abstract/component.ts b/app/preprints/-components/submit/title-and-abstract/component.ts
index 07726a12158..bd7b65ecabf 100644
--- a/app/preprints/-components/submit/title-and-abstract/component.ts
+++ b/app/preprints/-components/submit/title-and-abstract/component.ts
@@ -2,10 +2,11 @@ import Component from '@glimmer/component';
import PreprintStateMachine from 'ember-osf-web/preprints/-components/submit/preprint-state-machine/component';
import { action } from '@ember/object';
import { ValidationObject } from 'ember-changeset-validations';
-import { validatePresence, validateLength } from 'ember-changeset-validations/validators';
+import { validatePresence, validateLength, validateFormat } from 'ember-changeset-validations/validators';
import buildChangeset from 'ember-osf-web/utils/build-changeset';
import { inject as service } from '@ember/service';
import Intl from 'ember-intl/services/intl';
+import { DOIRegex } from 'ember-osf-web/utils/doi';
/**
* The TitleAndAbstract Args
@@ -17,6 +18,8 @@ interface TitleAndAbstractArgs {
interface TitleAndAbstractForm {
title: string;
description: string;
+ manualDoi: string;
+ manualGuid: string;
}
/**
@@ -45,6 +48,22 @@ export default class TitleAndAbstract extends Component{
},
}),
],
+ manualDoi: validateFormat({
+ allowBlank: true,
+ allowNone: true,
+ ignoreBlank: true,
+ regex: DOIRegex,
+ type: 'invalid_doi',
+ }),
+ manualGuid: validateLength({
+ allowBlank: true,
+ min:5,
+ type: 'greaterThanOrEqualTo',
+ translationArgs: {
+ description: this.intl.t('preprints.submit.step-title.guid'),
+ gte: '5 characters',
+ },
+ }),
};
titleAndAbstractFormChangeset = buildChangeset(this.args.manager.preprint, this.titleAndAbstractFormValidation);
diff --git a/app/preprints/-components/submit/title-and-abstract/template.hbs b/app/preprints/-components/submit/title-and-abstract/template.hbs
index a53255e1aa2..2848d91f6ac 100644
--- a/app/preprints/-components/submit/title-and-abstract/template.hbs
+++ b/app/preprints/-components/submit/title-and-abstract/template.hbs
@@ -48,6 +48,42 @@
@onKeyUp={{this.validate}}
/>
{{/let}}
+ {{#if (and (feature-flag 'manual_doi_and_guid') (not @manager.isEditFlow))}}
+ {{#let (unique-id 'manualDoi') as |manualDoiField|}}
+
+
+ {{/let}}
+ {{#let (unique-id 'manualGuid') as |manualGuidField|}}
+
+
+ {{/let}}
+ {{/if}}
\ No newline at end of file
diff --git a/config/environment.js b/config/environment.js
index 61bc5d0ec3b..b35ce2b8859 100644
--- a/config/environment.js
+++ b/config/environment.js
@@ -102,6 +102,13 @@ const {
SHARE_SEARCH_URL: shareSearchUrl = 'http://localhost:8003/api/v2/search/creativeworks/_search',
SOURCEMAPS_ENABLED: sourcemapsEnabled = true,
SHOW_DEV_BANNER = false,
+
+ GOOGLE_FILE_PICKER_SCOPES,
+ /* eslint-disable-next-line max-len */
+ GOOGLE_FILE_PICKER_CLIENT_ID,
+ GOOGLE_FILE_PICKER_API_KEY,
+ GOOGLE_FILE_PICKER_APP_ID,
+
} = { ...process.env, ...localConfig };
module.exports = function(environment) {
@@ -224,6 +231,12 @@ module.exports = function(environment) {
doiUrlPrefix: 'https://doi.org/',
dataciteTrackerRepoId,
dataCiteTrackerUrl,
+ googleFilePicker: {
+ GOOGLE_FILE_PICKER_SCOPES,
+ GOOGLE_FILE_PICKER_CLIENT_ID,
+ GOOGLE_FILE_PICKER_API_KEY,
+ GOOGLE_FILE_PICKER_APP_ID,
+ },
},
social: {
twitter: {
@@ -316,6 +329,7 @@ module.exports = function(environment) {
},
storageI18n: 'storage_i18n',
gravyWaffle: 'gravy_waffle',
+ manualDoiAndGuid: 'manual_doi_and_guid',
enableInactiveSchemas: 'enable_inactive_schemas',
verifyEmailModals: 'ember_verify_email_modals',
ABTesting: {
diff --git a/lib/osf-components/addon/components/activity-log/component.ts b/lib/osf-components/addon/components/activity-log/component.ts
index 1f6e9318c03..dc3b9614a77 100644
--- a/lib/osf-components/addon/components/activity-log/component.ts
+++ b/lib/osf-components/addon/components/activity-log/component.ts
@@ -4,7 +4,7 @@ export default class ActivityLogComponent extends Component {
public loadEmbeds = {
embed:
[
- 'group', 'linked_node', 'linked_registration', 'original_node',
+ 'linked_node', 'linked_registration', 'original_node',
'template_node', 'user',
],
};
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 6b55e01e763..28bec3d385e 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
@@ -1,7 +1,10 @@
import { action } from '@ember/object';
+import { waitFor } from '@ember/test-waiters';
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
-import { TaskInstance } from 'ember-concurrency';
+import { task, TaskInstance } from 'ember-concurrency';
+import { taskFor } from 'ember-concurrency-ts';
+
import { Item, ItemType } from 'ember-osf-web/models/addon-operation-invocation';
import AuthorizedAccountModel from 'ember-osf-web/models/authorized-account';
@@ -12,6 +15,7 @@ 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 ConfiguredStorageAddonModel from 'ember-osf-web/models/configured-storage-addon';
+import ExternalStorageServiceModel from 'ember-osf-web/models/external-storage-service';
interface Args {
@@ -25,6 +29,8 @@ export default class ConfiguredAddonEdit extends Component {
@tracked selectedFolder = this.args.configuredAddon?.rootFolder;
@tracked selectedFolderDisplayName = this.args.configuredAddon?.rootFolderName;
@tracked currentItems: Item[] = [];
+ @tracked isWBGoogleDrive = false;
+ @tracked accountId!: string;
originalName = this.displayName;
originalRootFolder = this.selectedFolder;
@@ -34,6 +40,7 @@ export default class ConfiguredAddonEdit extends Component {
super(owner, args);
if (this.args.configuredAddon) {
if (this.args.configuredAddon instanceof ConfiguredStorageAddonModel) {
+ taskFor(this.loadExternalStorageService).perform();
this.defaultKwargs['itemType'] = ItemType.Folder;
}
if (this.args.configuredAddon instanceof ConfiguredCitationAddonModel) {
@@ -42,6 +49,7 @@ export default class ConfiguredAddonEdit extends Component {
}
if (this.args.authorizedAccount) {
if (this.args.authorizedAccount instanceof AuthorizedStorageAccountModel) {
+ taskFor(this.loadExternalStorageService).perform();
this.defaultKwargs['itemType'] = ItemType.Folder;
}
if (this.args.authorizedAccount instanceof AuthorizedCitationAccountModel) {
@@ -50,6 +58,28 @@ export default class ConfiguredAddonEdit extends Component {
}
}
+ /**
+ * This is called only to authorize because the current implementation will throw an
+ * error because the "root folder" is not yet set.
+ */
+ @task
+ @waitFor
+ async loadExternalStorageService() {
+ let external!: ExternalStorageServiceModel;
+ if (this.args.configuredAddon && this.args.configuredAddon instanceof ConfiguredStorageAddonModel) {
+ const baseAccount = await this.args.configuredAddon.baseAccount;
+ this.accountId = baseAccount?.id;
+ external = await this.args.configuredAddon.externalStorageService;
+ }
+ if (this.args.authorizedAccount && this.args.authorizedAccount instanceof AuthorizedStorageAccountModel) {
+ external = await this.args.authorizedAccount.externalStorageService;
+
+ this.accountId = this.args.authorizedAccount.id;
+ }
+
+ this.isWBGoogleDrive = external?.wbKey === 'googledrive';
+ }
+
get requiresRootFolder() {
return !(
this.args.authorizedAccount instanceof AuthorizedComputingAccountModel
@@ -58,6 +88,14 @@ export default class ConfiguredAddonEdit extends Component {
);
}
+ get isGoogleDrive(): boolean {
+ return this.isWBGoogleDrive;
+ }
+
+ get displayFileManager(): boolean {
+ return this.requiresRootFolder && !this.isGoogleDrive;
+ }
+
get invalidDisplayName() {
return !this.displayName || this.displayName?.trim().length === 0;
}
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 3dc6c476d5d..f362d65404e 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
@@ -62,3 +62,8 @@
.item-name {
white-space: normal;
}
+
+.picker-style {
+ white-space: pre-wrap;
+
+}
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 4d40bc76e44..44737b94b70 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
@@ -21,7 +21,7 @@
{{/if}}
- {{#if this.requiresRootFolder }}
+ {{#if this.displayFileManager}}
{{t 'addons.configure.selected-folder'}}
@@ -150,6 +150,13 @@
+ {{else}}
+
{{/if}}
+ {{#if this.isGoogleDrive}}
+
+ {{/if}}
+ {{#if this.isGoogleDrive}}
+
+ {{/if}}
{{/let}}
\ No newline at end of file
diff --git a/lib/osf-components/addon/components/file-browser/template.hbs b/lib/osf-components/addon/components/file-browser/template.hbs
index 924bf455b8e..7e76424f154 100644
--- a/lib/osf-components/addon/components/file-browser/template.hbs
+++ b/lib/osf-components/addon/components/file-browser/template.hbs
@@ -160,7 +160,11 @@
@isRegistration={{@manager.targetNode.isRegistration}}
/>
{{#if (and @manager.currentFolder.userCanUploadToHere @enableUpload)}}
-
+
{{/if}}
{{/if}}
diff --git a/lib/osf-components/addon/components/google-file-picker-widget/component.ts b/lib/osf-components/addon/components/google-file-picker-widget/component.ts
new file mode 100644
index 00000000000..8f104d89dcb
--- /dev/null
+++ b/lib/osf-components/addon/components/google-file-picker-widget/component.ts
@@ -0,0 +1,215 @@
+import Store from '@ember-data/store';
+import { action } from '@ember/object';
+import { waitFor } from '@ember/test-waiters';
+import Component from '@glimmer/component';
+import { tracked } from '@glimmer/tracking';
+import { task } from 'ember-concurrency';
+import { taskFor } from 'ember-concurrency-ts';
+import config from 'ember-osf-web/config/environment';
+import { Item } from 'ember-osf-web/models/addon-operation-invocation';
+import StorageManager from 'osf-components/components/storage-provider-manager/storage-manager/component';
+import { inject as service } from '@ember/service';
+import Intl from 'ember-intl/services/intl';
+
+const {
+ GOOGLE_FILE_PICKER_SCOPES,
+ GOOGLE_FILE_PICKER_API_KEY,
+ GOOGLE_FILE_PICKER_APP_ID,
+} = config.OSF.googleFilePicker;
+
+//
+// 📚 Interface for Expected Arguments
+//
+interface Args {
+ /**
+ * selectFolder
+ *
+ * @description
+ * A callback function passed into the component
+ * that accepts a partial Item object and handles it (e.g., selects a file).
+ */
+ selectFolder?: (a: Partial- ) => void;
+ onRegisterChild?: (a: GoogleFilePickerWidget) => void;
+ selectedFolderName?: string;
+ isFolderPicker: boolean;
+ rootFolderId: string;
+ manager: StorageManager;
+ accountId: string;
+}
+
+//
+// 📚 Extend Global Window Type
+//
+// Declares that `window` can optionally have a GoogleFilePickerWidget instance.
+// This allows safe typing when accessing it elsewhere.
+//
+declare global {
+ interface Window {
+ GoogleFilePickerWidget?: GoogleFilePickerWidget;
+ gapi?: any;
+ google?: any;
+ }
+}
+
+//
+// GoogleFilePickerWidget Component
+//
+// @description
+// An Ember Glimmer component that exposes itself to the global `window`
+// so that external JavaScript (like Google Picker API callbacks)
+// can interact with it directly.
+//
+export default class GoogleFilePickerWidget extends Component {
+ @service intl!: Intl;
+ @service store!: Store;
+ @tracked folderName!: string | undefined;
+ @tracked isFolderPicker = false;
+ @tracked openGoogleFilePicker = false;
+ @tracked visible = false;
+ @tracked isGFPDisabled = true;
+ pickerInited = false;
+ selectFolder: any = undefined;
+ accessToken!: string;
+ scopes = GOOGLE_FILE_PICKER_SCOPES;
+ apiKey = GOOGLE_FILE_PICKER_API_KEY;
+ appId = GOOGLE_FILE_PICKER_APP_ID;
+ mimeTypes = '';
+ parentId = '';
+ isMultipleSelect: boolean;
+ title!: string;
+
+ /**
+ * Constructor
+ *
+ * @description
+ * Initializes the GoogleFilePickerWidget component and exposes its key methods to the global `window` object
+ * for integration with external JavaScript (e.g., Google Picker API).
+ *
+ * - Sets `window.GoogleFilePickerWidget` to the current component instance (`this`),
+ * allowing external scripts to call methods like `filePickerCallback()`.
+ * - Captures the closure action `selectFolder` from `this.args` and assigns it directly to `window.selectFolder`,
+ * preserving the correct closure reference even outside of Ember's internal context.
+ *
+ * @param owner - The owner/context passed by Ember at component instantiation.
+ * @param args - The arguments passed to the component, including closure actions like `selectFolder`.
+ */
+ constructor(owner: unknown, args: Args) {
+ super(owner, args);
+
+ window.GoogleFilePickerWidget = this;
+ this.selectFolder = this.args.selectFolder;
+ this.mimeTypes = this.args.isFolderPicker ? 'application/vnd.google-apps.folder' : '';
+ this.parentId = this.args.isFolderPicker ? '': this.args.rootFolderId;
+ this.title = this.args.isFolderPicker ?
+ this.intl.t('addons.configure.google-file-picker.root-folder-title') :
+ this.intl.t('addons.configure.google-file-picker.file-folder-title');
+ this.isMultipleSelect = !this.args.isFolderPicker;
+ this.isFolderPicker = this.args.isFolderPicker;
+
+
+ this.folderName = this.args.selectedFolderName;
+
+ taskFor(this.loadOauthToken).perform();
+ }
+
+ @task
+ @waitFor
+ private async loadOauthToken(): Promise{
+ if (this.args.accountId) {
+ const authorizedStorageAccount = await this.store.
+ findRecord('authorized-storage-account', this.args.accountId);
+ authorizedStorageAccount.serializeOauthToken = true;
+ const token = await authorizedStorageAccount.save();
+ this.accessToken = token.oauthToken;
+ this.isGFPDisabled = this.accessToken ? false : true;
+ }
+ }
+
+ /**
+ * filePickerCallback
+ *
+ * @description
+ * Action triggered when a file is selected via an external picker.
+ * Logs the file data and notifies the parent system by calling `selectFolder`.
+ *
+ * @param file - The file object selected (format determined by external API)
+ */
+ @action
+ filePickerCallback(data: any) {
+ if (this.selectFolder !== undefined) {
+ this.folderName = data.name;
+ this.selectFolder({
+ itemName: data.name,
+ itemId: data.id,
+ });
+ } else {
+ this.args.manager.reload();
+ }
+ }
+
+ @action
+ registerComponent() {
+ if (this.args.onRegisterChild) {
+ this.args.onRegisterChild(this); // Pass the child's instance to the parent
+ }
+ }
+
+ willDestroy() {
+ super.willDestroy();
+ this.pickerInited = false;
+ }
+
+
+ /**
+ * Callback after api.js is loaded.
+ */
+ gapiLoaded() {
+ window.gapi.load('client:picker', this.initializePicker.bind(this));
+ }
+
+ /**
+ * Callback after the API client is loaded. Loads the
+ * discovery doc to initialize the API.
+ */
+ async initializePicker() {
+ this.pickerInited = true;
+ if (this.isFolderPicker) {
+ this.visible = true;
+ }
+ }
+
+ /**
+ * Create and render a Picker object for searching images.
+ */
+ @action
+ createPicker() {
+ const googlePickerView = new window.google.picker.DocsView(window.google.picker.ViewId.DOCS);
+ googlePickerView.setSelectFolderEnabled(true);
+ googlePickerView.setMimeTypes(this.mimeTypes);
+ googlePickerView.setIncludeFolders(true);
+ googlePickerView.setParent(this.parentId);
+
+ const picker = new window.google.picker.PickerBuilder()
+ .enableFeature(this.isMultipleSelect ? window.google.picker.Feature.MULTISELECT_ENABLED : '')
+ .setDeveloperKey(this.apiKey)
+ .setAppId(this.appId)
+ .addView(googlePickerView)
+ .setTitle(this.title)
+ .setOAuthToken(this.accessToken)
+ .setCallback(this.pickerCallback.bind(this))
+ .build();
+ picker.setVisible(true);
+ }
+
+ /**
+ * Displays the file details of the user's selection.
+ * @param {object} data - Containers the user selection from the picker
+ */
+ async pickerCallback(data: any) {
+ if (data.action === window.google.picker.Action.PICKED) {
+ this.filePickerCallback(data.docs[0]);
+ }
+ }
+}
+
+
diff --git a/lib/osf-components/addon/components/google-file-picker-widget/styles.scss b/lib/osf-components/addon/components/google-file-picker-widget/styles.scss
new file mode 100644
index 00000000000..a1e4c242a9c
--- /dev/null
+++ b/lib/osf-components/addon/components/google-file-picker-widget/styles.scss
@@ -0,0 +1,25 @@
+// stylelint-disable max-nesting-depth, selector-max-compound-selectors, selector-no-qualifying-type
+
+.google-file-picker-container {
+ width: 0;
+
+ &.file-picker {
+ width: 100%;
+ }
+
+ .instruction-container {
+ width: 100%;
+ }
+
+ .action-container {
+ width: 100%;
+
+ .authorize-button {
+ visibility: hidden;
+
+ &.visible {
+ visibility: visible;
+ }
+ }
+ }
+}
diff --git a/lib/osf-components/addon/components/google-file-picker-widget/template.hbs b/lib/osf-components/addon/components/google-file-picker-widget/template.hbs
new file mode 100644
index 00000000000..f00ea98b7ee
--- /dev/null
+++ b/lib/osf-components/addon/components/google-file-picker-widget/template.hbs
@@ -0,0 +1,27 @@
+
+ {{#if this.isFolderPicker}}
+
+ {{#if this.folderName}}
+
+
+ {{~t 'addons.configure.google-file-picker.selected-folder'~}}:
+
+
+ {{this.folderName}}
+
+
+ {{/if}}
+
+
+
+
+ {{/if}}
+
+
\ No newline at end of file
diff --git a/lib/osf-components/addon/components/registries/finalize-registration-modal/manager/component.ts b/lib/osf-components/addon/components/registries/finalize-registration-modal/manager/component.ts
index db0835f6045..2ca16819ca6 100644
--- a/lib/osf-components/addon/components/registries/finalize-registration-modal/manager/component.ts
+++ b/lib/osf-components/addon/components/registries/finalize-registration-modal/manager/component.ts
@@ -14,6 +14,10 @@ import RegistrationModel from 'ember-osf-web/models/registration';
import captureException, { getApiErrorMessage } from 'ember-osf-web/utils/capture-exception';
import DraftRegistrationManager from 'registries/drafts/draft/draft-registration-manager';
+import buildChangeset from 'ember-osf-web/utils/build-changeset';
+import { ValidationObject } from 'ember-changeset-validations';
+import { validateFormat, validateLength } from 'ember-changeset-validations/validators';
+import { DOIRegex } from 'ember-osf-web/utils/doi';
import template from './template';
export interface FinalizeRegistrationModalManager {
@@ -25,6 +29,11 @@ export interface FinalizeRegistrationModalManager {
draftManager: DraftRegistrationManager;
}
+interface ManualDoiAndGuidForm {
+ manualDoi: string;
+ manualGuid: string;
+}
+
@layout(template)
@tagName('')
export default class FinalizeRegistrationModalManagerComponent extends Component
@@ -32,9 +41,32 @@ export default class FinalizeRegistrationModalManagerComponent extends Component
@service intl!: Intl;
@service toast!: Toast;
+ // validationFunction() {
+ // debugger;
+ // }
+ manualDoiAndGuidFormChangesetValidation: ValidationObject = {
+ manualDoi: validateFormat({
+ allowBlank: true,
+ allowNone: true,
+ ignoreBlank: true,
+ regex: DOIRegex,
+ type: 'invalid_doi',
+ }),
+ // manualDoi: this.validationFunction,
+ manualGuid: validateLength({
+ allowBlank: true,
+ min:5,
+ type: 'greaterThanOrEqualTo',
+ translationArgs: {
+ description: this.intl.t('preprints.submit.step-title.guid'),
+ gte: '5 characters',
+ },
+ }),
+ };
// Required arguments
registration!: RegistrationModel;
draftManager!: DraftRegistrationManager;
+ guidAndDoiFormChangeset!: any;
// Optional arguments
onSubmitRegistration?: (registrationId: string) => void;
@@ -67,6 +99,14 @@ export default class FinalizeRegistrationModalManagerComponent extends Component
didReceiveAttrs() {
assert('finalize-registration-modal::manager must have a registration', Boolean(this.registration));
+ this.guidAndDoiFormChangeset = buildChangeset(this.registration, this.manualDoiAndGuidFormChangesetValidation);
+ }
+
+ @action
+ validateManualDoiAndGuid() {
+ // debugger;
+ this.guidAndDoiFormChangeset.validate();
+ this.guidAndDoiFormChangeset.execute();
}
@action
diff --git a/lib/osf-components/addon/components/registries/finalize-registration-modal/manager/template.hbs b/lib/osf-components/addon/components/registries/finalize-registration-modal/manager/template.hbs
index e6359a0caa2..fc945e23a5c 100644
--- a/lib/osf-components/addon/components/registries/finalize-registration-modal/manager/template.hbs
+++ b/lib/osf-components/addon/components/registries/finalize-registration-modal/manager/template.hbs
@@ -5,4 +5,6 @@
hasEmbargoEndDate=this.hasEmbargoEndDate
submittingRegistration=this.submittingRegistration
draftManager=this.draftManager
+ guidAndDoiFormChangeset=this.guidAndDoiFormChangeset
+ validateManualDoiAndGuid=this.validateManualDoiAndGuid
)}}
diff --git a/lib/osf-components/addon/components/registries/finalize-registration-modal/template.hbs b/lib/osf-components/addon/components/registries/finalize-registration-modal/template.hbs
index 281eb00cc85..cd53e157514 100644
--- a/lib/osf-components/addon/components/registries/finalize-registration-modal/template.hbs
+++ b/lib/osf-components/addon/components/registries/finalize-registration-modal/template.hbs
@@ -43,6 +43,44 @@
/>
{{/if}}
+ {{#if (feature-flag 'manual_doi_and_guid')}}
+
+ {{#let (unique-id 'manualDoi') as |manualDoiField|}}
+
+
+ {{/let}}
+ {{#let (unique-id 'manualGuid') as |manualGuidField|}}
+
+
+ {{/let}}
+
+ {{/if}}