mirror of https://github.com/nocodb/nocodb
Pranav C
2 years ago
committed by
GitHub
43 changed files with 803 additions and 183 deletions
@ -0,0 +1,50 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import { message } from 'ant-design-vue' |
||||||
|
import { extractSdkResponseErrorMsg } from '~/utils' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
modelValue: boolean |
||||||
|
} |
||||||
|
const props = defineProps<Props>() |
||||||
|
const emit = defineEmits(['update:modelValue']) |
||||||
|
|
||||||
|
const route = useRoute() |
||||||
|
const { loadSharedView } = useSharedView() |
||||||
|
|
||||||
|
const formState = ref({ password: undefined }) |
||||||
|
const vModel = useVModel(props, 'modelValue', emit) |
||||||
|
|
||||||
|
const onFinish = async () => { |
||||||
|
try { |
||||||
|
await loadSharedView(route.params.viewId as string, formState.value.password) |
||||||
|
vModel.value = false |
||||||
|
} catch (e: any) { |
||||||
|
console.error(e) |
||||||
|
message.error(await extractSdkResponseErrorMsg(e)) |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-modal |
||||||
|
v-model:visible="vModel" |
||||||
|
:closable="false" |
||||||
|
width="28rem" |
||||||
|
centered |
||||||
|
:footer="null" |
||||||
|
:mask-closable="false" |
||||||
|
@close="vModel = 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="formState" class="mt-2" @finish="onFinish"> |
||||||
|
<a-form-item name="password" :rules="[{ required: true, message: 'Password is required' }]"> |
||||||
|
<a-input-password v-model:value="formState.password" placeholder="Enter password" /> |
||||||
|
</a-form-item> |
||||||
|
<a-button type="primary" html-type="submit">Unlock</a-button> |
||||||
|
</a-form> |
||||||
|
</div> |
||||||
|
</a-modal> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"></style> |
@ -0,0 +1,85 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' |
||||||
|
import { FieldsInj, MetaInj } from '#imports' |
||||||
|
|
||||||
|
const fields = inject(FieldsInj, ref([])) |
||||||
|
const meta = inject(MetaInj) |
||||||
|
const { sharedView } = useSharedView() |
||||||
|
|
||||||
|
const formState = ref(fields.value.reduce((a, v) => ({ ...a, [v.title]: undefined }), {})) |
||||||
|
|
||||||
|
function isRequired(_columnObj: Record<string, any>, required = false) { |
||||||
|
let columnObj = _columnObj |
||||||
|
if ( |
||||||
|
columnObj.uidt === UITypes.LinkToAnotherRecord && |
||||||
|
columnObj.colOptions && |
||||||
|
columnObj.colOptions.type === RelationTypes.BELONGS_TO |
||||||
|
) { |
||||||
|
columnObj = fields.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) |
||||||
|
} |
||||||
|
|
||||||
|
useSmartsheetStoreOrThrow() |
||||||
|
useProvideSmartsheetRowStore(meta, formState) |
||||||
|
|
||||||
|
const formRef = ref() |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div class="flex flex-col my-4 space-y-2 mx-32 items-center"> |
||||||
|
<div class="flex w-2/3 flex-col mt-10"> |
||||||
|
<div class="flex flex-col items-start px-14 py-8 bg-gray-50 rounded-md w-full"> |
||||||
|
<a-typography-title class="border-b-1 border-gray-100 w-full pb-3" :level="1"> |
||||||
|
{{ sharedView.view.heading }} |
||||||
|
</a-typography-title> |
||||||
|
<a-typography class="pl-1 text-sm">{{ sharedView.view.subheading }}</a-typography> |
||||||
|
</div> |
||||||
|
|
||||||
|
<a-form ref="formRef" :model="formState" class="mt-8 pb-12 mb-8 px-3 bg-gray-50 rounded-md"> |
||||||
|
<div v-for="(field, index) in fields" :key="index" class="flex flex-col mt-4 px-10 pt-6 space-y-2"> |
||||||
|
<div class="flex"> |
||||||
|
<SmartsheetHeaderVirtualCell |
||||||
|
v-if="isVirtualCol(field)" |
||||||
|
:column="{ ...field, title: field.label || field.title }" |
||||||
|
:required="isRequired(field, field.required)" |
||||||
|
:hide-menu="true" |
||||||
|
/> |
||||||
|
<SmartsheetHeaderCell |
||||||
|
v-else |
||||||
|
:column="{ ...field, title: field.label || field.title }" |
||||||
|
:required="isRequired(field, field.required)" |
||||||
|
:hide-menu="true" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<a-form-item |
||||||
|
v-if="isVirtualCol(field)" |
||||||
|
class="ma-0 gap-0 pa-0" |
||||||
|
:name="field.title" |
||||||
|
:rules="[{ required: field.required, message: `${field.title} is required` }]" |
||||||
|
> |
||||||
|
<SmartsheetVirtualCell v-model="formState[field.title]" class="nc-input" :column="field" /> |
||||||
|
</a-form-item> |
||||||
|
<a-form-item |
||||||
|
v-else |
||||||
|
class="ma-0 gap-0 pa-0" |
||||||
|
:name="field.title" |
||||||
|
:rules="[{ required: field.required, message: `${field.title} is required` }]" |
||||||
|
> |
||||||
|
<SmartsheetCell v-model="formState[field.title]" class="nc-input" :column="field" :edit-enabled="true" /> |
||||||
|
</a-form-item> |
||||||
|
</div> |
||||||
|
</a-form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.nc-input { |
||||||
|
@apply w-full !bg-white rounded px-2 py-2 min-h-[40px] mt-2 mb-2 flex align-center border-solid border-1 border-primary; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,32 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import type { Ref } from 'vue' |
||||||
|
import type { TableType } from 'nocodb-sdk' |
||||||
|
|
||||||
|
import { ActiveViewInj, FieldsInj, IsPublicInj, MetaInj, ReadonlyInj, ReloadViewDataHookInj } from '~/context' |
||||||
|
|
||||||
|
const { sharedView, meta, columns } = useSharedView() |
||||||
|
|
||||||
|
const reloadEventHook = createEventHook<void>() |
||||||
|
provide(ReloadViewDataHookInj, reloadEventHook) |
||||||
|
provide(ReadonlyInj, ref(true)) |
||||||
|
provide(MetaInj, meta) |
||||||
|
provide(ActiveViewInj, sharedView) |
||||||
|
provide(FieldsInj, columns) |
||||||
|
provide(IsPublicInj, ref(true)) |
||||||
|
|
||||||
|
useProvideSmartsheetStore(sharedView as Ref<TableType>, meta) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div class="nc-container flex flex-col h-full mt-4 px-6"> |
||||||
|
<SmartsheetToolbar /> |
||||||
|
<SmartsheetGrid class="px-3" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.nc-container { |
||||||
|
height: calc(100% - var(--header-height)); |
||||||
|
flex: 1 1 100%; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,108 @@ |
|||||||
|
import type { ColumnType, ExportTypes, FilterType, PaginatedType, SortType, TableType, ViewType } from 'nocodb-sdk' |
||||||
|
import { UITypes } from 'nocodb-sdk' |
||||||
|
import { useNuxtApp } from '#app' |
||||||
|
|
||||||
|
export function useSharedView() { |
||||||
|
const nestedFilters = useState<(FilterType & { status?: 'update' | 'delete' | 'create'; parentId?: string })[]>( |
||||||
|
'nestedFilters', |
||||||
|
() => [], |
||||||
|
) |
||||||
|
const paginationData = useState<PaginatedType>('paginationData', () => ({ page: 1, pageSize: 25 })) |
||||||
|
const sharedView = useState<ViewType>('sharedView') |
||||||
|
const sorts = useState<SortType[]>('sorts', () => []) |
||||||
|
const password = useState<string | undefined>('password') |
||||||
|
const allowCSVDownload = useState<boolean>('allowCSVDownload', () => false) |
||||||
|
|
||||||
|
const meta = ref<TableType>(sharedView.value?.model) |
||||||
|
const columns = ref<ColumnType[]>(sharedView.value?.model?.columns) |
||||||
|
const formColumns = computed( |
||||||
|
() => |
||||||
|
columns.value |
||||||
|
.filter( |
||||||
|
(f: Record<string, any>) => |
||||||
|
f.show && f.uidt !== UITypes.Rollup && f.uidt !== UITypes.Lookup && f.uidt !== UITypes.Formula, |
||||||
|
) |
||||||
|
.sort((a: Record<string, any>, b: Record<string, any>) => a.order - b.order) |
||||||
|
.map((c: Record<string, any>) => ({ ...c, required: !!(c.required || 0) })) ?? [], |
||||||
|
) |
||||||
|
|
||||||
|
const { $api } = useNuxtApp() |
||||||
|
const { setMeta } = useMetas() |
||||||
|
|
||||||
|
const loadSharedView = async (viewId: string, localPassword: string | undefined = undefined) => { |
||||||
|
const viewMeta = await $api.public.sharedViewMetaGet(viewId, { |
||||||
|
headers: { |
||||||
|
'xc-password': localPassword ?? password.value, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
allowCSVDownload.value = JSON.parse(viewMeta.meta).allowCSVDownload |
||||||
|
|
||||||
|
if (localPassword) password.value = localPassword |
||||||
|
sharedView.value = viewMeta |
||||||
|
|
||||||
|
meta.value = viewMeta.model |
||||||
|
columns.value = viewMeta.model.columns |
||||||
|
|
||||||
|
setMeta(viewMeta.model) |
||||||
|
|
||||||
|
const relatedMetas = { ...viewMeta.relatedMetas } |
||||||
|
Object.keys(relatedMetas).forEach((key) => setMeta(relatedMetas[key])) |
||||||
|
} |
||||||
|
|
||||||
|
const fetchSharedViewData = async () => { |
||||||
|
const page = paginationData.value.page || 1 |
||||||
|
const pageSize = paginationData.value.pageSize || 25 |
||||||
|
|
||||||
|
const { data } = await $api.public.dataList( |
||||||
|
sharedView?.value?.uuid, |
||||||
|
{ |
||||||
|
offset: (page - 1) * pageSize, |
||||||
|
filterArrJson: JSON.stringify(nestedFilters.value), |
||||||
|
sortArrJson: JSON.stringify(sorts.value), |
||||||
|
} as any, |
||||||
|
{ |
||||||
|
headers: { |
||||||
|
'xc-password': password.value, |
||||||
|
}, |
||||||
|
}, |
||||||
|
) |
||||||
|
|
||||||
|
return data |
||||||
|
} |
||||||
|
|
||||||
|
const exportFile = async ( |
||||||
|
fields: any[], |
||||||
|
offset: number, |
||||||
|
type: ExportTypes.EXCEL | ExportTypes.CSV, |
||||||
|
responseType: 'base64' | 'blob', |
||||||
|
) => { |
||||||
|
return await $api.public.csvExport(sharedView.value?.uuid, type, { |
||||||
|
format: responseType as any, |
||||||
|
query: { |
||||||
|
fields: fields.map((field) => field.title), |
||||||
|
offset, |
||||||
|
sortArrJson: JSON.stringify(sorts.value), |
||||||
|
|
||||||
|
filterArrJson: JSON.stringify(nestedFilters.value), |
||||||
|
}, |
||||||
|
headers: { |
||||||
|
'xc-password': password.value, |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
sharedView, |
||||||
|
loadSharedView, |
||||||
|
meta, |
||||||
|
columns, |
||||||
|
nestedFilters, |
||||||
|
fetchSharedViewData, |
||||||
|
paginationData, |
||||||
|
sorts, |
||||||
|
exportFile, |
||||||
|
formColumns, |
||||||
|
allowCSVDownload, |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { navigateTo } from '#app' |
||||||
|
</script> |
||||||
|
|
||||||
|
<script lang="ts"> |
||||||
|
export default { |
||||||
|
name: 'SharedView', |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-layout id="nc-app"> |
||||||
|
<a-layout class="!flex-col"> |
||||||
|
<a-layout-header class="flex !bg-primary items-center text-white pl-3 pr-4 shadow-lg"> |
||||||
|
<div class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105" @click="navigateTo('/')"> |
||||||
|
<img width="35" alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="flex-1" /> |
||||||
|
</a-layout-header> |
||||||
|
|
||||||
|
<div class="w-full overflow-hidden" style="height: calc(100% - var(--header-height))"> |
||||||
|
<slot /> |
||||||
|
</div> |
||||||
|
</a-layout> |
||||||
|
</a-layout> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
:deep(.ant-dropdown-menu-item-group-title) { |
||||||
|
@apply border-b-1; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.ant-dropdown-menu-item-group-list) { |
||||||
|
@apply m-0; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,39 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import type { Ref } from 'vue' |
||||||
|
import type { TableType } from 'nocodb-sdk' |
||||||
|
import { ActiveViewInj, FieldsInj, IsPublicInj, MetaInj, ReloadViewDataHookInj, useRoute } from '#imports' |
||||||
|
|
||||||
|
definePageMeta({ |
||||||
|
requiresAuth: false, |
||||||
|
}) |
||||||
|
|
||||||
|
const route = useRoute() |
||||||
|
|
||||||
|
const reloadEventHook = createEventHook<void>() |
||||||
|
const { sharedView, loadSharedView, meta, formColumns } = useSharedView() |
||||||
|
|
||||||
|
await loadSharedView(route.params.viewId as string) |
||||||
|
|
||||||
|
provide(ReloadViewDataHookInj, reloadEventHook) |
||||||
|
provide(MetaInj, meta) |
||||||
|
provide(ActiveViewInj, sharedView) |
||||||
|
provide(FieldsInj, formColumns) |
||||||
|
provide(IsPublicInj, ref(true)) |
||||||
|
|
||||||
|
useProvideSmartsheetStore(sharedView as Ref<TableType>, meta) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<NuxtLayout id="content" class="flex"> |
||||||
|
<div class="nc-container flex flex-col h-full mt-2 px-6"> |
||||||
|
<SharedViewForm /> |
||||||
|
</div> |
||||||
|
</NuxtLayout> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.nc-container { |
||||||
|
height: calc(100% - var(--header-height)); |
||||||
|
flex: 1 1 100%; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,32 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import { ReadonlyInj, ReloadViewDataHookInj, useRoute } from '#imports' |
||||||
|
|
||||||
|
definePageMeta({ |
||||||
|
requiresAuth: false, |
||||||
|
layout: 'shared-view', |
||||||
|
}) |
||||||
|
|
||||||
|
const route = useRoute() |
||||||
|
|
||||||
|
const reloadEventHook = createEventHook<void>() |
||||||
|
provide(ReloadViewDataHookInj, reloadEventHook) |
||||||
|
provide(ReadonlyInj, ref(true)) |
||||||
|
|
||||||
|
const { loadSharedView } = useSharedView() |
||||||
|
const showPassword = ref(false) |
||||||
|
|
||||||
|
try { |
||||||
|
await loadSharedView(route.params.viewId as string) |
||||||
|
} catch (e) { |
||||||
|
showPassword.value = true |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<NuxtLayout id="content" class="flex" name="shared-view"> |
||||||
|
<div v-if="showPassword"> |
||||||
|
<SharedViewAskPassword v-model="showPassword" /> |
||||||
|
</div> |
||||||
|
<SharedViewGrid v-else /> |
||||||
|
</NuxtLayout> |
||||||
|
</template> |
Loading…
Reference in new issue