多维表格
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1174 lines
42 KiB

<script setup lang="ts">
import Draggable from 'vuedraggable'
import { Pane, Splitpanes } from 'splitpanes'
import 'splitpanes/dist/splitpanes.css'
9 months ago
import { RelationTypes, UITypes, ViewTypes, getSystemColumns, isLinksOrLTAR, isVirtualCol } from 'nocodb-sdk'
import type { Permission } from '#imports'
import {
ActiveViewInj,
9 months ago
IsFormInj,
IsGalleryInj,
MetaInj,
ReloadViewDataHookInj,
computed,
createEventHook,
extractSdkResponseErrorMsg,
iconMap,
inject,
message,
onClickOutside,
parseProp,
provide,
reactive,
ref,
useDebounceFn,
useGlobal,
useI18n,
useNuxtApp,
useRoles,
useViewColumnsOrThrow,
useViewData,
watch,
} from '#imports'
provide(IsFormInj, ref(true))
provide(IsGalleryInj, ref(false))
// todo: generate hideCols based on default values
const hiddenCols = ['created_at', 'updated_at']
9 months ago
const hiddenColTypes = [
UITypes.Rollup,
UITypes.Lookup,
UITypes.Formula,
UITypes.QrCode,
UITypes.Barcode,
UITypes.SpecificDBType,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
9 months ago
]
const { isMobileMode, user } = useGlobal()
const formRef = ref()
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useRoles()
const { appInfo } = useGlobal()
let formState = reactive({})
const secondsRemain = ref(0)
const isLocked = inject(IsLockedInj, ref(false))
const isEditable = isUIAllowed('viewFieldEdit' as Permission)
const meta = inject(MetaInj, ref())
const view = inject(ActiveViewInj, ref())
feat: Improved UI (#6222) * feat: Improved ui (#6156) * refactor: revert Signed-off-by: Pranav C <pranavxc@gmail.com> feat: shared base Signed-off-by: Pranav C <pranavxc@gmail.com> fix: remove duplicate import statement Signed-off-by: Pranav C <pranavxc@gmail.com> fix: disable starred & license menu Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: fix airtable wait issue Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: enable mysql in ci Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: fix checkbox order for sqlite Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: disable quick tests Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: fix dbType env variable for CI Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: workspace API access error fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: enable SQLite CI CD Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: use DB_TYPE env variable Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: enable SQLite UT Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: isHub cleanup Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: add check for EE Timezone spec Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> chore: cleanup Signed-off-by: Pranav C <pranavxc@gmail.com> chore: cleanup Signed-off-by: Pranav C <pranavxc@gmail.com> test: EE check fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> chore: test correction Signed-off-by: Pranav C <pranavxc@gmail.com> chore: sync latest changes Signed-off-by: Pranav C <pranavxc@gmail.com> test: set EE=false Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: set NC Edition to community in workflow file Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> chore: update sdk build command Signed-off-by: Pranav C <pranavxc@gmail.com> refactor: i18n and other changes Signed-off-by: Pranav C <pranavxc@gmail.com> feat: new ui Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: sync tests Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: lint Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: shared view/base related bugs Signed-off-by: Pranav C <pranavxc@gmail.com> * test: checkbox verification sort order fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: fix sqlite reset Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: enable selfhosted runners Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: table ops (draft) Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * Docs: screenshots for table-operations.md * refactor: introduce missing buttons Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: get all fields Signed-off-by: Pranav C <pranavxc@gmail.com> * test: UT fix- new data API response Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: EE is false Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: webhook lookup as string in CE Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: include created_at and updated_at Signed-off-by: Pranav C <pranavxc@gmail.com> * test: fix UT newDataAPI response for PG Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: separate api for webhook related plugins Signed-off-by: Pranav C <pranavxc@gmail.com> * test: msyql filter corrections Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: mysql group by test corrections Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: fix datatype for rating field in groupby spec for pg Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: kanban datatype correction Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: column edit for mysql- rating field Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: misc fixes Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: enable 4 workers Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: enable 2 workers per shard only Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: table CRUD * Rename table-operations.md to table-crud.md * Create column-crud.md * docs: row CRUD * Rename row.md to row-crud.md * docs: project crud * docs: toolbar (skeleton) * refactor: single page UI and bug fixes Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: sync tests playwright Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: add missing dependency Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: single page ui, test corrections Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: tests Signed-off-by: Pranav C <pranavxc@gmail.com> * test: project rename test correction Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: remove only Signed-off-by: Pranav C <pranavxc@gmail.com> * test: remove wrong import statement Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: delete option not visible in project context menu Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: move ws access within isEE() Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: fix groupby * test: groupby fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: signup & landing page * docs: project crud * docs: project-crud misc * docs: toolbar fields * docs: toolbar / filters * docs: toolbar / group by * docs: toolbar / sort * docs: toolbar / row height * docs: filters additional options * docs: file re-order Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: add links to column types * docs: code snippets * docs: links * docs: lookup * docs: rollup * docs: formula * docs: primary key * docs: display value * docs: development setup * docs: swagger * fix(nc-gui): encodeURIComponent for row id - closes: #6202 * docs: language * docs: expanded record * docs: import airtable * docs: airtable * docs: webhook * docs: revert file rename Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: account settings * docs: audit * docs: meta management * docs: project settings * docs: shared base * docs: shared view * docs: meta sync * docs: team-auth * docs: views * docs: fix URL * docs: URL corrections * fix: shared base, view related bugs Signed-off-by: Pranav C <pranavxc@gmail.com> * test: EE check for WSaccess Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: exclude EE tests Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: missing project delete closes #6215 Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: merge existing project meta if found closes #6216 Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: merge existing project meta if found closes #6216 Signed-off-by: Pranav C <pranavxc@gmail.com> --------- Signed-off-by: Pranav C <pranavxc@gmail.com> Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refactor: docs and other bug fixes Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: populate default project on super admin signup Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: include created project details in signup response if avail, missing Dockerfile Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: use custom function for resolving ts path aliases Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: add missing generate script Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: webpack build correction - ts path resolve Signed-off-by: Pranav C <pranavxc@gmail.com> --------- Signed-off-by: Pranav C <pranavxc@gmail.com> Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: mertmit <mertmit99@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Wing-Kam Wong <wingkwong.code@gmail.com>
1 year ago
const isPublic = inject(IsPublicInj, ref(false))
const { loadFormView, insertRow, formColumnData, formViewData, updateFormView } = useViewData(meta, view)
const reloadEventHook = inject(ReloadViewDataHookInj, createEventHook())
reloadEventHook.on(async () => {
await loadFormView()
setFormData()
})
const { fields, showAll, hideAll, saveOrUpdate } = useViewColumnsOrThrow()
const { state, row } = useProvideSmartsheetRowStore(
meta,
ref({
row: formState,
oldRow: {},
rowMeta: { new: true },
}),
)
const columns = computed(() => meta?.value?.columns || [])
const localColumns = ref<Record<string, any>[]>([])
const hiddenColumns = ref<Record<string, any>[]>([])
const draggableRef = ref()
const systemFieldsIds = ref<Record<string, any>[]>([])
const showColumnDropdown = ref(false)
const moved = ref(false)
const drag = ref(false)
const emailMe = ref(false)
const submitted = ref(false)
const activeRow = ref('')
const editEnabled = ref<boolean[]>([])
const searchQuery = ref('')
const { t } = useI18n()
const { betaFeatureToggleState } = useBetaFeatureToggle()
const visibleColumns = computed(() => localColumns.value.filter((f) => f.show).sort((a, b) => a.order - b.order))
const updateView = useDebounceFn(
() => {
updateFormView(formViewData.value)
},
300,
{ maxWait: 2000 },
)
async function submitForm() {
try {
await formRef.value?.validateFields()
} catch (e: any) {
e.errorFields.map((f: Record<string, any>) => message.error(f.errors.join(',')))
if (e.errorFields.length) return
}
await insertRow({
row: { ...formState, ...state.value },
oldRow: {},
rowMeta: { new: true },
})
submitted.value = true
}
async function clearForm() {
formState = {}
state.value = {}
reloadEventHook.trigger()
}
function isDbRequired(column: Record<string, any>) {
if (hiddenCols.includes(column.fk_column_id)) {
return false
}
let isRequired =
// confirm column is not virtual
(!isVirtualCol(column) &&
// column required / not null
column.rqd &&
// column default value
!column.cdf &&
// confirm it's not foreign key
!columns.value.some(
(c: Record<string, any>) =>
feat: Improved UI (#6222) * feat: Improved ui (#6156) * refactor: revert Signed-off-by: Pranav C <pranavxc@gmail.com> feat: shared base Signed-off-by: Pranav C <pranavxc@gmail.com> fix: remove duplicate import statement Signed-off-by: Pranav C <pranavxc@gmail.com> fix: disable starred & license menu Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: fix airtable wait issue Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: enable mysql in ci Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: fix checkbox order for sqlite Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: disable quick tests Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: fix dbType env variable for CI Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: workspace API access error fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: enable SQLite CI CD Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: use DB_TYPE env variable Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: enable SQLite UT Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: isHub cleanup Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: add check for EE Timezone spec Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> chore: cleanup Signed-off-by: Pranav C <pranavxc@gmail.com> chore: cleanup Signed-off-by: Pranav C <pranavxc@gmail.com> test: EE check fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> chore: test correction Signed-off-by: Pranav C <pranavxc@gmail.com> chore: sync latest changes Signed-off-by: Pranav C <pranavxc@gmail.com> test: set EE=false Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: set NC Edition to community in workflow file Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> chore: update sdk build command Signed-off-by: Pranav C <pranavxc@gmail.com> refactor: i18n and other changes Signed-off-by: Pranav C <pranavxc@gmail.com> feat: new ui Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: sync tests Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: lint Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: shared view/base related bugs Signed-off-by: Pranav C <pranavxc@gmail.com> * test: checkbox verification sort order fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: fix sqlite reset Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: enable selfhosted runners Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: table ops (draft) Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * Docs: screenshots for table-operations.md * refactor: introduce missing buttons Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: get all fields Signed-off-by: Pranav C <pranavxc@gmail.com> * test: UT fix- new data API response Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: EE is false Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: webhook lookup as string in CE Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: include created_at and updated_at Signed-off-by: Pranav C <pranavxc@gmail.com> * test: fix UT newDataAPI response for PG Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: separate api for webhook related plugins Signed-off-by: Pranav C <pranavxc@gmail.com> * test: msyql filter corrections Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: mysql group by test corrections Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: fix datatype for rating field in groupby spec for pg Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: kanban datatype correction Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: column edit for mysql- rating field Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: misc fixes Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: enable 4 workers Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: enable 2 workers per shard only Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: table CRUD * Rename table-operations.md to table-crud.md * Create column-crud.md * docs: row CRUD * Rename row.md to row-crud.md * docs: project crud * docs: toolbar (skeleton) * refactor: single page UI and bug fixes Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: sync tests playwright Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: add missing dependency Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: single page ui, test corrections Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: tests Signed-off-by: Pranav C <pranavxc@gmail.com> * test: project rename test correction Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: remove only Signed-off-by: Pranav C <pranavxc@gmail.com> * test: remove wrong import statement Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: delete option not visible in project context menu Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: move ws access within isEE() Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: fix groupby * test: groupby fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: signup & landing page * docs: project crud * docs: project-crud misc * docs: toolbar fields * docs: toolbar / filters * docs: toolbar / group by * docs: toolbar / sort * docs: toolbar / row height * docs: filters additional options * docs: file re-order Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: add links to column types * docs: code snippets * docs: links * docs: lookup * docs: rollup * docs: formula * docs: primary key * docs: display value * docs: development setup * docs: swagger * fix(nc-gui): encodeURIComponent for row id - closes: #6202 * docs: language * docs: expanded record * docs: import airtable * docs: airtable * docs: webhook * docs: revert file rename Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: account settings * docs: audit * docs: meta management * docs: project settings * docs: shared base * docs: shared view * docs: meta sync * docs: team-auth * docs: views * docs: fix URL * docs: URL corrections * fix: shared base, view related bugs Signed-off-by: Pranav C <pranavxc@gmail.com> * test: EE check for WSaccess Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: exclude EE tests Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: missing project delete closes #6215 Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: merge existing project meta if found closes #6216 Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: merge existing project meta if found closes #6216 Signed-off-by: Pranav C <pranavxc@gmail.com> --------- Signed-off-by: Pranav C <pranavxc@gmail.com> Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refactor: docs and other bug fixes Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: populate default project on super admin signup Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: include created project details in signup response if avail, missing Dockerfile Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: use custom function for resolving ts path aliases Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: add missing generate script Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: webpack build correction - ts path resolve Signed-off-by: Pranav C <pranavxc@gmail.com> --------- Signed-off-by: Pranav C <pranavxc@gmail.com> Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: mertmit <mertmit99@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Wing-Kam Wong <wingkwong.code@gmail.com>
1 year ago
isLinksOrLTAR(c.uidt) &&
c?.colOptions?.type === RelationTypes.BELONGS_TO &&
column.fk_column_id === c.colOptions.fk_child_column_id,
)) ||
// primary column
(column.pk && !column.ai && !column.cdf)
if (column.uidt === UITypes.LinkToAnotherRecord && column.colOptions.type === RelationTypes.BELONGS_TO) {
const col = columns.value.find((c: Record<string, any>) => c.id === column.colOptions.fk_child_column_id) as Record<
string,
any
>
if (col.rqd && !col.default) {
isRequired = true
}
}
return isRequired
}
/** Block user from drag n drop required column to hidden fields */
function onMoveCallback(event: any) {
if (event.from !== event.to && shouldSkipColumn(event.draggedContext.element)) {
return false
}
}
// Todo: reorder visible form fields
function onMove(event: any, isVisibleFormFields = false) {
let { newIndex, element, oldIndex } = event.moved
const fieldIndex = fields.value?.findIndex((f) => f?.fk_column_id === element.fk_column_id)
if (fieldIndex === -1 || fieldIndex === undefined) return
if (isVisibleFormFields) {
element = localColumns.value[localColumns.value?.findIndex((c) => c.fk_column_id === element.fk_column_id)]
newIndex = localColumns.value.findIndex(
(c) =>
c.fk_column_id ===
visibleColumns.value[newIndex > oldIndex ? newIndex - 1 : newIndex < oldIndex ? newIndex + 1 : newIndex].fk_column_id,
)
}
if (!localColumns.value.length || localColumns.value.length === 1) {
element.order = 1
} else if (localColumns.value.length - 1 === newIndex) {
element.order = (localColumns.value[newIndex - 1]?.order || 0) + 1
} else if (newIndex === 0) {
element.order = (localColumns.value[1]?.order || 0) / 2
} else {
element.order = ((localColumns.value[newIndex - 1]?.order || 0) + (localColumns.value[newIndex + 1].order || 0)) / 2
}
saveOrUpdate(element, fieldIndex)
$e('a:form-view:reorder')
}
function showOrHideColumn(column: Record<string, any>, show: boolean, isSidePannel = false) {
if (shouldSkipColumn(column)) {
// Required field can't be moved
!isSidePannel && message.info(t('msg.info.requriedFieldsCantBeMoved'))
return
}
const fieldIndex = fields.value?.findIndex((f) => f?.fk_column_id === column.fk_column_id)
if (fieldIndex !== -1 && fieldIndex !== undefined) {
saveOrUpdate(
{
...column,
show,
},
fieldIndex,
)
reloadEventHook.trigger()
if (show) {
$e('a:form-view:show-columns')
} else {
$e('a:form-view:hide-columns')
}
}
}
function shouldSkipColumn(col: Record<string, any>) {
return (
isDbRequired(col) || !!col.required || (!!col.rqd && !col.cdf) || col.uidt === UITypes.QrCode || col.uidt === UITypes.Barcode
)
}
async function handleAddOrRemoveAllColumns(value: boolean) {
if (value) {
for (const col of (localColumns as Record<string, any>)?.value) {
col.show = true
}
await showAll(systemFieldsIds.value)
$e('a:form-view:add-all')
} else {
for (const col of (localColumns as Record<string, any>)?.value) {
if (!shouldSkipColumn(col)) col.show = false
}
await hideAll(
(localColumns as Record<string, any>)?.value
.filter((col: Record<string, any>) => shouldSkipColumn(col))
.map((col: Record<string, any>) => col.fk_column_id),
)
$e('a:form-view:remove-all')
}
}
async function checkSMTPStatus() {
if (emailMe.value) {
const emailPluginActive = await $api.plugin.status('SMTP')
if (!emailPluginActive) {
emailMe.value = false
// Please activate SMTP plugin in App store for enabling email notification
message.info(t('msg.toast.formEmailSMTP'))
return false
}
}
return true
}
function setFormData() {
const col = formColumnData?.value || []
systemFieldsIds.value = getSystemColumns(col).map((c) => c.fk_column_id)
formViewData.value = {
...formViewData.value,
submit_another_form: !!(formViewData.value?.submit_another_form ?? 0),
show_blank_form: !!(formViewData.value?.show_blank_form ?? 0),
meta: {
hide_branding: false,
theme_color: '#F9F9FA',
...(parseProp(formViewData.value?.meta) ?? {}),
},
}
// email me
let data: Record<string, boolean> = {}
try {
data = JSON.parse(formViewData.value?.email || '') || {}
} catch (e) {}
emailMe.value = data[user.value?.email as string]
localColumns.value = col
.filter((f) => !hiddenColTypes.includes(f.uidt) && !systemFieldsIds.value.includes(f.fk_column_id))
.sort((a, b) => a.order - b.order)
.map((c) => ({ ...c, required: !!c.required }))
editEnabled.value = new Array(localColumns.value.length).fill(false)
hiddenColumns.value = col.filter(
(f) => !f.show && !systemFieldsIds.value.includes(f.fk_column_id) && !hiddenColTypes.includes(f.uidt),
)
}
function isRequired(_columnObj: Record<string, any>, required = false) {
let columnObj = _columnObj
feat: Improved UI (#6222) * feat: Improved ui (#6156) * refactor: revert Signed-off-by: Pranav C <pranavxc@gmail.com> feat: shared base Signed-off-by: Pranav C <pranavxc@gmail.com> fix: remove duplicate import statement Signed-off-by: Pranav C <pranavxc@gmail.com> fix: disable starred & license menu Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: fix airtable wait issue Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: enable mysql in ci Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: fix checkbox order for sqlite Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: disable quick tests Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: fix dbType env variable for CI Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: workspace API access error fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: enable SQLite CI CD Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: use DB_TYPE env variable Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: enable SQLite UT Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: isHub cleanup Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: add check for EE Timezone spec Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> chore: cleanup Signed-off-by: Pranav C <pranavxc@gmail.com> chore: cleanup Signed-off-by: Pranav C <pranavxc@gmail.com> test: EE check fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> chore: test correction Signed-off-by: Pranav C <pranavxc@gmail.com> chore: sync latest changes Signed-off-by: Pranav C <pranavxc@gmail.com> test: set EE=false Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> test: set NC Edition to community in workflow file Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> chore: update sdk build command Signed-off-by: Pranav C <pranavxc@gmail.com> refactor: i18n and other changes Signed-off-by: Pranav C <pranavxc@gmail.com> feat: new ui Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: sync tests Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: lint Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: shared view/base related bugs Signed-off-by: Pranav C <pranavxc@gmail.com> * test: checkbox verification sort order fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: fix sqlite reset Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: enable selfhosted runners Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: table ops (draft) Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * Docs: screenshots for table-operations.md * refactor: introduce missing buttons Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: get all fields Signed-off-by: Pranav C <pranavxc@gmail.com> * test: UT fix- new data API response Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: EE is false Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: webhook lookup as string in CE Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: include created_at and updated_at Signed-off-by: Pranav C <pranavxc@gmail.com> * test: fix UT newDataAPI response for PG Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: separate api for webhook related plugins Signed-off-by: Pranav C <pranavxc@gmail.com> * test: msyql filter corrections Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: mysql group by test corrections Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: fix datatype for rating field in groupby spec for pg Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: kanban datatype correction Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: column edit for mysql- rating field Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: misc fixes Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: enable 4 workers Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: enable 2 workers per shard only Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: table CRUD * Rename table-operations.md to table-crud.md * Create column-crud.md * docs: row CRUD * Rename row.md to row-crud.md * docs: project crud * docs: toolbar (skeleton) * refactor: single page UI and bug fixes Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: sync tests playwright Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: add missing dependency Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: single page ui, test corrections Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: tests Signed-off-by: Pranav C <pranavxc@gmail.com> * test: project rename test correction Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: remove only Signed-off-by: Pranav C <pranavxc@gmail.com> * test: remove wrong import statement Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: delete option not visible in project context menu Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: move ws access within isEE() Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: fix groupby * test: groupby fix Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: signup & landing page * docs: project crud * docs: project-crud misc * docs: toolbar fields * docs: toolbar / filters * docs: toolbar / group by * docs: toolbar / sort * docs: toolbar / row height * docs: filters additional options * docs: file re-order Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: add links to column types * docs: code snippets * docs: links * docs: lookup * docs: rollup * docs: formula * docs: primary key * docs: display value * docs: development setup * docs: swagger * fix(nc-gui): encodeURIComponent for row id - closes: #6202 * docs: language * docs: expanded record * docs: import airtable * docs: airtable * docs: webhook * docs: revert file rename Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * docs: account settings * docs: audit * docs: meta management * docs: project settings * docs: shared base * docs: shared view * docs: meta sync * docs: team-auth * docs: views * docs: fix URL * docs: URL corrections * fix: shared base, view related bugs Signed-off-by: Pranav C <pranavxc@gmail.com> * test: EE check for WSaccess Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * test: exclude EE tests Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> * fix: missing project delete closes #6215 Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: merge existing project meta if found closes #6216 Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: merge existing project meta if found closes #6216 Signed-off-by: Pranav C <pranavxc@gmail.com> --------- Signed-off-by: Pranav C <pranavxc@gmail.com> Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Wing-Kam Wong <wingkwong.code@gmail.com> * refactor: docs and other bug fixes Signed-off-by: Pranav C <pranavxc@gmail.com> * feat: populate default project on super admin signup Signed-off-by: Pranav C <pranavxc@gmail.com> * fix: include created project details in signup response if avail, missing Dockerfile Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: use custom function for resolving ts path aliases Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: add missing generate script Signed-off-by: Pranav C <pranavxc@gmail.com> * chore: webpack build correction - ts path resolve Signed-off-by: Pranav C <pranavxc@gmail.com> --------- Signed-off-by: Pranav C <pranavxc@gmail.com> Signed-off-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: mertmit <mertmit99@gmail.com> Co-authored-by: Raju Udava <86527202+dstala@users.noreply.github.com> Co-authored-by: DarkPhoenix2704 <anbarasun123@gmail.com> Co-authored-by: Wing-Kam Wong <wingkwong.code@gmail.com>
1 year ago
if (isLinksOrLTAR(columnObj.uidt) && columnObj.colOptions && columnObj.colOptions.type === RelationTypes.BELONGS_TO) {
columnObj = columns.value.find((c: Record<string, any>) => c.id === columnObj.colOptions.fk_child_column_id) as Record<
string,
any
>
}
return required || (columnObj && columnObj.rqd && !columnObj.cdf)
}
async function updateEmail() {
try {
if (!(await checkSMTPStatus())) return
const data = formViewData.value?.email ? JSON.parse(formViewData.value?.email) : {}
data[user.value?.email as string] = emailMe.value
formViewData.value!.email = JSON.stringify(data)
} catch (e) {}
}
function onEmailChange() {
updateEmail()
updateView()
}
async function submitCallback() {
await loadFormView()
setFormData()
showColumnDropdown.value = false
}
const updateColMeta = useDebounceFn(async (col: Record<string, any>) => {
if (col.id) {
try {
await $api.dbView.formColumnUpdate(col.id, col)
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
}, 250)
watch(submitted, (v) => {
if (v && formViewData.value?.show_blank_form) {
secondsRemain.value = 5
const intvl = setInterval(() => {
if (--secondsRemain.value < 0) {
submitted.value = false
clearInterval(intvl)
}
}, 1000)
}
})
function handleMouseUp(col: Record<string, any>, hiddenColIndex: number) {
if (isLocked.value) return
if (!moved.value) {
const index = localColumns.value.length
col.order = (index ? localColumns.value[index - 1].order : 0) + 1
col.show = true
saveOrUpdate(col, index)
}
}
const columnSupportsScanning = (elementType: UITypes) =>
betaFeatureToggleState.show &&
[UITypes.SingleLineText, UITypes.Number, UITypes.Email, UITypes.URL, UITypes.LongText].includes(elementType)
onClickOutside(draggableRef, () => {
activeRow.value = ''
})
onMounted(async () => {
await loadFormView()
setFormData()
})
watch(view, (nextView) => {
if (nextView?.type === ViewTypes.FORM) {
reloadEventHook.trigger()
}
})
const onFormItemClick = (element: any) => {
if (isLocked.value || !isEditable) return
activeRow.value = element.title
}
</script>
<template>
<div class="h-full">
<template v-if="isMobileMode">
<div class="pl-6 pr-[120px] py-6 bg-white flex-col justify-start items-start gap-2.5 inline-flex">
<div class="text-gray-500 text-5xl font-semibold leading-16">
{{ $t('general.available') }}<br />{{ $t('title.inDesktop') }}
</div>
<div class="text-gray-500 text-base font-medium leading-normal">
{{ $t('msg.formViewNotSupportedOnMobile') }}
</div>
</div>
</template>
<template v-else>
<a-row v-if="submitted" class="h-full" data-testid="nc-form-wrapper-submit">
<a-col :span="24">
<div v-if="formViewData" class="items-center justify-center text-center mt-2">
<a-alert type="success">
<template #message>
<div class="text-center">
{{ formViewData.success_msg || $t('msg.successfullySubmittedFormData') }}
</div>
</template>
</a-alert>
<div v-if="formViewData.show_blank_form" class="text-gray-400 mt-4">
{{
$t('msg.newFormWillBeLoaded', {
seconds: secondsRemain,
})
}}
</div>
<div v-if="formViewData.submit_another_form || !isPublic" class="text-center mt-4">
<a-button type="primary" size="large" @click="submitted = false"> {{ $t('activity.submitAnotherForm') }}</a-button>
</div>
</div>
</a-col>
</a-row>
<div v-else class="h-full w-full flex" data-testid="nc-form-wrapper">
<div
v-if="formViewData"
class="flex-1 h-full overflow-auto nc-form-scrollbar p-6 bg-gray-50"
:style="{background:(formViewData?.meta as Record<string,any>).theme_color}"
>
<div :class="isEditable ? 'min-w-[616px] overflow-x-auto nc-form-scrollbar' : ''">
<div class="h-[160px] bg-white border-gray-200 rounded-3xl overflow-hidden">
<!-- for future implementation of cover image -->
<!-- Todo: cover image uploader and image cropper to crop image in fixed aspect ratio -->
<LazyNuxtImg
v-if="formViewData.banner_image_url"
class="h-full"
:src="formViewData.banner_image_url"
alt="form-banner'"
placeholder
quality="75"
/>
<div v-else class="h-full flex items-stretch justify-between">
<img class="" src="~assets/img/form-banner-left.png" alt="form-banner-left'" />
<div class="w-[91px] grid place-items-center">
<img class="w-full" src="~assets/img/icons/256x256.png" alt="form-banner-logo'" />
</div>
<img class="" src="~assets/img/form-banner-right.png" alt="form-banner-left'" />
</div>
</div>
<a-card
class="!py-8 !lg:py-12 border-gray-200 !rounded-3xl !mt-6 max-w-[688px] !mx-auto"
:body-style="{
margin: '0 auto',
padding: '0px !important',
}"
>
<a-form ref="formRef" :model="formState" class="nc-form" no-style>
<!-- form header -->
<div class="flex flex-col gap-4 px-8 lg:px-12 mb-6">
<!-- Form logo -->
<!-- <div v-if="isEditable">
<div class="inline-block rounded-xl bg-gray-100 p-3">
<NcButton type="secondary" size="small" class="nc-form-upload-logo" data-testid="nc-form-upload-log">
<div class="flex gap-2 items-center">
<component :is="iconMap.upload" class="w-4 h-4" />
<span> Upload Logo </span>
</div>
</NcButton>
</div>
</div> -->
<!-- form title -->
<a-form-item v-if="isEditable">
<a-textarea
v-model:value="formViewData.heading"
class="w-full !font-bold !text-4xl !border-0 !border-b-1 !border-dashed !rounded-none !border-gray-400"
:style="{
'borderRightWidth': '0px !important',
'height': '70px',
'max-height': '250px',
'resize': 'vertical',
}"
autosize
size="large"
hide-details
:disabled="isLocked"
placeholder="Form Title"
:bordered="false"
data-testid="nc-form-heading"
@blur="updateView"
@keydown.enter="updateView"
/>
</a-form-item>
<div v-else class="w-full text-bold text-4xl">
{{ formViewData.heading }}
</div>
<!-- form description -->
<a-form-item v-if="isEditable" class="w-full">
<a-textarea
v-model:value="formViewData.subheading"
class="w-full !border-0 !border-b-1 !border-dashed !rounded-none !border-gray-400"
:style="{
'borderRightWidth': '0px !important',
'height': '40px',
'min-height': '40px',
'resize': 'vertical',
}"
size="large"
autosize
hide-details
:placeholder="$t('msg.info.formDesc')"
:bordered="false"
:disabled="!isEditable || isLocked"
data-testid="nc-form-sub-heading"
@blur="updateView"
@click="updateView"
/>
</a-form-item>
<div v-else class="w-full text-bold text-base">
{{ formViewData.subheading || '---' }}
</div>
</div>
<Draggable
ref="draggableRef"
:list="visibleColumns"
item-key="fk_column_id"
draggable=".item"
handle=".nc-form-field-drag-handler"
group="form-inputs"
ghost-class="nc-form-field-ghost"
class="h-full px-4 lg:px-6"
:move="onMoveCallback"
:disabled="isLocked || !isEditable"
@change="onMove($event, true)"
@start="drag = true"
@end="drag = false"
>
<template #item="{ element, index }">
<div
class="nc-editable item relative bg-white"
:class="[
`nc-form-drag-${element.title.replaceAll(' ', '')}`,
{
'rounded-2xl overflow-hidden border-2 cursor-pointer my-1': isEditable,
},
{
'p-4 lg:p-6 border-transparent my-0': !isEditable,
},
{
'nc-form-field-drag-handler border-transparent hover:(bg-gray-50) p-4 lg:p-6 ':
activeRow !== element.title && isEditable,
},
{
'border-brand-500': activeRow === element.title,
},
{
'!hover:bg-white !ring-0 !cursor-auto': isLocked,
},
]"
:style="{
transition: 'height 1s ease-in',
}"
data-testid="nc-form-fields"
@click="onFormItemClick(element)"
>
<div v-if="activeRow !== element.title">
<div>
<span data-testid="nc-form-field-title">
{{ element.label || element.title }}
</span>
<span v-if="element.required" class="text-red-500">&nbsp;*</span>
</div>
<div class="nc-form-help-text text-gray-500 text-sm mt-2" data-testid="nc-form-help-text">
{{ element.description }}
</div>
</div>
<!-- Field Header -->
<div v-if="activeRow === element.title" class="w-full flex gap-3 items-center px-3 py-2 bg-gray-50">
<component
:is="iconMap.drag"
class="nc-form-field-drag-handler flex-none cursor-move !h-4 !w-4 text-gray-600"
/>
<div class="flex-1 flex items-center max-w-[calc(100%_-_152px)]">
<SmartsheetHeaderVirtualCellIcon v-if="element && isVirtualCol(element)" :column-meta="element" />
<SmartsheetHeaderCellIcon v-else :column-meta="element" />
<NcTooltip class="truncate" show-on-truncate-only>
<template #title> {{ element.label || element.title }} </template>
<span data-testid="nc-form-field-title">
{{ element.label || element.title }}
</span>
</NcTooltip>
<span v-if="element.required" class="text-red-500">&nbsp;*</span>
</div>
<div class="flex gap-2 items-center">
<span
class="text-gray-500 mr-2 nc-form-input-required"
data-testid="nc-form-input-required"
@click="
() => {
element.required = !element.required
updateColMeta(element)
}
"
>
{{ $t('general.required') }}
</span>
<a-switch
v-model:checked="element.required"
v-e="['a:form-view:field:mark-required']"
size="small"
@change="updateColMeta(element)"
/>
</div>
</div>
<!-- Field Body -->
<div class="nc-form-field-body" :class="activeRow === element.title ? 'p-4 lg:p-6' : ''">
<template v-if="activeRow === element.title">
<a-form-item v-if="columnSupportsScanning(element.uidt)" class="my-0 !mb-1">
<div class="flex gap-2 items-center">
<span
class="text-gray-500 mr-2 nc-form-input-required"
data-testid="nc-form-input-enable-scanner"
@click="
() => {
element.general.enable_scanner = !element.general.enable_scanner
updateColMeta(element)
}
"
>
{{ $t('general.enableScanner') }}
</span>
<a-switch
v-model:checked="element.enable_scanner"
v-e="['a:form-view:field:mark-enable-scaner']"
size="small"
@change="updateColMeta(element)"
/>
</div>
</a-form-item>
<a-form-item class="my-0 !mb-2">
<a-input
v-model:value="element.label"
type="text"
class="form-meta-input nc-form-input-label"
data-testid="nc-form-input-label"
:placeholder="$t('msg.info.formInput')"
@change="updateColMeta(element)"
>
</a-input>
</a-form-item>
<a-form-item class="!my-0 !mb-3">
<a-input
v-model:value="element.description"
type="text"
class="form-meta-input text-sm nc-form-input-help-text"
data-testid="nc-form-input-help-text"
:placeholder="$t('msg.info.formHelpText')"
@change="updateColMeta(element)"
/>
</a-form-item>
</template>
<div :class="activeRow !== element.title ? 'mt-2' : ''">
<a-form-item
v-if="isVirtualCol(element)"
:name="element.title"
class="!my-0 nc-input-required-error nc-form-input-item"
:rules="[
{
required: isRequired(element, element.required),
message: `${element.label || element.title} is required`,
},
]"
>
<LazySmartsheetVirtualCell
v-model="formState[element.title]"
:row="row"
class="nc-input"
:class="`nc-form-input-${element.title.replaceAll(' ', '')}`"
:data-testid="`nc-form-input-${element.title.replaceAll(' ', '')}`"
:column="element"
@click.stop.prevent
/>
</a-form-item>
<a-form-item
v-else
:name="element.title"
class="!my-0 nc-input-required-error nc-form-input-item"
:rules="[
{
required: isRequired(element, element.required),
message: `${element.label || element.title} is required`,
},
]"
>
<LazySmartsheetDivDataCell class="relative">
<LazySmartsheetCell
v-model="formState[element.title]"
class="nc-input truncate"
:class="`nc-form-input-${element.title.replaceAll(' ', '')}`"
:data-testid="`nc-form-input-${element.title.replaceAll(' ', '')}`"
:column="element"
:edit-enabled="editEnabled[index]"
@click="editEnabled[index] = true"
@cancel="editEnabled[index] = false"
@update:edit-enabled="editEnabled[index] = $event"
@click.stop.prevent
/>
</LazySmartsheetDivDataCell>
</a-form-item>
</div>
</div>
<!-- Field Settings -->
<div
v-if="activeRow === element.title && false"
class="nc-form-field-settings border-t border-gray-200 p-4 lg:p-6"
>
<!-- Todo: Show on conditions, options limit,... -->
<div class="flex items-start gap-3 px-3 py-2 border-1 border-gray-200 rounded-lg">
<a-switch v-e="['a:form-view:field:show-on-condition']" size="small" />
<div>
<div class="font-medium text-gray-800">Show on condtions</div>
<div class="text-gray-500">Shows field only when conditions are met</div>
</div>
</div>
</div>
</div>
</template>
<template #footer>
<div
v-if="!visibleColumns.length"
class="mt-4 border-dashed border-2 border-gray-400 py-3 text-gray-400 text-center"
>
Select fields from right panel to add here
</div>
</template>
</Draggable>
<div class="flex justify-between items-center mt-6 !px-8 !lg:px-12">
<NcButton
html-type="reset"
type="secondary"
size="small"
:disabled="!isUIAllowed('dataInsert')"
class="nc-form-clear"
data-testid="nc-form-clear"
@click="clearForm"
>
Crear Form
</NcButton>
<NcButton
html-type="submit"
type="primary"
size="small"
:disabled="!isUIAllowed('dataInsert')"
class="nc-form-submit"
data-testid="nc-form-submit"
@click="submitForm"
>
{{ $t('general.submit') }}
</NcButton>
</div>
</a-form>
<div v-if="!parseProp(formViewData?.meta).hide_branding" class="px-8 lg:px-12">
<a-divider v-if="!isLocked" class="!my-8" />
<!-- Nocodb Branding -->
<div class="flex items-center gap-3">
<!-- Todo: add new NocoDB icon -->
<img src="~assets/img/icons/64x64.png" alt="NocoDB" class="w-6" />
<div class="text-sm text-gray-700">NocoDB Forms</div>
</div>
</div>
</a-card>
</div>
</div>
<div v-if="isEditable" class="h-full w-full max-w-[384px] nc-form-left-drawer border-l border-gray-200">
<Splitpanes horizontal class="w-full nc-form-right-splitpane">
<Pane min-size="30" size="50" class="nc-form-right-splitpane-item p-4 flex flex-col space-y-4 !min-h-200px">
<div class="flex flex-wrap justify-between items-center gap-2">
<div class="flex gap-3">
<div class="text-base font-bold text-gray-900">
<!-- {{ $t('objects.fields') }} -->
Form Fields
</div>
<NcBadge color="border-gray-200" class="">
{{ visibleColumns.length }}/{{ localColumns.length }} Field
</NcBadge>
</div>
<a-dropdown
v-model:visible="showColumnDropdown"
:trigger="['click']"
overlay-class-name="nc-dropdown-form-add-column"
>
<!-- v-if="localColumns.length" -->
<NcButton
type="secondary"
size="small"
class="nc-form-add-field"
data-testid="nc-form-add-field"
@click.stop="showColumnDropdown = true"
>
<div class="flex gap-2 items-center">
<component :is="iconMap.plus" class="w-4 h-4" />
<span> Add Field </span>
</div>
</NcButton>
<template #overlay>
<SmartsheetColumnEditOrAddProvider
v-if="showColumnDropdown"
@submit="submitCallback"
@cancel="showColumnDropdown = false"
@click.stop
@keydown.stop
/>
</template>
</a-dropdown>
</div>
<div>
<a-input
v-model:value="searchQuery"
data-testid="nc-form-field-search-input"
class="!h-9 !px-3 !py-1 !rounded-lg"
:placeholder="`${$t('placeholder.searchFields')}...`"
>
<template #prefix>
<GeneralIcon icon="search" class="mr-2 h-4 w-4 text-gray-500 group-hover:text-black" />
</template>
<template #suffix>
<GeneralIcon
v-if="searchQuery.length > 0"
icon="close"
class="ml-2 h-4 w-4 text-gray-500 group-hover:text-black"
data-testid="nc-form-field-clear-search"
@click="searchQuery = ''"
/>
</template>
</a-input>
</div>
<div class="nc-form-fields-list border-1 border-gray-200 rounded-lg overflow-y-auto nc-form-scrollbar">
<div v-if="!localColumns.length" class="px-0.5 py-2 text-gray-500 text-center">
{{ $t('title.noFieldsFound') }}
</div>
<template v-if="localColumns.length">
<div
key="nc-form-show-all-fields"
class="w-full p-2 flex items-center border-b-1 rounded-t-lg border-gray-200 bg-gray-50 sticky top-0 z-100"
data-testid="nc-form-show-all-fields"
@click.stop
>
<div class="w-4 h-4 flex-none mr-2"></div>
<div class="flex-1 flex flex-row items-center truncate cursor-pointer">
<div class="flex-1 font-base">Select all fields</div>
<NcSwitch :checked="visibleColumns.length === localColumns.length" @change="handleAddOrRemoveAllColumns" />
</div>
</div>
<Draggable
:list="localColumns"
item-key="id"
ghost-class="nc-form-field-ghost"
:style="{ height: 'calc(100% - 64px)' }"
@change="onMove($event)"
>
<template #item="{ element: field }">
<div
v-if="field.title.toLowerCase().includes(searchQuery.toLowerCase())"
:key="field.id"
class="w-full p-2 flex flex-row items-center border-b-1 last:border-none border-gray-200 hover:bg-gray-50"
:data-testid="`nc-form-field-item-${field.title}`"
>
<component :is="iconMap.drag" class="flex-none cursor-move !h-4 !w-4 text-gray-600 mr-1" />
<div
class="flex-1 flex items-center justify-between space-x-2 cursor-pointer max-w-[calc(100%_-_20px)]"
@click="showOrHideColumn(field, !field.show, true)"
>
<div class="flex-1 flex items-center max-w-[calc(100%_-_40px)]">
<SmartsheetHeaderVirtualCellIcon v-if="field && isVirtualCol(field)" :column-meta="field" />
<SmartsheetHeaderCellIcon v-else :column-meta="field" />
<NcTooltip class="truncate ml-1" show-on-truncate-only>
<template #title> {{ field.label || field.title }} </template>
<span data-testid="nc-field-title">
{{ field.label || field.title }}
</span>
</NcTooltip>
<span v-if="field.required" class="text-red-500">&nbsp;*</span>
</div>
<NcSwitch :checked="!!field.show" :disabled="field.required" />
</div>
</div>
</template>
<template
v-if="!localColumns?.filter((el) => el.title.toLowerCase().includes(searchQuery.toLowerCase())).length"
#footer
>
<div class="px-0.5 py-2 text-gray-500 text-center">
{{ $t('title.noFieldsFound') }} with title `{{ searchQuery }}`
</div>
</template>
</Draggable>
</template>
</div>
</Pane>
<Pane
v-if="isEditable && !isLocked && formViewData"
min-size="20"
size="50"
class="nc-form-right-splitpane-item !overflow-y-auto nc-form-scrollbar"
>
<div class="p-4 flex flex-col space-y-4 border-b border-gray-200">
<!-- Appearance Settings -->
<div class="text-base font-bold text-gray-900">Appearance Settings</div>
<div>
<div class="text-gray-800 mb-2">Form Theme</div>
<div class="flex justify-start">
<LazyGeneralColorPicker
v-model="(formViewData.meta as Record<string,any>).theme_color"
:colors="['#FFFFF', '#FFDBD9', '#FEE6D6', '#FFF0D1', '#D4F7E0', '#D7F2FF', '#FFCFE6', '#E5D4F5']"
class="nc-form-theme-color-picker !p-0 !-ml-1"
@input="(el:string)=>{
(formViewData?.meta as Record<string,any>).theme_color = el
updateView()
}"
/>
</div>
</div>
<div class="flex items-center">
<!-- Hide NocoDB Branding -->
<a-switch
v-model:checked="(formViewData.meta as Record<string,any>).hide_branding"
v-e="[`a:form-view:submit-another-form`]"
size="small"
class="nc-form-checkbox-submit-another-form"
data-testid="nc-form-checkbox-submit-another-form"
@change="updateView"
/>
<span class="ml-4">Hide NocoDB Branding</span>
</div>
</div>
<div class="p-4 flex flex-col space-y-4">
<!-- Post Form Submission Settings -->
<div class="text-base font-bold text-gray-900">
Post Form Submission Settings
<!-- {{ $t('msg.info.afterFormSubmitted') }} -->
</div>
<!-- Show this message -->
<div>
<div class="text-gray-800 mb-2">
Display Message
<!-- {{ $t('msg.info.showMessage') }} -->
</div>
<a-textarea
v-model:value="formViewData.success_msg"
:rows="3"
hide-details
class="nc-form-after-submit-msg !rounded-lg !px-3 !py-1"
data-testid="nc-form-after-submit-msg"
@change="updateView"
/>
</div>
<!-- Other options -->
<div class="flex flex-col gap-3">
<div class="flex items-center">
<!-- Show "Submit Another Form" button -->
<a-switch
v-model:checked="formViewData.submit_another_form"
v-e="[`a:form-view:submit-another-form`]"
size="small"
class="nc-form-checkbox-submit-another-form"
data-testid="nc-form-checkbox-submit-another-form"
@change="updateView"
/>
<span class="ml-4">{{ $t('msg.info.submitAnotherForm') }}</span>
</div>
<div class="flex items-center">
<!-- Show a blank form after 5 seconds -->
<a-switch
v-model:checked="formViewData.show_blank_form"
v-e="[`a:form-view:show-blank-form`]"
size="small"
class="nc-form-checkbox-show-blank-form"
data-testid="nc-form-checkbox-show-blank-form"
@change="updateView"
/>
<span class="ml-4">{{ $t('msg.info.showBlankForm') }}</span>
</div>
<div class="flex items-center">
<a-switch
v-model:checked="emailMe"
v-e="[`a:form-view:email-me`]"
size="small"
class="nc-form-checkbox-send-email"
data-testid="nc-form-checkbox-send-email"
@change="onEmailChange"
/>
<!-- Email me at <email> -->
<span class="ml-4">
{{ $t('msg.info.emailForm') }}
<span class="text-bold text-gray-600">{{ user?.email }}</span>
</span>
</div>
</div>
</div>
</Pane>
</Splitpanes>
</div>
</div>
</template>
</div>
</template>
<style scoped lang="scss">
.nc-editable:hover {
:deep(.nc-field-remove-icon) {
@apply opacity-100;
}
}
.nc-input {
@apply appearance-none w-full !bg-white rounded-lg px-2 py-2 border-solid border-1 border-gray-200 focus-within:border-brand-500;
&.nc-cell-rating,
&.nc-cell-geodata {
@apply !py-1;
}
:deep(input) {
@apply !px-1;
}
}
.form-meta-input {
@apply !rounded-lg !text-sm;
&::placeholder {
@apply !text-gray-500;
}
}
.nc-form-input-label {
@apply !px-4 !py-2;
}
.nc-form-input-help-text {
@apply !px-3 !py-1;
}
.nc-form-help-text,
.nc-input-required-error {
max-width: 100%;
white-space: pre-line;
}
:deep(.nc-cell-attachment) {
@apply p-0;
.nc-attachment-cell {
@apply px-4 min-h-[75px] w-full h-full;
.nc-attachment {
@apply md: (w-[50px] h-[50px]) lg:(w-[75px] h-[75px]) min-h-[50px] min-w-[50px];
}
.nc-attachment-cell-dropzone {
@apply rounded bg-gray-400/75;
}
}
}
.nc-form-input-item .nc-data-cell {
@apply !border-none rounded-none;
&:focus-within {
@apply !border-none;
}
}
:deep(.nc-form-right-splitpane .splitpanes__splitter) {
@apply !border-t-1 !border-gray-200 relative;
&::before {
@apply content-[':::'] block h-4 leading-12px px-2 font-bold text-gray-800 border-1 border-gray-200 rounded bg-white absolute -top-2.5 z-100 left-[calc(50%_-_16px)];
}
}
.nc-form-fields-list {
}
.nc-form-scrollbar {
@apply scrollbar scrollbar-thin scrollbar-thumb-gray-200 scrollbar-track-transparent;
&::-webkit-scrollbar-thumb:hover {
@apply !scrollbar-thumb-gray-300;
}
}
:deep(.nc-form-theme-color-picker .color-selector) {
@apply !text-white;
}
:deep(.nc-form-field-body .nc-cell) {
@apply my-0;
}
.nc-form-field-ghost {
@apply bg-gray-50;
}
</style>