diff --git a/packages/nc-gui-v2/components/cell/attachment/index.vue b/packages/nc-gui-v2/components/cell/attachment/index.vue index fb11a2a1e8..090a1c07c4 100644 --- a/packages/nc-gui-v2/components/cell/attachment/index.vue +++ b/packages/nc-gui-v2/components/cell/attachment/index.vue @@ -18,7 +18,7 @@ import { } from '#imports' interface Props { - modelValue: string | Record[] | null + modelValue?: string | Record[] | null rowIndex?: number } @@ -50,10 +50,16 @@ const { selectedImage, isReadonly, storedFiles, -} = useProvideAttachmentCell(updateModelValue) +} = useProvideAttachmentCell((val) => { + console.log(val) + + updateModelValue(val) +}) 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) diff --git a/packages/nc-gui-v2/components/cell/attachment/utils.ts b/packages/nc-gui-v2/components/cell/attachment/utils.ts index 243934474c..c2ea48bf75 100644 --- a/packages/nc-gui-v2/components/cell/attachment/utils.ts +++ b/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 IcOutlineInsertDriveFile from '~icons/ic/outline-insert-drive-file' -interface AttachmentProps { +interface AttachmentProps extends File { data?: any file: File title: string + mimetype: string } export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( @@ -44,11 +45,8 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( 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 */ - const storedFiles = ref<{ title: string; file: File }[]>([]) + const storedFiles = ref([]) const attachments = ref([]) @@ -60,15 +58,14 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( 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) */ function removeFile(i: number) { if (isPublic.value) { - storedFilesData.value.splice(i, 1) storedFiles.value.splice(i, 1) - updateModelValue(storedFilesData.value.map((storedFile) => storedFile.file)) + updateModelValue(storedFiles.value.map((stored) => stored.file)) } else { attachments.value.splice(i, 1) @@ -86,9 +83,8 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( Array.from(selectedFiles).map( (file) => new Promise((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, (file).mimetype ?? file.type)) { const reader = new FileReader() @@ -111,9 +107,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( )), ) - reset() - - return updateModelValue(storedFiles.value.map((next) => next.file)) + return updateModelValue(storedFiles.value.map((stored) => stored.file)) } const newAttachments = [] @@ -136,8 +130,6 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( } } - reset() - updateModelValue([...attachments.value, ...newAttachments]) } @@ -176,7 +168,6 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState( return { attachments, - storedFilesData, visibleItems, isPublic, isPublicGrid, diff --git a/packages/nc-gui-v2/composables/useSharedFormViewStore.ts b/packages/nc-gui-v2/composables/useSharedFormViewStore.ts index 902405b41c..6902ffe036 100644 --- a/packages/nc-gui-v2/composables/useSharedFormViewStore.ts +++ b/packages/nc-gui-v2/composables/useSharedFormViewStore.ts @@ -1,12 +1,21 @@ import useVuelidate from '@vuelidate/core' import { minLength, required } from '@vuelidate/validators' +import type { Ref } from 'vue' import { message } from 'ant-design-vue' import type { ColumnType, FormType, LinkToAnotherRecordType, TableType, ViewType } from 'nocodb-sdk' import { ErrorMessages, RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' -import type { Ref } from 'vue' -import { SharedViewPasswordInj } from '~/context' -import { extractSdkResponseErrorMsg } from '~/utils' -import { useInjectionState, useMetas } from '#imports' +import { + SharedViewPasswordInj, + computed, + extractSdkResponseErrorMsg, + provide, + ref, + useApi, + useInjectionState, + useMetas, + useProvideSmartsheetRowStore, + watch, +} from '#imports' const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((sharedViewId: string) => { const progress = ref(false) @@ -23,8 +32,10 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share const meta = ref() const columns = ref<(ColumnType & { required?: boolean; show?: boolean })[]>() - const { $api } = useNuxtApp() + const { api, isLoading } = useApi() + const { metas, setMeta } = useMetas() + const formState = ref({}) const { state: additionalState } = useProvideSmartsheetRowStore( @@ -37,11 +48,11 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share ) 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 () => { try { - const viewMeta = await $api.public.sharedViewMetaGet(sharedViewId, { + const viewMeta: Record = await api.public.sharedViewMetaGet(sharedViewId, { headers: { 'xc-password': password.value, }, @@ -54,9 +65,10 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share meta.value = viewMeta.model columns.value = viewMeta.model?.columns - setMeta(viewMeta.model) + await setMeta(viewMeta.model) const relatedMetas = { ...viewMeta.relatedMetas } + Object.keys(relatedMetas).forEach((key) => setMeta(relatedMetas[key])) } catch (e: any) { if (e.response && e.response.status === 404) { @@ -68,11 +80,14 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share } const validators = computed(() => { - const obj: any = { + const obj: Record> = { localState: {}, virtual: {}, } - for (const column of formColumns?.value ?? []) { + + if (!formColumns.value) return obj + + for (const column of formColumns.value) { if ( !isVirtualCol(column) && ((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( validators, - computed(() => ({ localState: formState?.value, virtual: additionalState?.value })), + computed(() => ({ localState: formState.value, virtual: additionalState.value })), ) const submitForm = async () => { @@ -113,18 +128,20 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share } progress.value = true - const data: Record = { ...(formState?.value ?? {}), ...(additionalState?.value || {}) } + const data: Record = { ...formState.value, ...additionalState.value } const attachment: Record = {} - 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) { - attachment[`_${col.title}`] = data[col.title!] - delete data[col.title!] + console.log(data[col.title!]) + + attachment[`_${col.title}`] = data[col.title].map((item: { file: File }) => item.file) } } - await $api.public.dataCreate( - sharedView?.value?.uuid as string, + await api.public.dataCreate( + (sharedView.value as any)?.uuid as string, { data, ...attachment, @@ -139,7 +156,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share submitted.value = true progress.value = false - await message.success(sharedFormView.value?.success_msg || 'Saved successfully.') + await message.success(sharedFormView.value?.sucess_msg || 'Saved successfully.') } catch (e: any) { console.log(e) await message.error(await extractSdkResponseErrorMsg(e)) @@ -148,13 +165,15 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share } /** reset form if show_blank_form is true */ - watch(submitted, (nextVal: boolean) => { - if (nextVal && sharedFormView.value?.show_blank_form) { + watch(submitted, (nextVal) => { + if (nextVal && (sharedFormView.value as any)?.show_blank_form) { secondsRemain.value = 5 const intvl = setInterval(() => { secondsRemain.value = secondsRemain.value - 1 + if (secondsRemain.value < 0) { submitted.value = false + clearInterval(intvl) } }, 1000) @@ -185,6 +204,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share submitted, secondsRemain, passwordDlg, + isLoading, } }, 'expanded-form-store') @@ -192,6 +212,8 @@ export { useProvideSharedFormStore } export function useSharedFormStoreOrThrow() { const sharedFormStore = useSharedFormStore() + if (sharedFormStore == null) throw new Error('Please call `useProvideSharedFormStore` on the appropriate parent component') + return sharedFormStore } diff --git a/packages/nc-gui-v2/composables/useSmartsheetRowStore.ts b/packages/nc-gui-v2/composables/useSmartsheetRowStore.ts index de2987577e..5267fc4498 100644 --- a/packages/nc-gui-v2/composables/useSmartsheetRowStore.ts +++ b/packages/nc-gui-v2/composables/useSmartsheetRowStore.ts @@ -2,27 +2,41 @@ import { message } from 'ant-design-vue' import { UITypes } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, RelationTypes, TableType } from 'nocodb-sdk' import type { Ref } from 'vue' +import type { MaybeRef } from '@vueuse/core' import type { Row } from './useViewData' -import { useInjectionState, useMetas, useNuxtApp, useProject, useVirtualCell } from '#imports' -import { NOCO } from '~/lib' -import { deepCompare, extractPkFromRow, extractSdkResponseErrorMsg } from '~/utils' +import { + NOCO, + computed, + deepCompare, + extractPkFromRow, + extractSdkResponseErrorMsg, + ref, + unref, + useInjectionState, + useMetas, + useNuxtApp, + useProject, + useVirtualCell, +} from '#imports' -const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState((meta: Ref, row: Ref) => { +const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState((meta: Ref, row: MaybeRef) => { const { $api } = useNuxtApp() + const { project } = useProject() + const { metas } = useMetas() // state const state = ref | Record[] | null>>({}) // getters - const isNew = computed(() => row.value?.rowMeta?.new ?? false) + const isNew = computed(() => unref(row).rowMeta?.new ?? false) // actions const addLTARRef = async (value: Record, column: ColumnType) => { const { isHm, isMm, isBt } = $(useVirtualCell(ref(column))) 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) => deepCompare(ln, value))) { return message.info('This value is already in the list') @@ -106,6 +120,8 @@ export { useProvideSmartsheetRowStore } export function useSmartsheetRowStoreOrThrow() { const smartsheetRowStore = useSmartsheetRowStore() + if (smartsheetRowStore == null) throw new Error('Please call `useSmartsheetRowStore` on the appropriate parent component') + return smartsheetRowStore } diff --git a/packages/nc-gui-v2/utils/urlUtils.ts b/packages/nc-gui-v2/utils/urlUtils.ts index 95c3aeb41a..e6bf9d8c74 100644 --- a/packages/nc-gui-v2/utils/urlUtils.ts +++ b/packages/nc-gui-v2/utils/urlUtils.ts @@ -21,10 +21,11 @@ export const replaceUrlsWithLink = (text: string): boolean | string => { export const isValidURL = (str: string) => { 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 - 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) window.open(url.href, target) }