diff --git a/client/modules/_hooks/src/workspace/types.ts b/client/modules/_hooks/src/workspace/types.ts index 9d73e2ef6..febcb0a19 100644 --- a/client/modules/_hooks/src/workspace/types.ts +++ b/client/modules/_hooks/src/workspace/types.ts @@ -1,6 +1,7 @@ export type TParameterSetNotes = { isHidden?: boolean; fieldType?: string; + inputType?: string; validator?: { regex: string; message: string; diff --git a/client/modules/workspace/src/AppsWizard/AppsFormSchema.ts b/client/modules/workspace/src/AppsWizard/AppsFormSchema.ts index 4a04a5818..8a3fec01c 100644 --- a/client/modules/workspace/src/AppsWizard/AppsFormSchema.ts +++ b/client/modules/workspace/src/AppsWizard/AppsFormSchema.ts @@ -56,8 +56,7 @@ export type TField = { parameterSet?: string; description?: string; options?: TFieldOptions[]; - tapisFile?: boolean; - tapisFileSelectionMode?: string; + fileSettings?: TAppFileSettings; placeholder?: string; readOnly?: boolean; }; @@ -96,7 +95,7 @@ export type TAppFormSchema = { }; }; -export const inputFileRegex = /^tapis:\/\/(?[^/]+)/; +export const tapisInputFileRegex = /^tapis:\/\/(?[^/]+)/; export const fieldDisplayOrder: Record = { configuration: [ @@ -109,6 +108,14 @@ export const fieldDisplayOrder: Record = { outputs: ['name', 'archiveSystemId', 'archiveSystemDir'], }; +export type TAppFilePathRepresentation = 'FullTapisPath' | 'NameOnly'; +export type TAppFileSelectionMode = 'both' | 'file' | 'directory'; + +export type TAppFileSettings = { + fileNameRepresentation: TAppFilePathRepresentation; + fileSelectionMode: TAppFileSelectionMode; +}; + // See https://github.com/colinhacks/zod/issues/310 for Zod issue const emptyStringToUndefined = z.literal('').transform(() => undefined); function asOptionalField(schema: T) { @@ -316,6 +323,12 @@ const FormSchema = ( name: `parameters.${parameterSet}.${label}`, key: paramId, type: 'text', + ...(param.notes?.inputType === 'fileInput' && { + fileSettings: { + fileNameRepresentation: 'NameOnly', + fileSelectionMode: 'file', + }, + }), }; if (param.notes?.enum_values) { @@ -399,11 +412,14 @@ const FormSchema = ( required: input.inputMode === 'REQUIRED', name: `inputs.${input.name}`, key: `inputs.${input.name}`, - tapisFile: true, type: 'text', placeholder: 'Browse Data Files', readOnly: input.inputMode === 'FIXED', - tapisFileSelectionMode: input.notes?.selectionMode ?? 'both', + fileSettings: { + fileNameRepresentation: 'FullTapisPath', + fileSelectionMode: + (input.notes?.selectionMode as TAppFileSelectionMode) ?? 'both', + }, }; appFields.fileInputs.schema[input.name] = z.string(); diff --git a/client/modules/workspace/src/AppsWizard/FormField.tsx b/client/modules/workspace/src/AppsWizard/FormField.tsx index 1ec10262f..8979c0b01 100644 --- a/client/modules/workspace/src/AppsWizard/FormField.tsx +++ b/client/modules/workspace/src/AppsWizard/FormField.tsx @@ -2,30 +2,32 @@ import React, { useState, useEffect } from 'react'; import { Button, Form, Input, Select } from 'antd'; import { FormItem } from 'react-hook-form-antd'; import { useFormContext, useWatch } from 'react-hook-form'; -import { TFieldOptions, inputFileRegex } from '../AppsWizard/AppsFormSchema'; +import { + TFieldOptions, + tapisInputFileRegex, + TAppFileSettings, +} from '../AppsWizard/AppsFormSchema'; import { SecondaryButton } from '@client/common-components'; import { SelectModal } from '../SelectModal/SelectModal'; export const FormField: React.FC<{ name: string; - tapisFile?: boolean; parameterSet?: string; description?: string; label: string; required?: boolean; type: string; - tapisFileSelectionMode?: string; + fileSettings?: TAppFileSettings; placeholder?: string; options?: TFieldOptions[]; }> = ({ name, - tapisFile = false, parameterSet = null, description, label, required = false, type, - tapisFileSelectionMode = null, + fileSettings = null, ...props }) => { const { resetField, control, getValues, setValue, trigger } = @@ -39,16 +41,17 @@ export const FormField: React.FC<{ setIsModalOpen(true); }; useEffect(() => { - if (tapisFile) { + setStorageSystem(null); + + if (fileSettings?.fileNameRepresentation === 'FullTapisPath') { const inputFileValue = getValues(name); - const match = inputFileValue?.match(inputFileRegex); - if (match && match.groups) { + const match = inputFileValue?.match(tapisInputFileRegex); + + if (match?.groups) { setStorageSystem(match.groups.storageSystem); - } else { - setStorageSystem(null); } } - }, [tapisFile, name, fieldState]); + }, [fileSettings, name, fieldState]); if (parameterSet) { parameterSetLabel = ( @@ -89,7 +92,7 @@ export const FormField: React.FC<{ /> ) : (
- {tapisFile && ( + {fileSettings && ( Select @@ -133,11 +136,11 @@ export const FormField: React.FC<{ {/* Select Modal has Form and input which cause state sharing with above FormItem So, SelectModal is outside FormItem. */} - {tapisFile && ( + {fileSettings && ( setIsModalOpen(false)} onSelect={(value: string) => { diff --git a/client/modules/workspace/src/SelectModal/SelectModal.tsx b/client/modules/workspace/src/SelectModal/SelectModal.tsx index fbbb4906f..01a6cdd71 100644 --- a/client/modules/workspace/src/SelectModal/SelectModal.tsx +++ b/client/modules/workspace/src/SelectModal/SelectModal.tsx @@ -23,6 +23,8 @@ import { TFileListing, } from '@client/hooks'; +import { TAppFileSettings } from '../AppsWizard/AppsFormSchema'; + import { BaseFileListingBreadcrumb, FileListingTable, @@ -152,7 +154,7 @@ const getParentFolder = ( function getFilesColumns( api: string, path: string, - selectionMode: string, + appFileSettings: TAppFileSettings, searchTerm: string | null, clearSearchTerm: () => void, selectionCallback: (path: string) => void, @@ -233,9 +235,11 @@ function getFilesColumns( title: '', render: (_, record, index) => { const selectionModeAllowed = - (record.type === 'dir' && selectionMode === 'directory') || - (record.type === 'file' && selectionMode === 'file') || - selectionMode === 'both'; + (record.type === 'dir' && + appFileSettings.fileSelectionMode === 'directory') || + (record.type === 'file' && + appFileSettings.fileSelectionMode === 'file') || + appFileSettings.fileSelectionMode === 'both'; const isNotRoot = index > 0 || record.system.startsWith(projectPrefix) || @@ -244,9 +248,15 @@ function getFilesColumns( return shouldRenderSelectButton ? ( - selectionCallback(`${api}://${record.system}${record.path}`) - } + onClick={() => { + const lastPartOfPath = record.path.split('/').pop() ?? ''; + const filePath = + appFileSettings.fileNameRepresentation === 'FullTapisPath' + ? `${api}://${record.system}${record.path}` + : lastPartOfPath; + + selectionCallback(filePath); + }} > Select @@ -259,11 +269,11 @@ function getFilesColumns( export const SelectModal: React.FC<{ inputLabel: string; system: string | null; - selectionMode: string; + appFileSettings: TAppFileSettings; isOpen: boolean; onClose: () => void; onSelect: (value: string) => void; -}> = ({ inputLabel, system, selectionMode, isOpen, onClose, onSelect }) => { +}> = ({ inputLabel, system, appFileSettings, isOpen, onClose, onSelect }) => { const [isModalOpen, setIsModalOpen] = useState(false); const [searchTerm, setSearchTerm] = useState(null); const [form] = Form.useForm(); @@ -420,7 +430,7 @@ export const SelectModal: React.FC<{ getFilesColumns( selectedApi, selectedPath, - selectionMode, + appFileSettings, searchTerm, clearSearchTerm, (selection: string) => selectCallback(selection), @@ -435,7 +445,7 @@ export const SelectModal: React.FC<{ selectedSystem, selectedPath, systemLabel, - selectionMode, + appFileSettings, selectCallback, ] );