Browse Source

fix(gui-v2): shared form view not passing files when submitting

pull/3300/head
braks 2 years ago
parent
commit
40e67573ad
  1. 12
      packages/nc-gui-v2/components/cell/attachment/index.vue
  2. 23
      packages/nc-gui-v2/components/cell/attachment/utils.ts
  3. 62
      packages/nc-gui-v2/composables/useSharedFormViewStore.ts
  4. 28
      packages/nc-gui-v2/composables/useSmartsheetRowStore.ts
  5. 5
      packages/nc-gui-v2/utils/urlUtils.ts

12
packages/nc-gui-v2/components/cell/attachment/index.vue

@ -18,7 +18,7 @@ import {
} from '#imports' } from '#imports'
interface Props { interface Props {
modelValue: string | Record<string, any>[] | null modelValue?: string | Record<string, any>[] | null
rowIndex?: number rowIndex?: number
} }
@ -50,10 +50,16 @@ const {
selectedImage, selectedImage,
isReadonly, isReadonly,
storedFiles, storedFiles,
} = useProvideAttachmentCell(updateModelValue) } = useProvideAttachmentCell((val) => {
console.log(val)
updateModelValue(val)
})
const currentCellRef = computed(() => const currentCellRef = computed(() =>
isForm.value ? attachmentCellRef.value : cellRefs.value.find((cell) => cell.dataset.key === `${rowIndex}${column.value.id}`), !rowIndex && isForm.value
? attachmentCellRef.value
: cellRefs.value.find((cell) => cell.dataset.key === `${rowIndex}${column.value.id}`),
) )
const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, isReadonly) const { dragging } = useSortable(sortableRef, visibleItems, updateModelValue, isReadonly)

23
packages/nc-gui-v2/components/cell/attachment/utils.ts

@ -23,10 +23,11 @@ import MdiFilePowerpointBox from '~icons/mdi/file-powerpoint-box'
import MdiFileExcelOutline from '~icons/mdi/file-excel-outline' import MdiFileExcelOutline from '~icons/mdi/file-excel-outline'
import IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file' import IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file'
interface AttachmentProps { interface AttachmentProps extends File {
data?: any data?: any
file: File file: File
title: string title: string
mimetype: string
} }
export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
@ -44,11 +45,8 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
const editEnabled = inject(EditModeInj, ref(false)) const editEnabled = inject(EditModeInj, ref(false))
/** keep user selected files data (in base encoded string format) and meta details */
const storedFilesData = ref<{ title: string; file: File }[]>([])
/** keep user selected File object */ /** keep user selected File object */
const storedFiles = ref<{ title: string; file: File }[]>([]) const storedFiles = ref<AttachmentProps[]>([])
const attachments = ref<File[]>([]) const attachments = ref<File[]>([])
@ -60,15 +58,14 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
const { api, isLoading } = useApi() const { api, isLoading } = useApi()
const { files, open, reset } = useFileDialog() const { files, open } = useFileDialog()
/** remove a file from our stored attachments (either locally stored or saved ones) */ /** remove a file from our stored attachments (either locally stored or saved ones) */
function removeFile(i: number) { function removeFile(i: number) {
if (isPublic.value) { if (isPublic.value) {
storedFilesData.value.splice(i, 1)
storedFiles.value.splice(i, 1) storedFiles.value.splice(i, 1)
updateModelValue(storedFilesData.value.map((storedFile) => storedFile.file)) updateModelValue(storedFiles.value.map((stored) => stored.file))
} else { } else {
attachments.value.splice(i, 1) attachments.value.splice(i, 1)
@ -86,9 +83,8 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
Array.from(selectedFiles).map( Array.from(selectedFiles).map(
(file) => (file) =>
new Promise<AttachmentProps>((resolve) => { new Promise<AttachmentProps>((resolve) => {
const res: any = { ...file, file, title: file.name } const res: AttachmentProps = { ...file, file, title: file.name, mimetype: file.type }
console.log(res)
if (isImage(file.name, (<any>file).mimetype ?? file.type)) { if (isImage(file.name, (<any>file).mimetype ?? file.type)) {
const reader = new FileReader() const reader = new FileReader()
@ -111,9 +107,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
)), )),
) )
reset() return updateModelValue(storedFiles.value.map((stored) => stored.file))
return updateModelValue(storedFiles.value.map((next) => next.file))
} }
const newAttachments = [] const newAttachments = []
@ -136,8 +130,6 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
} }
} }
reset()
updateModelValue([...attachments.value, ...newAttachments]) updateModelValue([...attachments.value, ...newAttachments])
} }
@ -176,7 +168,6 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
return { return {
attachments, attachments,
storedFilesData,
visibleItems, visibleItems,
isPublic, isPublic,
isPublicGrid, isPublicGrid,

62
packages/nc-gui-v2/composables/useSharedFormViewStore.ts

@ -1,12 +1,21 @@
import useVuelidate from '@vuelidate/core' import useVuelidate from '@vuelidate/core'
import { minLength, required } from '@vuelidate/validators' import { minLength, required } from '@vuelidate/validators'
import type { Ref } from 'vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import type { ColumnType, FormType, LinkToAnotherRecordType, TableType, ViewType } from 'nocodb-sdk' import type { ColumnType, FormType, LinkToAnotherRecordType, TableType, ViewType } from 'nocodb-sdk'
import { ErrorMessages, RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' import { ErrorMessages, RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue' import {
import { SharedViewPasswordInj } from '~/context' SharedViewPasswordInj,
import { extractSdkResponseErrorMsg } from '~/utils' computed,
import { useInjectionState, useMetas } from '#imports' extractSdkResponseErrorMsg,
provide,
ref,
useApi,
useInjectionState,
useMetas,
useProvideSmartsheetRowStore,
watch,
} from '#imports'
const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((sharedViewId: string) => { const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((sharedViewId: string) => {
const progress = ref(false) const progress = ref(false)
@ -23,8 +32,10 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
const meta = ref<TableType>() const meta = ref<TableType>()
const columns = ref<(ColumnType & { required?: boolean; show?: boolean })[]>() const columns = ref<(ColumnType & { required?: boolean; show?: boolean })[]>()
const { $api } = useNuxtApp() const { api, isLoading } = useApi()
const { metas, setMeta } = useMetas() const { metas, setMeta } = useMetas()
const formState = ref({}) const formState = ref({})
const { state: additionalState } = useProvideSmartsheetRowStore( const { state: additionalState } = useProvideSmartsheetRowStore(
@ -37,11 +48,11 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
) )
const formColumns = computed(() => const formColumns = computed(() =>
columns?.value?.filter((c) => c.show)?.filter((col) => !isVirtualCol(col) || col.uidt === UITypes.LinkToAnotherRecord), columns.value?.filter((c) => c.show).filter((col) => !isVirtualCol(col) || col.uidt === UITypes.LinkToAnotherRecord),
) )
const loadSharedView = async () => { const loadSharedView = async () => {
try { try {
const viewMeta = await $api.public.sharedViewMetaGet(sharedViewId, { const viewMeta: Record<string, any> = await api.public.sharedViewMetaGet(sharedViewId, {
headers: { headers: {
'xc-password': password.value, 'xc-password': password.value,
}, },
@ -54,9 +65,10 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
meta.value = viewMeta.model meta.value = viewMeta.model
columns.value = viewMeta.model?.columns columns.value = viewMeta.model?.columns
setMeta(viewMeta.model) await setMeta(viewMeta.model)
const relatedMetas = { ...viewMeta.relatedMetas } const relatedMetas = { ...viewMeta.relatedMetas }
Object.keys(relatedMetas).forEach((key) => setMeta(relatedMetas[key])) Object.keys(relatedMetas).forEach((key) => setMeta(relatedMetas[key]))
} catch (e: any) { } catch (e: any) {
if (e.response && e.response.status === 404) { if (e.response && e.response.status === 404) {
@ -68,11 +80,14 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
} }
const validators = computed(() => { const validators = computed(() => {
const obj: any = { const obj: Record<string, Record<string, any>> = {
localState: {}, localState: {},
virtual: {}, virtual: {},
} }
for (const column of formColumns?.value ?? []) {
if (!formColumns.value) return obj
for (const column of formColumns.value) {
if ( if (
!isVirtualCol(column) && !isVirtualCol(column) &&
((column.rqd && !column.cdf) || (column.pk && !(column.ai || column.cdf)) || (column as any).required) ((column.rqd && !column.cdf) || (column.pk && !(column.ai || column.cdf)) || (column as any).required)
@ -103,7 +118,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
const v$ = useVuelidate( const v$ = useVuelidate(
validators, validators,
computed(() => ({ localState: formState?.value, virtual: additionalState?.value })), computed(() => ({ localState: formState.value, virtual: additionalState.value })),
) )
const submitForm = async () => { const submitForm = async () => {
@ -113,18 +128,20 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
} }
progress.value = true progress.value = true
const data: Record<string, any> = { ...(formState?.value ?? {}), ...(additionalState?.value || {}) } const data: Record<string, any> = { ...formState.value, ...additionalState.value }
const attachment: Record<string, any> = {} const attachment: Record<string, any> = {}
for (const col of metas?.value?.[sharedView?.value?.fk_model_id as string]?.columns ?? []) { /** find attachments in form data */
for (const col of metas.value?.[sharedView.value?.fk_model_id as string]?.columns) {
if (col.uidt === UITypes.Attachment) { if (col.uidt === UITypes.Attachment) {
attachment[`_${col.title}`] = data[col.title!] console.log(data[col.title!])
delete data[col.title!]
attachment[`_${col.title}`] = data[col.title].map((item: { file: File }) => item.file)
} }
} }
await $api.public.dataCreate( await api.public.dataCreate(
sharedView?.value?.uuid as string, (sharedView.value as any)?.uuid as string,
{ {
data, data,
...attachment, ...attachment,
@ -139,7 +156,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
submitted.value = true submitted.value = true
progress.value = false progress.value = false
await message.success(sharedFormView.value?.success_msg || 'Saved successfully.') await message.success(sharedFormView.value?.sucess_msg || 'Saved successfully.')
} catch (e: any) { } catch (e: any) {
console.log(e) console.log(e)
await message.error(await extractSdkResponseErrorMsg(e)) await message.error(await extractSdkResponseErrorMsg(e))
@ -148,13 +165,15 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
} }
/** reset form if show_blank_form is true */ /** reset form if show_blank_form is true */
watch(submitted, (nextVal: boolean) => { watch(submitted, (nextVal) => {
if (nextVal && sharedFormView.value?.show_blank_form) { if (nextVal && (sharedFormView.value as any)?.show_blank_form) {
secondsRemain.value = 5 secondsRemain.value = 5
const intvl = setInterval(() => { const intvl = setInterval(() => {
secondsRemain.value = secondsRemain.value - 1 secondsRemain.value = secondsRemain.value - 1
if (secondsRemain.value < 0) { if (secondsRemain.value < 0) {
submitted.value = false submitted.value = false
clearInterval(intvl) clearInterval(intvl)
} }
}, 1000) }, 1000)
@ -185,6 +204,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
submitted, submitted,
secondsRemain, secondsRemain,
passwordDlg, passwordDlg,
isLoading,
} }
}, 'expanded-form-store') }, 'expanded-form-store')
@ -192,6 +212,8 @@ export { useProvideSharedFormStore }
export function useSharedFormStoreOrThrow() { export function useSharedFormStoreOrThrow() {
const sharedFormStore = useSharedFormStore() const sharedFormStore = useSharedFormStore()
if (sharedFormStore == null) throw new Error('Please call `useProvideSharedFormStore` on the appropriate parent component') if (sharedFormStore == null) throw new Error('Please call `useProvideSharedFormStore` on the appropriate parent component')
return sharedFormStore return sharedFormStore
} }

28
packages/nc-gui-v2/composables/useSmartsheetRowStore.ts

@ -2,27 +2,41 @@ import { message } from 'ant-design-vue'
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType, RelationTypes, TableType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, RelationTypes, TableType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import type { MaybeRef } from '@vueuse/core'
import type { Row } from './useViewData' import type { Row } from './useViewData'
import { useInjectionState, useMetas, useNuxtApp, useProject, useVirtualCell } from '#imports' import {
import { NOCO } from '~/lib' NOCO,
import { deepCompare, extractPkFromRow, extractSdkResponseErrorMsg } from '~/utils' computed,
deepCompare,
extractPkFromRow,
extractSdkResponseErrorMsg,
ref,
unref,
useInjectionState,
useMetas,
useNuxtApp,
useProject,
useVirtualCell,
} from '#imports'
const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState((meta: Ref<TableType>, row: Ref<Row>) => { const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState((meta: Ref<TableType>, row: MaybeRef<Row>) => {
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const { project } = useProject() const { project } = useProject()
const { metas } = useMetas() const { metas } = useMetas()
// state // state
const state = ref<Record<string, Record<string, any> | Record<string, any>[] | null>>({}) const state = ref<Record<string, Record<string, any> | Record<string, any>[] | null>>({})
// getters // getters
const isNew = computed(() => row.value?.rowMeta?.new ?? false) const isNew = computed(() => unref(row).rowMeta?.new ?? false)
// actions // actions
const addLTARRef = async (value: Record<string, any>, column: ColumnType) => { const addLTARRef = async (value: Record<string, any>, column: ColumnType) => {
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column))) const { isHm, isMm, isBt } = $(useVirtualCell(ref(column)))
if (isHm || isMm) { if (isHm || isMm) {
state.value[column.title!] = state.value[column.title!] || [] if (!state.value[column.title!]) state.value[column.title!] = []
if (state.value[column.title!]!.find((ln: Record<string, any>) => deepCompare(ln, value))) { if (state.value[column.title!]!.find((ln: Record<string, any>) => deepCompare(ln, value))) {
return message.info('This value is already in the list') return message.info('This value is already in the list')
@ -106,6 +120,8 @@ export { useProvideSmartsheetRowStore }
export function useSmartsheetRowStoreOrThrow() { export function useSmartsheetRowStoreOrThrow() {
const smartsheetRowStore = useSmartsheetRowStore() const smartsheetRowStore = useSmartsheetRowStore()
if (smartsheetRowStore == null) throw new Error('Please call `useSmartsheetRowStore` on the appropriate parent component') if (smartsheetRowStore == null) throw new Error('Please call `useSmartsheetRowStore` on the appropriate parent component')
return smartsheetRowStore return smartsheetRowStore
} }

5
packages/nc-gui-v2/utils/urlUtils.ts

@ -21,10 +21,11 @@ export const replaceUrlsWithLink = (text: string): boolean | string => {
export const isValidURL = (str: string) => { export const isValidURL = (str: string) => {
const pattern = const pattern =
/^(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00A1-\uFFFF0-9]-*)*[a-z\u00A1-\uFFFF0-9]+)(?:\.(?:[a-z\u00A1-\uFFFF0-9]-*)*[a-z\u00A1-\uFFFF0-9]+)*(?:\.(?:[a-z\u00A1-\uFFFF]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i /^(?:(?:https?|ftp):\/\/)?(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00A1-\uFFFF0-9]-*)*[a-z\u00A1-\uFFFF0-9]+)(?:\.(?:[a-z\u00A1-\uFFFF0-9]-*)*[a-z\u00A1-\uFFFF0-9]+)*(?:\.(?:[a-z\u00A1-\uFFFF]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i
return !!pattern.test(str)
return pattern.test(str)
} }
export const openLink = (path: string, baseURL: string, target = '_blank') => { export const openLink = (path: string, baseURL?: string, target = '_blank') => {
const url = new URL(path, baseURL) const url = new URL(path, baseURL)
window.open(url.href, target) window.open(url.href, target)
} }

Loading…
Cancel
Save