Browse Source

feat(nc-gui): use shared view meta for survey mode and theme config

pull/3669/head
braks 2 years ago committed by Raju Udava
parent
commit
a672ef3040
  1. 93
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  2. 6
      packages/nc-gui/composables/useSharedFormViewStore.ts
  3. 28
      packages/nc-gui/pages/[projectType]/form/[viewId].vue
  4. 59
      packages/nc-gui/pages/[projectType]/form/[viewId]/index.vue
  5. 6
      packages/nc-gui/pages/[projectType]/form/[viewId]/index/survey.vue

93
packages/nc-gui/components/smartsheet/toolbar/ShareView.vue

@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ViewTypes } from 'nocodb-sdk' import { ViewTypes } from 'nocodb-sdk'
import { isString } from '@vueuse/core'
import { import {
computed, computed,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
@ -15,6 +16,21 @@ import {
useUIPermission, useUIPermission,
watch, watch,
} from '#imports' } from '#imports'
import type { ThemeConfig } from '~/lib'
interface SharedViewMeta extends Record<string, any> {
surveyMode?: boolean
theme?: Partial<ThemeConfig>
allowCSVDownload?: boolean
}
interface SharedView {
uuid?: string
id: string
password: string | null
type?: ViewTypes
meta: SharedViewMeta
}
const { theme } = useTheme() const { theme } = useTheme()
@ -36,29 +52,39 @@ let showShareModel = $ref(false)
const passwordProtected = ref(false) const passwordProtected = ref(false)
const surveyMode = ref(false) const shared = ref<SharedView>({ id: '', meta: {}, password: null })
const withTheme = ref(false)
const shared = ref()
const allowCSVDownload = computed({ const allowCSVDownload = computed({
get() { get: () => !!shared.value.meta.allowCSVDownload,
return !!(shared.value?.meta && typeof shared.value.meta === 'string' ? JSON.parse(shared.value.meta) : shared.value.meta) set: (allow) => {
?.allowCSVDownload shared.value.meta = { ...shared.value.meta, allowCSVDownload: allow }
},
set(allow) {
shared.value.meta = { allowCSVDownload: allow }
saveAllowCSVDownload() saveAllowCSVDownload()
}, },
}) })
const surveyMode = computed({
get: () => !!shared.value.meta.surveyMode,
set: (survey) => {
shared.value.meta = { ...shared.value.meta, surveyMode: survey }
saveSurveyMode()
},
})
const viewTheme = computed({
get: () => !!shared.value.meta.theme,
set: (hasTheme) => {
shared.value.meta = { ...shared.value.meta, theme: hasTheme ? { ...theme.value } : undefined }
saveTheme()
},
})
const genShareLink = async () => { const genShareLink = async () => {
if (!view.value?.id) return if (!view.value?.id) return
shared.value = await $api.dbViewShare.create(view.value.id) const response = (await $api.dbViewShare.create(view.value.id)) as SharedView
shared.value.meta = const meta = isString(response.meta) ? JSON.parse(response.meta) : response.meta
shared.value.meta && typeof shared.value.meta === 'string' ? JSON.parse(shared.value.meta) : shared.value.meta
shared.value = { ...response, meta }
passwordProtected.value = shared.value.password !== null && shared.value.password !== '' passwordProtected.value = shared.value.password !== null && shared.value.password !== ''
@ -80,24 +106,27 @@ const sharedViewUrl = computed(() => {
viewType = 'view' viewType = 'view'
} }
let url = `${dashboardUrl?.value}#/nc/${viewType}/${shared.value.uuid}` return `${dashboardUrl?.value}#/nc/${viewType}/${shared.value.uuid}`
})
/** if survey mode is enabled, append survey path segment */ async function saveAllowCSVDownload() {
if (surveyMode.value) { await updateSharedViewMeta()
url = `${url}/survey` $e(`a:view:share:${allowCSVDownload.value ? 'enable' : 'disable'}-csv-download`)
} }
/** if theme is enabled, append theme query params */ async function saveSurveyMode() {
if (withTheme.value) { await updateSharedViewMeta()
url = `${url}?theme=${theme.value.primaryColor.replace('#', '')},${theme.value.accentColor.replace('#', '')}` $e(`a:view:share:${surveyMode.value ? 'enable' : 'disable'}-survey-mode`)
} }
return url async function saveTheme() {
}) await updateSharedViewMeta()
$e(`a:view:share:${viewTheme.value ? 'enable' : 'disable'}-theme`)
}
async function saveAllowCSVDownload() { async function updateSharedViewMeta() {
try { try {
const meta = shared.value.meta && typeof shared.value.meta === 'string' ? JSON.parse(shared.value.meta) : shared.value.meta const meta = shared.value.meta && isString(shared.value.meta) ? JSON.parse(shared.value.meta) : shared.value.meta
await $api.dbViewShare.update(shared.value.id, { await $api.dbViewShare.update(shared.value.id, {
meta, meta,
@ -108,14 +137,12 @@ async function saveAllowCSVDownload() {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }
if (allowCSVDownload.value) { return true
$e('a:view:share:enable-csv-download')
} else {
$e('a:view:share:disable-csv-download')
}
} }
const saveShareLinkPassword = async () => { const saveShareLinkPassword = async () => {
if (!shared.value.password) return
try { try {
await $api.dbViewShare.update(shared.value.id, { await $api.dbViewShare.update(shared.value.id, {
password: shared.value.password, password: shared.value.password,
@ -129,9 +156,9 @@ const saveShareLinkPassword = async () => {
$e('a:view:share:enable-pwd') $e('a:view:share:enable-pwd')
} }
const copyLink = () => { const copyLink = async () => {
if (sharedViewUrl.value) { if (sharedViewUrl.value) {
copy(sharedViewUrl.value) await copy(sharedViewUrl.value)
// Copied to clipboard // Copied to clipboard
message.success(t('msg.info.copiedToClipboard')) message.success(t('msg.info.copiedToClipboard'))
@ -195,7 +222,7 @@ watch(passwordProtected, (value) => {
<div> <div>
<!-- todo: i18n --> <!-- todo: i18n -->
<a-checkbox v-model:checked="withTheme" class="!text-xs"> Use Theme </a-checkbox> <a-checkbox v-model:checked="viewTheme" class="!text-xs"> Use Theme </a-checkbox>
</div> </div>
<div> <div>

6
packages/nc-gui/composables/useSharedFormViewStore.ts

@ -3,6 +3,7 @@ import { minLength, required } from '@vuelidate/validators'
import type { Ref } from 'vue' import type { Ref } from '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 { isString } from '@vueuse/core'
import { import {
SharedViewPasswordInj, SharedViewPasswordInj,
computed, computed,
@ -31,6 +32,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
const sharedFormView = ref<FormType>() const sharedFormView = ref<FormType>()
const meta = ref<TableType>() const meta = ref<TableType>()
const columns = ref<(ColumnType & { required?: boolean; show?: boolean; label?: string })[]>() const columns = ref<(ColumnType & { required?: boolean; show?: boolean; label?: string })[]>()
const sharedViewMeta = ref<any>({})
const { api, isLoading } = useApi() const { api, isLoading } = useApi()
@ -65,6 +67,9 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
meta.value = viewMeta.model meta.value = viewMeta.model
columns.value = viewMeta.model?.columns columns.value = viewMeta.model?.columns
const _sharedViewMeta = (viewMeta as any).meta
sharedViewMeta.value = isString(_sharedViewMeta) ? JSON.parse(_sharedViewMeta) : _sharedViewMeta
await setMeta(viewMeta.model) await setMeta(viewMeta.model)
const relatedMetas = { ...viewMeta.relatedMetas } const relatedMetas = { ...viewMeta.relatedMetas }
@ -205,6 +210,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
secondsRemain, secondsRemain,
passwordDlg, passwordDlg,
isLoading, isLoading,
sharedViewMeta,
} }
}, 'expanded-form-store') }, 'expanded-form-store')

28
packages/nc-gui/pages/[projectType]/form/[viewId].vue

@ -22,7 +22,9 @@ useSidebar('nc-left-sidebar', { hasSidebar: false })
const route = useRoute() const route = useRoute()
const { loadSharedView, sharedView, meta, notFound } = useProvideSharedFormStore(route.params.viewId as string) const { loadSharedView, sharedView, meta, notFound, password, passwordDlg } = useProvideSharedFormStore(
route.params.viewId as string,
)
await loadSharedView() await loadSharedView()
@ -39,6 +41,30 @@ if (!notFound.value) {
<template> <template>
<NuxtLayout> <NuxtLayout>
<NuxtPage /> <NuxtPage />
<a-modal
v-model:visible="passwordDlg"
:closable="false"
width="28rem"
centered
:footer="null"
:mask-closable="false"
wrap-class-name="nc-modal-shared-form-password-dlg"
@close="passwordDlg = false"
>
<div class="w-full flex flex-col">
<a-typography-title :level="4">This shared view is protected</a-typography-title>
<a-form ref="formRef" :model="{ password }" class="mt-2" @finish="passwordDlg = false">
<a-form-item name="password" :rules="[{ required: true, message: $t('msg.error.signUpRules.passwdRequired') }]">
<a-input-password v-model:value="password" :placeholder="$t('msg.info.signUp.enterPassword')" />
</a-form-item>
<!-- Unlock -->
<a-button type="primary" html-type="submit">{{ $t('general.unlock') }}</a-button>
</a-form>
</div>
</a-modal>
</NuxtLayout> </NuxtLayout>
</template> </template>

59
packages/nc-gui/pages/[projectType]/form/[viewId]/index.vue

@ -1,25 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { useDark, useRoute, useSharedFormStoreOrThrow, useTheme, watch } from '#imports' import { navigateTo, useDark, useRoute, useRouter, useSharedFormStoreOrThrow, useTheme, watch } from '#imports'
const { passwordDlg, password, loadSharedView } = useSharedFormStoreOrThrow() const { sharedViewMeta } = useSharedFormStoreOrThrow()
const route = useRoute()
const isDark = useDark() const isDark = useDark()
const { setTheme } = useTheme() const { setTheme } = useTheme()
const route = useRoute()
const router = useRouter()
watch( watch(
() => route.query.theme, () => sharedViewMeta.value.theme,
(nextTheme) => { (nextTheme) => {
if (nextTheme) { if (nextTheme) setTheme(nextTheme)
const theme = (nextTheme as string).split(',').map((t) => t.trim() && `#${t}`)
setTheme({
primaryColor: theme[0],
accentColor: theme[1],
})
}
}, },
{ immediate: true }, { immediate: true },
) )
@ -27,6 +22,20 @@ watch(
const onClick = () => { const onClick = () => {
isDark.value = !isDark.value isDark.value = !isDark.value
} }
const shouldRedirect = (to: string) => {
if (sharedViewMeta.value.surveyMode) {
if (!to.includes('survey')) navigateTo(`/nc/form/${route.params.viewId}/survey`)
} else {
navigateTo(`/nc/form/${route.params.viewId}`)
}
}
shouldRedirect(route.name as string)
router.afterEach((to) => {
shouldRedirect(to.name as string)
})
</script> </script>
<template> <template>
@ -48,30 +57,6 @@ const onClick = () => {
<MaterialSymbolsLightModeOutline v-else /> <MaterialSymbolsLightModeOutline v-else />
</Transition> </Transition>
</div> </div>
<a-modal
v-model:visible="passwordDlg"
:closable="false"
width="28rem"
centered
:footer="null"
:mask-closable="false"
wrap-class-name="nc-modal-shared-form-password-dlg"
@close="passwordDlg = false"
>
<div class="w-full flex flex-col">
<a-typography-title :level="4">This shared view is protected</a-typography-title>
<a-form ref="formRef" :model="{ password }" class="mt-2" @finish="loadSharedView">
<a-form-item name="password" :rules="[{ required: true, message: $t('msg.error.signUpRules.passwdRequired') }]">
<a-input-password v-model:value="password" :placeholder="$t('msg.info.signUp.enterPassword')" />
</a-form-item>
<!-- Unlock -->
<a-button type="primary" html-type="submit">{{ $t('general.unlock') }}</a-button>
</a-form>
</div>
</a-modal>
</div> </div>
</template> </template>

6
packages/nc-gui/pages/[projectType]/form/[viewId]/index/survey.vue

@ -136,7 +136,7 @@ onKeyStroke(['ArrowRight', 'ArrowUp', 'Enter', 'Space'], goNext)
<div ref="el" class="w-full min-h-2/3 grid grid-rows-2"> <div ref="el" class="w-full min-h-2/3 grid grid-rows-2">
<template v-if="sharedFormView"> <template v-if="sharedFormView">
<div class="max-w-[max(33%,600px)] m-auto flex flex-col justify-end"> <div class="max-w-[max(33%,600px)] m-auto flex flex-col justify-end">
<h1 class="prose-2xl font-bold my-4">{{ sharedFormView.heading }}</h1> <h1 class="prose-2xl font-bold self-center my-4">{{ sharedFormView.heading }}</h1>
<h2 class="prose-lg text-slate-500 dark:text-slate-300 self-center mb-4"> <h2 class="prose-lg text-slate-500 dark:text-slate-300 self-center mb-4">
{{ sharedFormView.subheading }} {{ sharedFormView.subheading }}
@ -226,7 +226,9 @@ onKeyStroke(['ArrowRight', 'ArrowUp', 'Enter', 'Space'], goNext)
</a-tooltip> </a-tooltip>
<!-- todo: i18n --> <!-- todo: i18n -->
<div class="text-sm flex items-center gap-1">Press Enter <MaterialSymbolsKeyboardReturn class="mt-1" /></div> <div class="text-sm text-gray-500 flex items-center gap-1">
Press Enter <MaterialSymbolsKeyboardReturn class="mt-1" />
</div>
</div> </div>
</div> </div>
</div> </div>

Loading…
Cancel
Save