diff --git a/packages/nc-gui/components/smartsheet/Form.vue b/packages/nc-gui/components/smartsheet/Form.vue index fce548493d..7ccc0215f1 100644 --- a/packages/nc-gui/components/smartsheet/Form.vue +++ b/packages/nc-gui/components/smartsheet/Form.vue @@ -4,7 +4,7 @@ import Draggable from 'vuedraggable' import tinycolor from 'tinycolor2' import { Pane, Splitpanes } from 'splitpanes' import 'splitpanes/dist/splitpanes.css' -import { RelationTypes, UITypes, ViewTypes, getSystemColumns, isLinksOrLTAR, isVirtualCol } from 'nocodb-sdk' +import { RelationTypes, UITypes, ViewTypes, getSystemColumns, isLinksOrLTAR, isVirtualCol, ProjectRoles } from 'nocodb-sdk' import type { Permission } from '#imports' import { ActiveViewInj, @@ -33,6 +33,7 @@ import { useViewColumnsOrThrow, useViewData, watch, + useFileDialog, } from '#imports' provide(IsFormInj, ref(true)) @@ -62,6 +63,10 @@ const { $api, $e } = useNuxtApp() const { isUIAllowed } = useRoles() +const { base } = storeToRefs(useBase()) + +const { getPossibleAttachmentSrc } = useAttachment() + let formState = reactive>({}) const secondsRemain = ref(0) @@ -118,6 +123,34 @@ const isTabPressed = ref(false) const isLoadingFormView = ref(false) +const showCropper = ref(false) + +const imageCropperData = ref<{ + imageConfig: { + src: string + type: string + name: string + } + cropperConfig: { + aspectRatio?: number + } + uploadConfig?: { + path?: string + } + cropFor: 'banner' | 'logo' +}>({ + imageConfig: { + src: '', + type: '', + name: '', + }, + cropperConfig: {}, + uploadConfig: { + path: '', + }, + cropFor: 'banner', +}) + const focusLabel: VNodeRef = (el) => { return (el as HTMLInputElement)?.focus() } @@ -128,6 +161,11 @@ const { t } = useI18n() const { betaFeatureToggleState } = useBetaFeatureToggle() +const { open, onChange: onChangeFile } = useFileDialog({ + accept: 'image/*', + multiple: false, +}) + const visibleColumns = computed(() => localColumns.value.filter((f) => f.show).sort((a, b) => a.order - b.order)) const updateView = useDebounceFn( @@ -139,6 +177,8 @@ const updateView = useDebounceFn( ) async function submitForm() { + if (isLocked.value || !isUIAllowed('dataInsert')) return + try { await formRef.value?.validateFields() } catch (e: any) { @@ -158,6 +198,8 @@ async function submitForm() { } async function clearForm() { + if (isLocked.value || !isUIAllowed('dataInsert')) return + formState = reactive>({}) state.value = {} await formRef.value.clearValidate() @@ -205,8 +247,9 @@ function onMoveCallback(event: any) { } } -// Todo: reorder visible form fields function onMove(event: any, isVisibleFormFields = false) { + if (isLocked.value || !isEditable) return + const { oldIndex } = event.moved let { newIndex, element } = event.moved @@ -239,6 +282,8 @@ function onMove(event: any, isVisibleFormFields = false) { } async function showOrHideColumn(column: Record, show: boolean, isSidePannel = false) { + if (isLocked.value || !isEditable) return + if (shouldSkipColumn(column)) { // Required field can't be moved !isSidePannel && message.info(t('msg.info.requriedFieldsCantBeMoved')) @@ -273,6 +318,8 @@ function shouldSkipColumn(col: Record) { } async function handleAddOrRemoveAllColumns(value: boolean) { + if (isLocked.value || !isEditable) return + if (value) { for (const col of (localColumns as Record)?.value) { col.show = true @@ -383,11 +430,14 @@ const columnSupportsScanning = (elementType: UITypes) => const onFormItemClick = (element: any) => { if (isLocked.value || !isEditable) return + activeRow.value = element.title isTabPressed.value = false } const handleChangeBackground = (color: string) => { + if (isLocked.value || !isEditable) return + const tcolor = tinycolor(color) if (tcolor.isValid()) { ;(formViewData.value?.meta as Record).background_color = color @@ -395,12 +445,66 @@ const handleChangeBackground = (color: string) => { } } +const openUploadImage = (isUploadBanner: boolean) => { + if (!isEditable) return + + imageCropperData.value.uploadConfig = { + path: [NOCO, base.value.id, meta.value?.id, formViewData.value?.id].join('/'), + } + if (isUploadBanner) { + imageCropperData.value.cropperConfig = { + aspectRatio: 4 / 1, + } + imageCropperData.value.cropFor = 'banner' + } else { + imageCropperData.value.cropperConfig = { + aspectRatio: undefined, + } + imageCropperData.value.cropFor = 'logo' + } + + open() +} + +onChangeFile((files) => { + if (files && files[0]) { + // 1. Revoke the object URL, to allow the garbage collector to destroy the uploaded before file + if (imageCropperData.value.imageConfig.src) { + URL.revokeObjectURL(imageCropperData.value.imageConfig.src) + } + // 2. Create the blob link to the file to optimize performance: + const blob = URL.createObjectURL(files[0]) + + // 3. Update the image. The type will be derived from the extension + imageCropperData.value.imageConfig = { + src: blob, + type: files[0].type, + name: files[0].name, + } + + showCropper.value = true + } +}) + +const handleOnUploadImage = (data: Record = {}) => { + if (imageCropperData.value.cropFor === 'banner') { + formViewData.value!.banner_image_url = stringifyProp(data) ?? '' + } else { + formViewData.value!.logo_url = stringifyProp(data) ?? '' + } + updateView() +} + onClickOutside(draggableRef, () => { activeRow.value = '' isTabPressed.value = false }) onMounted(async () => { + if (imageCropperData.value.src) { + URL.revokeObjectURL(imageCropperData.value.imageConfig.src) + } + isLoadingFormView.value = true await loadFormView() setFormData() @@ -480,7 +584,7 @@ useEventListener( +
+
+
+ {{ $t('msg.info.yourCurrentRoleIs') }} + ' {{ Object.keys(user.base_roles)?.[0] ?? ProjectRoles.NO_ACCESS }}'. +
+
+ {{ $t('msg.info.pleaseRequestAccessForView', { viewName: 'form view' }) }} +
+
+
diff --git a/packages/nc-gui/lang/en.json b/packages/nc-gui/lang/en.json index b769565cdb..ed08c6e58a 100644 --- a/packages/nc-gui/lang/en.json +++ b/packages/nc-gui/lang/en.json @@ -1294,7 +1294,10 @@ "notAvailableAtTheMoment": "Not available at the moment", "groupPasteIsNotSupportedOnLinksColumn": "Group paste operation is not supported on Links/LinkToAnotherRecord column", "groupClearIsNotSupportedOnLinksColumn": "Group clear operation is not supported on Links/LinkToAnotherRecord column", - "upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}" + "upgradeToEnterpriseEdition": "Upgrade to Enterprise Edition {extraInfo}", + "yourCurrentRoleIs": "Your current role is", + "pleaseRequestAccessForView": "Please request for higher permission from the Admin / Base owner / Workspace owner to get access to this {viewName}" + }, "error": { "fetchingCalendarData": "Error fetching calendar data",