diff --git a/client/package.json b/client/package.json index de12b9f71852..86720d29ca7d 100644 --- a/client/package.json +++ b/client/package.json @@ -45,6 +45,8 @@ "@types/jest": "^29.5.12", "@vueuse/core": "^10.5.0", "@vueuse/math": "^10.9.0", + "ag-grid-community": "^30", + "ag-grid-vue": "^30", "assert": "^2.1.0", "axios": "^1.6.2", "babel-runtime": "^6.26.0", @@ -106,11 +108,13 @@ "vega-embed": "^6.26.0", "vega-lite": "^5.21.0", "vue": "^2.7.14", + "vue-class-component": "^7.2.6", "vue-echarts": "^7.0.3", "vue-infinite-scroll": "^2.0.2", "vue-multiselect": "^2.1.7", "vue-observe-visibility": "^1.0.0", "vue-prismjs": "^1.2.0", + "vue-property-decorator": "^9.1.2", "vue-router": "^3.6.5", "vue-rx": "^6.2.0", "vue-virtual-scroll-list": "^2.3.5", diff --git a/client/src/api/datasetCollections.ts b/client/src/api/datasetCollections.ts index f659ec4982c4..08c53058deff 100644 --- a/client/src/api/datasetCollections.ts +++ b/client/src/api/datasetCollections.ts @@ -1,4 +1,12 @@ -import { type CollectionEntry, type DCESummary, GalaxyApi, type HDCADetailed, type HDCASummary, isHDCA } from "@/api"; +import { + type CollectionEntry, + type components, + type DCESummary, + GalaxyApi, + type HDCADetailed, + type HDCASummary, + isHDCA, +} from "@/api"; import { rethrowSimple } from "@/utils/simple-error"; const DEFAULT_LIMIT = 50; @@ -76,3 +84,44 @@ export async function fetchElementsFromCollection(params: { limit: params.limit ?? DEFAULT_LIMIT, }); } + +export type CollectionElementIdentifiers = components["schemas"]["CollectionElementIdentifier"][]; +export type CreateNewCollectionPayload = components["schemas"]["CreateNewCollectionPayload"]; + +export type NewCollectionOptions = { + name: string; + element_identifiers: CollectionElementIdentifiers; + collection_type: string; + history_id: string; + copy_elements?: boolean; + hide_source_items?: boolean; +}; + +export function createCollectionPayload(options: NewCollectionOptions): CreateNewCollectionPayload { + return { + name: options.name, + history_id: options.history_id, + element_identifiers: options.element_identifiers, + collection_type: options.collection_type, + instance_type: "history", + fields: "auto", + copy_elements: options.copy_elements || true, + hide_source_items: options.hide_source_items || true, + }; +} + +export async function createHistoryDatasetCollectionInstanceSimple(options: NewCollectionOptions) { + const payload = createCollectionPayload(options); + return createHistoryDatasetCollectionInstanceFull(payload); +} + +export async function createHistoryDatasetCollectionInstanceFull(payload: CreateNewCollectionPayload) { + const { data, error } = await GalaxyApi().POST("/api/dataset_collections", { + body: payload, + }); + + if (error) { + rethrowSimple(error); + } + return data; +} diff --git a/client/src/api/datasets.ts b/client/src/api/datasets.ts index 6b8f0606537c..fb4809c8487a 100644 --- a/client/src/api/datasets.ts +++ b/client/src/api/datasets.ts @@ -67,6 +67,7 @@ export async function copyDataset( // TODO: Investigate. These should be optional, but the API requires explicit null values? type, copy_elements: null, + fields: null, hide_source_items: null, instance_type: null, }, diff --git a/client/src/api/index.ts b/client/src/api/index.ts index 6f9ce9d0213d..5a8c0ad7b87e 100644 --- a/client/src/api/index.ts +++ b/client/src/api/index.ts @@ -306,10 +306,17 @@ export type DatasetTransform = { */ export type MessageException = components["schemas"]["MessageExceptionModel"]; +export type FieldDict = components["schemas"]["FieldDict"]; +export type FieldType = FieldDict["type"]; + export type StoreExportPayload = components["schemas"]["StoreExportPayload"]; export type ModelStoreFormat = components["schemas"]["ModelStoreFormat"]; export type ObjectExportTaskResponse = components["schemas"]["ObjectExportTaskResponse"]; export type ExportObjectRequestMetadata = components["schemas"]["ExportObjectRequestMetadata"]; export type ExportObjectResultMetadata = components["schemas"]["ExportObjectResultMetadata"]; +export type SampleSheetColumnDefinition = components["schemas"]["SampleSheetColumnDefinitionModel"]; +export type SampleSheetColumnDefinitionType = SampleSheetColumnDefinition["type"]; +export type SampleSheetColumnDefinitions = SampleSheetColumnDefinition[] | null; + export type AsyncTaskResultSummary = components["schemas"]["AsyncTaskResultSummary"]; diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 3a7c79e4de31..f4f20736c72c 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -4060,6 +4060,40 @@ export interface paths { patch?: never; trace?: never; }; + "/api/sample_sheet_workbook/generate": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Create an XLSX workbook for a sample sheet definition. */ + get: operations["dataset_collections__workbook_download"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/sample_sheet_workbook/parse": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Parse an XLSX workbook for a sample sheet definition and supplied file contents. */ + post: operations["dataset_collections__workbook_parse"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/short_term_storage/{storage_request_id}": { parameters: { query?: never; @@ -6922,6 +6956,11 @@ export interface components { * @description The type of the collection, can be `list`, `paired`, or define subcollections using `:` as separator like `list:paired` or `list:list`. */ collection_type?: string | null; + /** + * Column Definitions + * @description Specify definitions for row data if collection_type if sample_sheet + */ + column_definitions?: components["schemas"]["SampleSheetColumnDefinition"][] | null; /** * Content * @description Depending on the `source` it can be: @@ -6948,6 +6987,12 @@ export interface components { * @description List of elements that should be in the new collection. */ element_identifiers?: components["schemas"]["CollectionElementIdentifier"][] | null; + /** + * Fields + * @description List of fields to create for this collection. Set to 'auto' to guess fields from identifiers. + * @default [] + */ + fields: string | components["schemas"]["FieldDict"][] | null; /** * Folder Id * @description The ID of the library folder that will contain the collection. Required if `instance_type=library`. @@ -6975,6 +7020,13 @@ export interface components { * @description The name of the new collection. */ name?: string | null; + /** + * Row data + * @description Specify rows of metadata data corresponding to an indentifier if collection_type is sample_sheet + */ + rows?: { + [key: string]: (string | number | boolean)[]; + } | null; /** * Source * @description The source of the content. Can be other history element to be copied or library elements. @@ -7129,6 +7181,11 @@ export interface components { * @description The type of the collection, can be `list`, `paired`, or define subcollections using `:` as separator like `list:paired` or `list:list`. */ collection_type?: string | null; + /** + * Column Definitions + * @description Specify definitions for row data if collection_type if sample_sheet + */ + column_definitions?: components["schemas"]["SampleSheetColumnDefinition"][] | null; /** * Copy Elements * @description Whether to create a copy of the source HDAs for the new collection. @@ -7140,6 +7197,12 @@ export interface components { * @description List of elements that should be in the new collection. */ element_identifiers?: components["schemas"]["CollectionElementIdentifier"][] | null; + /** + * Fields + * @description List of fields to create for this collection. Set to 'auto' to guess fields from identifiers. + * @default [] + */ + fields: string | components["schemas"]["FieldDict"][] | null; /** * Folder Id * @description The ID of the library folder that will contain the collection. Required if `instance_type=library`. @@ -7167,6 +7230,13 @@ export interface components { * @description The name of the new collection. */ name?: string | null; + /** + * Row data + * @description Specify rows of metadata data corresponding to an indentifier if collection_type is sample_sheet + */ + rows?: { + [key: string]: (string | number | boolean)[]; + } | null; }; /** CreatePagePayload */ CreatePagePayload: { @@ -7765,6 +7835,11 @@ export interface components { * @description Dataset Collection Element summary information. */ DCESummary: { + /** + * Columns + * @description A row (or list of columns) of data associated with this element + */ + columns?: (string | number | boolean)[] | null; /** * Element Identifier * @description The actual name of this element. @@ -9080,6 +9155,17 @@ export interface components { /** Hash Value */ hash_value: string; }; + /** FieldDict */ + FieldDict: { + /** Format */ + format?: string | null; + /** Name */ + name: string; + /** Type */ + type: + | ("File" | "null" | "boolean" | "int" | "float" | "string") + | ("File" | "null" | "boolean" | "int" | "float" | "string")[]; + }; /** FileDataElement */ FileDataElement: { /** Md5 */ @@ -10347,6 +10433,11 @@ export interface components { * @description The type of the collection, can be `list`, `paired`, or define subcollections using `:` as separator like `list:paired` or `list:list`. */ collection_type?: string | null; + /** + * Column Definitions + * @description Column data associated with each element of this collection. + */ + column_definitions?: components["schemas"]["SampleSheetColumnDefinition"][] | null; /** * Contents URL * @description The relative URL to access the contents of this History. @@ -10486,6 +10577,11 @@ export interface components { * @description The type of the collection, can be `list`, `paired`, or define subcollections using `:` as separator like `list:paired` or `list:list`. */ collection_type: string; + /** + * Column Definitions + * @description Column data associated with each element of this collection. + */ + column_definitions?: components["schemas"]["SampleSheetColumnDefinition"][] | null; /** * Contents URL * @description The relative URL to access the contents of this History. @@ -11572,6 +11668,40 @@ export interface components { */ uri: string; }; + /** InRangeParameterValidatorModel */ + InRangeParameterValidatorModel: { + /** + * Exclude Max + * @default false + */ + exclude_max: boolean; + /** + * Exclude Min + * @default false + */ + exclude_min: boolean; + /** + * Implicit + * @default false + */ + implicit: boolean; + /** Max */ + max?: number | null; + /** Message */ + message?: string | null; + /** Min */ + min?: number | null; + /** + * Negate + * @default false + */ + negate: boolean; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "in_range"; + }; /** InputDataCollectionStep */ InputDataCollectionStep: { /** @@ -13209,6 +13339,30 @@ export interface components { */ LIBRARY_MODIFY_in: string[] | string | null; }; + /** LengthParameterValidatorModel */ + LengthParameterValidatorModel: { + /** + * Implicit + * @default false + */ + implicit: boolean; + /** Max */ + max?: number | null; + /** Message */ + message?: string | null; + /** Min */ + min?: number | null; + /** + * Negate + * @default false + */ + negate: boolean; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "length"; + }; /** LibraryAvailablePermissions */ LibraryAvailablePermissions: { /** @@ -14892,6 +15046,26 @@ export interface components { * @default [] */ PageSummaryList: components["schemas"]["PageSummary"][]; + /** ParseWorkbook */ + ParseWorkbook: { + /** + * Column Descriptions + * @description A description of the columns expected in the workbook after the first columns described by 'prefix_columns_type' + */ + column_definitions: components["schemas"]["RootModel_List_SampleSheetColumnDefinitionModel__"]; + /** + * Workbook Content (Base 64 encoded) + * @description The workbook content (the contents of the xlsx file) that have been base64 encoded. + */ + content: string; + }; + /** ParsedWorkbook */ + ParsedWorkbook: { + /** Rows */ + rows: { + [key: string]: string | number | boolean; + }[]; + }; /** PastedDataElement */ PastedDataElement: { /** Md5 */ @@ -15470,6 +15644,35 @@ export interface components { /** Workflow */ workflow: string; }; + /** + * RegexParameterValidatorModel + * @description Check if a regular expression **matches** the value, i.e. appears + * at the beginning of the value. To enforce a match of the complete value use + * ``$`` at the end of the expression. The expression is given is the content + * of the validator tag. Note that for ``selects`` each option is checked + * separately. + */ + RegexParameterValidatorModel: { + /** Expression */ + expression: string; + /** + * Implicit + * @default false + */ + implicit: boolean; + /** Message */ + message?: string | null; + /** + * Negate + * @default false + */ + negate: boolean; + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: "regex"; + }; /** ReloadFeedback */ ReloadFeedback: { /** Failed */ @@ -15667,6 +15870,54 @@ export interface components { RootModel_Dict_str__int__: { [key: string]: number; }; + /** RootModel[List[SampleSheetColumnDefinitionModel]] */ + RootModel_List_SampleSheetColumnDefinitionModel__: components["schemas"]["SampleSheetColumnDefinitionModel"][]; + /** SampleSheetColumnDefinition */ + SampleSheetColumnDefinition: { + /** Default Value */ + default_value: string | number | boolean | null; + /** Description */ + description?: string | null; + /** Name */ + name: string; + /** Restrictions */ + restrictions?: string[] | null; + /** Suggestions */ + suggestions?: string[] | null; + /** + * Type + * @enum {string} + */ + type: "string" | "int" | "float" | "boolean"; + /** Validators */ + validators?: Record[] | null; + }; + /** SampleSheetColumnDefinitionModel */ + SampleSheetColumnDefinitionModel: { + /** Default Value */ + default_value?: string | number | boolean | null; + /** Description */ + description?: string | null; + /** Name */ + name: string; + /** Restrictions */ + restrictions?: (string | number | boolean)[] | null; + /** Suggestions */ + suggestions?: (string | number | boolean)[] | null; + /** + * Type + * @enum {string} + */ + type: "string" | "int" | "float" | "boolean"; + /** Validators */ + validators?: + | ( + | components["schemas"]["RegexParameterValidatorModel"] + | components["schemas"]["InRangeParameterValidatorModel"] + | components["schemas"]["LengthParameterValidatorModel"] + )[] + | null; + }; /** SearchJobsPayload */ SearchJobsPayload: { /** @@ -31817,6 +32068,95 @@ export interface operations { }; }; }; + dataset_collections__workbook_download: { + parameters: { + query: { + /** @description Base64 encoding of column definitions. */ + column_definitions: string; + /** @description Filename of the workbook download to generate */ + filename?: string | null; + }; + header?: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + "run-as"?: string | null; + }; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Request Error */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + }; + }; + dataset_collections__workbook_parse: { + parameters: { + query?: never; + header?: { + /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ + "run-as"?: string | null; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ParseWorkbook"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ParsedWorkbook"]; + }; + }; + /** @description Request Error */ + "4XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageExceptionModel"]; + }; + }; + }; + }; serve_api_short_term_storage__storage_request_id__get: { parameters: { query?: never; diff --git a/client/src/components/Collections/BuildFileSetWizard.vue b/client/src/components/Collections/BuildFileSetWizard.vue new file mode 100644 index 000000000000..c74a53202c37 --- /dev/null +++ b/client/src/components/Collections/BuildFileSetWizard.vue @@ -0,0 +1,220 @@ + + + diff --git a/client/src/components/Collections/CollectionCreatorModal.vue b/client/src/components/Collections/CollectionCreatorModal.vue index 8db25d243c6d..661a7c3392ca 100644 --- a/client/src/components/Collections/CollectionCreatorModal.vue +++ b/client/src/components/Collections/CollectionCreatorModal.vue @@ -4,18 +4,20 @@ import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { BAlert, BLink, BModal } from "bootstrap-vue"; import { computed, ref, watch } from "vue"; -import type { HDASummary, HistoryItemSummary, HistorySummary } from "@/api"; -import { createDatasetCollection } from "@/components/History/model/queries"; +import type { HistoryItemSummary } from "@/api"; +import { createHistoryDatasetCollectionInstanceFull, type CreateNewCollectionPayload } from "@/api/datasetCollections"; import { useCollectionBuilderItemsStore } from "@/stores/collectionBuilderItemsStore"; import { useHistoryStore } from "@/stores/historyStore"; import localize from "@/utils/localization"; import { orList } from "@/utils/strings"; -import type { CollectionType, DatasetPair } from "../History/adapters/buildCollectionModal"; +import type { CollectionType } from "../History/adapters/buildCollectionModal"; +import { type SupportedPairedOrPairedBuilderCollectionTypes } from "./common/useCollectionCreator"; import ListCollectionCreator from "./ListCollectionCreator.vue"; import PairCollectionCreator from "./PairCollectionCreator.vue"; -import PairedListCollectionCreator from "./PairedListCollectionCreator.vue"; +import PairedOrUnpairedListCollectionCreator from "./PairedOrUnpairedListCollectionCreator.vue"; +import SampleSheetCollectionCreator from "./SampleSheetCollectionCreator.vue"; import Heading from "@/components/Common/Heading.vue"; import GenericItem from "@/components/History/Content/GenericItem.vue"; import LoadingSpan from "@/components/LoadingSpan.vue"; @@ -30,6 +32,8 @@ interface Props { fromRulesInput?: boolean; hideModalOnCreate?: boolean; filterText?: string; + useBetaComponents?: boolean; + fileSourcesConfigured: boolean; } const props = defineProps(); @@ -67,6 +71,15 @@ const historyDatasets = computed(() => { return []; } }); +const pairedOrUnpairedSupportedCollectionType = computed(() => { + if ( + ["list:paired", "list:list", "list:paired_or_unpaired", "list:list:paired"].indexOf(props.collectionType) !== -1 + ) { + return props.collectionType as SupportedPairedOrPairedBuilderCollectionTypes; + } else { + return null; + } +}); /** Flag for the initial fetch of history items */ const initialFetch = ref(false); @@ -141,63 +154,11 @@ const modalTitle = computed(() => { }); // Methods -function createListCollection(elements: HDASummary[], name: string, hideSourceItems: boolean) { - const returnedElems = elements.map((element) => ({ - id: element.id, - name: element.name, - //TODO: this allows for list:list even if the implementation does not - reconcile - src: "src" in element ? element.src : element.history_content_type == "dataset" ? "hda" : "hdca", - })); - return createHDCA(returnedElems, "list", name, hideSourceItems); -} - -function createListPairedCollection(elements: DatasetPair[], name: string, hideSourceItems: boolean) { - const returnedElems = elements.map((pair) => ({ - collection_type: "paired", - src: "new_collection", - name: pair.name, - element_identifiers: [ - { - name: "forward", - id: pair.forward.id, - src: "src" in pair.forward ? pair.forward.src : "hda", - }, - { - name: "reverse", - id: pair.reverse.id, - src: "src" in pair.reverse ? pair.reverse.src : "hda", - }, - ], - })); - return createHDCA(returnedElems, "list:paired", name, hideSourceItems); -} - -function createPairedCollection(elements: DatasetPair, name: string, hideSourceItems: boolean) { - const { forward, reverse } = elements; - const returnedElems = [ - { name: "forward", src: "src" in forward ? forward.src : "hda", id: forward.id }, - { name: "reverse", src: "src" in reverse ? reverse.src : "hda", id: reverse.id }, - ]; - return createHDCA(returnedElems, "paired", name, hideSourceItems); -} -async function createHDCA( - element_identifiers: any[], - collection_type: CollectionType, - name: string, - hide_source_items: boolean, - options = {} -) { +async function createHDCA(payload: CreateNewCollectionPayload) { try { creatingCollection.value = true; - const collection = await createDatasetCollection(history.value as HistorySummary, { - collection_type, - name, - hide_source_items, - element_identifiers, - options, - }); - + const collection = await createHistoryDatasetCollectionInstanceFull(payload); emit("created-collection", collection); createdCollection.value = collection; @@ -293,16 +254,19 @@ function resetModal() { :default-hide-source-items="props.defaultHideSourceItems" :from-selection="fromSelection" :extensions="props.extensions" - @clicked-create="createListCollection" + mode="modal" + @on-create="createHDCA" @on-cancel="hideModal" /> - + diff --git a/client/src/components/Collections/ListCollectionCreator.vue b/client/src/components/Collections/ListCollectionCreator.vue index ca9e25aec416..cb8377eda4fd 100644 --- a/client/src/components/Collections/ListCollectionCreator.vue +++ b/client/src/components/Collections/ListCollectionCreator.vue @@ -9,12 +9,14 @@ import { computed, ref, watch } from "vue"; import draggable from "vuedraggable"; import type { HDASummary, HistoryItemSummary } from "@/api"; +import { type CollectionElementIdentifiers, type CreateNewCollectionPayload } from "@/api/datasetCollections"; import { useConfirmDialog } from "@/composables/confirmDialog"; import { Toast } from "@/composables/toast"; -import STATES from "@/mvc/dataset/states"; -import { useDatatypesMapperStore } from "@/stores/datatypesMapperStore"; import localize from "@/utils/localization"; +import { stripExtension, useUpdateIdentifiersForRemoveExtensions } from "./common/stripExtension"; +import { type Mode, useCollectionCreator } from "./common/useCollectionCreator"; + import FormSelectMany from "../Form/Elements/FormSelectMany/FormSelectMany.vue"; import CollectionCreator from "@/components/Collections/common/CollectionCreator.vue"; import DatasetCollectionElementView from "@/components/Collections/ListDatasetCollectionElementView.vue"; @@ -27,13 +29,16 @@ interface Props { defaultHideSourceItems?: boolean; fromSelection?: boolean; extensions?: string[]; + mode: Mode; } const props = defineProps(); const emit = defineEmits<{ - (e: "clicked-create", workingElements: HDASummary[], collectionName: string, hideSourceItems: boolean): void; + (e: "on-create", options: CreateNewCollectionPayload): void; (e: "on-cancel"): void; + (e: "name", value: string): void; + (e: "input-valid", value: boolean): void; }>(); const state = ref("build"); @@ -41,9 +46,10 @@ const duplicateNames = ref([]); const invalidElements = ref([]); const workingElements = ref([]); const selectedDatasetElements = ref([]); -const hideSourceItems = ref(props.defaultHideSourceItems || false); const atLeastOneElement = ref(true); +const { updateIdentifierIfUnchanged } = useUpdateIdentifiersForRemoveExtensions(props); + const atLeastOneDatasetIsSelected = computed(() => { return selectedDatasetElements.value.length > 0; }); @@ -72,12 +78,16 @@ const allElementsAreInvalid = computed(() => { /** If not `fromSelection`, the list of elements that will become the collection */ const inListElements = ref([]); -// variables for datatype mapping and then filtering -const datatypesMapperStore = useDatatypesMapperStore(); -const datatypesMapper = computed(() => datatypesMapperStore.datatypesMapper); - -/** Are we filtering by datatype? */ -const filterExtensions = computed(() => !!datatypesMapper.value && !!props.extensions?.length); +const { + removeExtensions, + hideSourceItems, + onUpdateHideSourceItems, + isElementInvalid, + collectionName, + onUpdateCollectionName, + onCollectionCreate, + showButtonsForModal, +} = useCollectionCreator(props, emit); // ----------------------------------------------------------------------- process raw list /** set up main data */ @@ -95,12 +105,22 @@ function _elementsSetUp() { // reverse the order of the elements to emulate what we have in the history panel workingElements.value.reverse(); + if (removeExtensions.value) { + workingElements.value.forEach((el) => { + if (el.name) { + el.name = stripExtension(el.name); + } + }); + } else { + // + } + // for inListElements, reset their values (in order) to datasets from workingElements const inListElementsPrev = inListElements.value; inListElements.value = []; inListElementsPrev.forEach((prevElem) => { const element = workingElements.value.find((e) => e.id === prevElem.id); - const problem = _isElementInvalid(prevElem); + const problem = isElementInvalid(prevElem); if (element) { inListElements.value.push(element); @@ -134,7 +154,7 @@ function _elementsSetUp() { // /** separate working list into valid and invalid elements for this collection */ function _validateElements() { workingElements.value = workingElements.value.filter((element) => { - var problem = _isElementInvalid(element); + var problem = isElementInvalid(element); if (problem) { invalidElements.value.push(element.name + " " + problem); @@ -146,31 +166,13 @@ function _validateElements() { return workingElements.value; } -/** describe what is wrong with a particular element if anything */ -function _isElementInvalid(element: HistoryItemSummary): string | null { - if (element.history_content_type === "dataset_collection") { - return localize("is a collection, this is not allowed"); - } - - var validState = element.state === STATES.OK || STATES.NOT_READY_STATES.includes(element.state as string); - - if (!validState) { - return localize("has errored, is paused, or is not accessible"); - } - - if (element.deleted || element.purged) { - return localize("has been deleted or purged"); - } - - // is the element's extension not a subtype of any of the required extensions? - if ( - filterExtensions.value && - element.extension && - !datatypesMapper.value?.isSubTypeOfAny(element.extension, props.extensions!) - ) { - return localize(`has an invalid extension: ${element.extension}`); - } - return null; +function removeExtensionsToggle() { + removeExtensions.value = !removeExtensions.value; + const removeExtensionsValue = removeExtensions.value; + workingElements.value.forEach((el) => { + updateIdentifierIfUnchanged(el, removeExtensionsValue); + }); + _mangleDuplicateNames(); } // /** mangle duplicate names using a mac-like '(counter)' addition to any duplicates */ @@ -231,7 +233,7 @@ function clickSelectAll() { } const { confirm } = useConfirmDialog(); -async function clickedCreate(collectionName: string) { +async function attemptCreate() { checkForDuplicates(); const returnedElements = props.fromSelection ? workingElements.value : inListElements.value; @@ -247,10 +249,18 @@ async function clickedCreate(collectionName: string) { } if (state.value !== "error" && (atLeastOneElement.value || confirmed)) { - emit("clicked-create", returnedElements, collectionName, hideSourceItems.value); + const identifiers = returnedElements.map((element) => ({ + id: element.id, + name: element.name, + //TODO: this allows for list:list even if the implementation does not - reconcile + src: "src" in element ? element.src : element.history_content_type == "dataset" ? "hda" : "hdca", + })) as CollectionElementIdentifiers; + onCollectionCreate("list", identifiers); } } +defineExpose({ attemptCreate }); + function checkForDuplicates() { var valid = true; var existingNames: { [key: string]: boolean } = {}; @@ -291,10 +301,6 @@ function compareNames(a: HDASummary, b: HDASummary) { return 0; } -function onUpdateHideSourceItems(newHideSourceItems: boolean) { - hideSourceItems.value = newHideSourceItems; -} - watch( () => props.initialElements, () => { @@ -304,21 +310,11 @@ watch( { immediate: true } ); -watch( - () => datatypesMapper.value, - async (mapper) => { - if (props.extensions?.length && !mapper) { - await datatypesMapperStore.createMapper(); - } - }, - { immediate: true } -); - function addUploadedFiles(files: HDASummary[]) { const returnedElements = props.fromSelection ? workingElements : inListElements; files.forEach((f) => { const file = props.fromSelection ? f : workingElements.value.find((e) => e.id === f.id); - const problem = _isElementInvalid(f); + const problem = isElementInvalid(f); if (file && !returnedElements.value.find((e) => e.id === file.id)) { returnedElements.value.push(file); } else if (problem) { @@ -344,24 +340,6 @@ function renameElement(element: any, name: string) { element.name = name; } } - -//TODO: issue #9497 -// const removeExtensions = ref(true); -// removeExtensionsToggle: function () { -// this.removeExtensions = !this.removeExtensions; -// if (this.removeExtensions == true) { -// this.removeExtensionsFn(); -// } -// }, -// removeExtensionsFn: function () { -// workingElements.value.forEach((e) => { -// var lastDotIndex = e.lastIndexOf("."); -// if (lastDotIndex > 0) { -// var extension = e.slice(lastDotIndex, e.length); -// e = e.replace(extension, ""); -// } -// }); -// },