mirror of https://github.com/nocodb/nocodb
Wing-Kam Wong
2 years ago
88 changed files with 1802 additions and 1526 deletions
@ -1,96 +1,5 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { navigateTo } from '#app' |
|
||||||
import { useGlobal } from '#imports' |
|
||||||
|
|
||||||
const state = useGlobal() |
|
||||||
|
|
||||||
const sidebar = ref<HTMLDivElement>() |
|
||||||
|
|
||||||
const email = computed(() => state.user.value?.email ?? '---') |
|
||||||
|
|
||||||
const signOut = () => { |
|
||||||
state.signOut() |
|
||||||
navigateTo('/signin') |
|
||||||
} |
|
||||||
|
|
||||||
const sidebarCollapsed = computed({ |
|
||||||
get: () => !state.sidebarOpen.value, |
|
||||||
set: (val) => (state.sidebarOpen.value = !val), |
|
||||||
}) |
|
||||||
|
|
||||||
const toggleSidebar = () => { |
|
||||||
sidebarCollapsed.value = !sidebarCollapsed.value |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
<template> |
||||||
<a-layout class="min-h-[100vh]"> |
<NuxtLayout name="base"> |
||||||
<a-layout-header class="flex !bg-primary items-center text-white px-4 shadow-md"> |
|
||||||
<material-symbols-menu v-if="state.signedIn.value" class="text-xl cursor-pointer" @click="toggleSidebar" /> |
|
||||||
|
|
||||||
<div class="flex-1" /> |
|
||||||
|
|
||||||
<div class="ml-4 flex justify-center shrink"> |
|
||||||
<div class="flex items-center gap-2 cursor-pointer nc-noco-brand-icon" @click="navigateTo('/')"> |
|
||||||
<img width="35" src="~/assets/img/icons/512x512-trans.png" /> |
|
||||||
<span class="prose-xl">NocoDB</span> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="flex-1 text-left"> |
|
||||||
<div v-show="state.isLoading.value" class="flex items-center gap-2 ml-3"> |
|
||||||
{{ $t('general.loading') }} |
|
||||||
<mdi-reload :class="{ 'animate-infinite animate-spin': state.isLoading.value }" /> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div class="flex justify-end gap-4"> |
|
||||||
<general-language class="mr-3" /> |
|
||||||
|
|
||||||
<template v-if="state.signedIn.value"> |
|
||||||
<a-dropdown :trigger="['click']"> |
|
||||||
<mdi-dots-vertical class="md:text-xl cursor-pointer nc-user-menu" @click.prevent /> |
|
||||||
|
|
||||||
<template #overlay> |
|
||||||
<a-menu class="!py-0 nc-user-menu min-w-32 dark:(!bg-gray-800) leading-8 !rounded"> |
|
||||||
<a-menu-item key="0" class="!rounded-t"> |
|
||||||
<nuxt-link v-t="['c:navbar:user:email']" class="group flex items-center no-underline py-2" to="/user"> |
|
||||||
<MdiAt class="mt-1 group-hover:text-success" /> |
|
||||||
<span class="prose group-hover:text-black nc-user-menu-email">{{ email }}</span> |
|
||||||
</nuxt-link> |
|
||||||
</a-menu-item> |
|
||||||
|
|
||||||
<a-menu-divider class="!m-0" /> |
|
||||||
|
|
||||||
<a-menu-item key="1" class="!rounded-b"> |
|
||||||
<div v-t="['a:navbar:user:sign-out']" class="group flex items-center py-2" @click="signOut"> |
|
||||||
<mdi-logout class="dark:text-white group-hover:(!text-red-500)" /> |
|
||||||
|
|
||||||
<span class="prose font-semibold text-gray-500 group-hover:text-black nc-user-menu-signout"> |
|
||||||
{{ $t('general.signOut') }} |
|
||||||
</span> |
|
||||||
</div> |
|
||||||
</a-menu-item> |
|
||||||
</a-menu> |
|
||||||
</template> |
|
||||||
</a-dropdown> |
|
||||||
</template> |
|
||||||
</div> |
|
||||||
</a-layout-header> |
|
||||||
|
|
||||||
<a-layout> |
|
||||||
<a-layout-sider |
|
||||||
v-model:collapsed="sidebarCollapsed" |
|
||||||
width="300" |
|
||||||
collapsed-width="0" |
|
||||||
class="bg-white dark:!bg-gray-800 border-r-1 border-gray-200 dark:!border-gray-600 h-full" |
|
||||||
:trigger="null" |
|
||||||
collapsible |
|
||||||
> |
|
||||||
<div id="sidebar" ref="sidebar" class="w-full h-full" /> |
|
||||||
</a-layout-sider> |
|
||||||
|
|
||||||
<NuxtPage /> |
<NuxtPage /> |
||||||
</a-layout> |
</NuxtLayout> |
||||||
</a-layout> |
|
||||||
</template> |
</template> |
||||||
|
@ -0,0 +1,144 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { breakpointsTailwind } from '@vueuse/core' |
||||||
|
import { navigateTo } from '#app' |
||||||
|
import { computed, useBreakpoints, useGlobal, useProject, useRoute, useSidebar } from '#imports' |
||||||
|
|
||||||
|
/** get current breakpoints (for enabling sidebar) */ |
||||||
|
const breakpoints = useBreakpoints(breakpointsTailwind) |
||||||
|
|
||||||
|
const { signOut, signedIn, isLoading, user } = useGlobal() |
||||||
|
|
||||||
|
const { isOpen } = useSidebar({ isOpen: true }) |
||||||
|
|
||||||
|
const { project } = useProject() |
||||||
|
|
||||||
|
const route = useRoute() |
||||||
|
|
||||||
|
const email = computed(() => user.value?.email ?? '---') |
||||||
|
|
||||||
|
const logout = () => { |
||||||
|
signOut() |
||||||
|
navigateTo('/signin') |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-layout-sider |
||||||
|
:collapsed="isOpen" |
||||||
|
width="50" |
||||||
|
collapsed-width="0" |
||||||
|
class="nc-mini-sidebar !bg-primary h-full" |
||||||
|
:trigger="null" |
||||||
|
collapsible |
||||||
|
theme="light" |
||||||
|
> |
||||||
|
<a-dropdown placement="bottom" :trigger="['click']"> |
||||||
|
<div class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105"> |
||||||
|
<img width="35" alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<template v-if="signedIn" #overlay> |
||||||
|
<a-menu class="ml-2 !py-0 min-w-32 leading-8 !rounded"> |
||||||
|
<a-menu-item-group title="User Settings"> |
||||||
|
<a-menu-item key="email" class="!rounded-t"> |
||||||
|
<nuxt-link v-t="['c:navbar:user:email']" class="group flex items-center no-underline py-2" to="/user"> |
||||||
|
<MdiAt class="mt-1 group-hover:text-success" /> |
||||||
|
|
||||||
|
<span class="prose group-hover:text-black nc-user-menu-email">{{ email }}</span> |
||||||
|
</nuxt-link> |
||||||
|
</a-menu-item> |
||||||
|
|
||||||
|
<a-menu-divider class="!m-0" /> |
||||||
|
|
||||||
|
<a-menu-item key="signout" class="!rounded-b"> |
||||||
|
<div v-t="['a:navbar:user:sign-out']" class="group flex items-center py-2" @click="logout"> |
||||||
|
<MdiLogout class="dark:text-white group-hover:(!text-red-500)" /> |
||||||
|
<span class="prose font-semibold text-gray-500 group-hover:text-black nc-user-menu-signout"> |
||||||
|
{{ $t('general.signOut') }} |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
</a-menu-item-group> |
||||||
|
</a-menu> |
||||||
|
</template> |
||||||
|
</a-dropdown> |
||||||
|
|
||||||
|
<div id="sidebar" ref="sidebar" class="text-white flex-auto flex flex-col items-center w-full"> |
||||||
|
<a-dropdown :trigger="['contextmenu']" placement="right"> |
||||||
|
<div :class="[route.name === 'index' ? 'active' : '']" class="nc-mini-sidebar-item" @click="navigateTo('/')"> |
||||||
|
<MdiFolder class="cursor-pointer transform hover:scale-105 text-2xl" /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<template #overlay> |
||||||
|
<a-menu class="mt-6 select-none !py-0 min-w-32 leading-8 !rounded"> |
||||||
|
<a-menu-item-group> |
||||||
|
<template #title> |
||||||
|
<span class="cursor-pointer prose-sm text-gray-500 hover:text-primary" @click="navigateTo('/')"> |
||||||
|
{{ $t('objects.projects') }} |
||||||
|
</span> |
||||||
|
</template> |
||||||
|
|
||||||
|
<a-menu-item class="active:(ring ring-pink-500)"> |
||||||
|
<div |
||||||
|
v-t="['c:project:create:xcdb']" |
||||||
|
class="group flex items-center gap-2 py-2 hover:text-primary" |
||||||
|
@click="navigateTo('/project/create')" |
||||||
|
> |
||||||
|
<MdiPlus class="text-lg group-hover:text-pink-500" /> |
||||||
|
{{ $t('activity.createProject') }} |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
|
||||||
|
<a-menu-item class="rounded-b active:(ring ring-pink-500)"> |
||||||
|
<div |
||||||
|
v-t="['c:project:create:extdb']" |
||||||
|
class="group flex items-center gap-2 py-2 hover:text-primary" |
||||||
|
@click="navigateTo('/project/create-external')" |
||||||
|
> |
||||||
|
<MdiDatabaseOutline class="text-lg group-hover:text-pink-500" /> |
||||||
|
<div v-html="$t('activity.createProjectExtended.extDB')" /> |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
</a-menu-item-group> |
||||||
|
</a-menu> |
||||||
|
</template> |
||||||
|
</a-dropdown> |
||||||
|
|
||||||
|
<a-tooltip placement="right"> |
||||||
|
<template v-if="project" #title>{{ project.title }}</template> |
||||||
|
|
||||||
|
<div |
||||||
|
:class="[route.name.includes('nc-projectId') ? 'active' : 'pointer-events-none !text-gray-400']" |
||||||
|
class="nc-mini-sidebar-item" |
||||||
|
@click="navigateTo(`/nc/${route.params.projectId}`)" |
||||||
|
> |
||||||
|
<MdiDatabase class="cursor-pointer transform hover:scale-105 text-2xl" /> |
||||||
|
</div> |
||||||
|
</a-tooltip> |
||||||
|
</div> |
||||||
|
</a-layout-sider> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.nc-mini-sidebar { |
||||||
|
:deep(.ant-layout-sider-children) { |
||||||
|
@apply flex flex-col items-center; |
||||||
|
} |
||||||
|
|
||||||
|
.nc-mini-sidebar-item { |
||||||
|
@apply flex w-full justify-center items-center h-12 group p-2; |
||||||
|
|
||||||
|
&.active { |
||||||
|
@apply bg-pink-500 border-t-1 border-b-1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.ant-dropdown-menu-item-group-title) { |
||||||
|
@apply border-b-1; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.ant-dropdown-menu-item-group-list) { |
||||||
|
@apply m-0; |
||||||
|
} |
||||||
|
</style> |
@ -1,19 +0,0 @@ |
|||||||
<script setup lang="ts"> |
|
||||||
import MdiUnfoldMoreVertical from '~icons/mdi/unfold-more-vertical' |
|
||||||
import MdiUnfoldLessVertical from '~icons/mdi/unfold-less-vertical' |
|
||||||
import { inject, ref } from '#imports' |
|
||||||
import { RightSidebarInj } from '~/context' |
|
||||||
|
|
||||||
const sidebarOpen = inject(RightSidebarInj, ref(false)) |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<a-tooltip :placement="sidebarOpen ? 'bottomRight' : 'left'"> |
|
||||||
<template #title> {{ $t('tooltip.toggleNavDraw') }} </template> |
|
||||||
|
|
||||||
<div class="nc-sidebar-right-item hover:after:bg-pink-500 group"> |
|
||||||
<MdiUnfoldLessVertical v-if="sidebarOpen" class="group-hover:(!text-white)" @click="sidebarOpen = false" /> |
|
||||||
<MdiUnfoldMoreVertical v-else class="group-hover:(!text-white)" @click="sidebarOpen = true" /> |
|
||||||
</div> |
|
||||||
</a-tooltip> |
|
||||||
</template> |
|
@ -0,0 +1,58 @@ |
|||||||
|
import { useInjectionState, useToggle, watch } from '#imports' |
||||||
|
|
||||||
|
interface UseSidebarProps { |
||||||
|
hasSidebar?: boolean |
||||||
|
isOpen?: boolean |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Injection state for sidebars |
||||||
|
* |
||||||
|
* Use `provideSidebar` to provide the injection state on current component level (will affect all children injections) |
||||||
|
* Use `useSidebar` to use the injection state on current component level |
||||||
|
* |
||||||
|
* If `provideSidebar` is not called explicitly, `useSidebar` will trigger the provider if no injection state can be found |
||||||
|
*/ |
||||||
|
const [setup, use] = useInjectionState((props: UseSidebarProps = {}) => { |
||||||
|
const [isOpen, toggle] = useToggle(props.isOpen ?? false) |
||||||
|
const [hasSidebar, toggleHasSidebar] = useToggle(props.hasSidebar ?? true) |
||||||
|
|
||||||
|
watch( |
||||||
|
hasSidebar, |
||||||
|
(nextHasSidebar) => { |
||||||
|
if (!nextHasSidebar) toggle(false) |
||||||
|
}, |
||||||
|
{ immediate: true }, |
||||||
|
) |
||||||
|
|
||||||
|
watch( |
||||||
|
isOpen, |
||||||
|
(nextIsOpen) => { |
||||||
|
if (nextIsOpen && !hasSidebar.value) toggleHasSidebar(true) |
||||||
|
}, |
||||||
|
{ immediate: true }, |
||||||
|
) |
||||||
|
|
||||||
|
return { |
||||||
|
isOpen, |
||||||
|
toggle, |
||||||
|
hasSidebar, |
||||||
|
toggleHasSidebar, |
||||||
|
} |
||||||
|
}, 'useSidebar') |
||||||
|
|
||||||
|
export const provideSidebar = setup |
||||||
|
|
||||||
|
export function useSidebar(props: UseSidebarProps = {}) { |
||||||
|
const state = use() |
||||||
|
|
||||||
|
if (!state) { |
||||||
|
return setup(props) |
||||||
|
} else { |
||||||
|
// set state if props were passed
|
||||||
|
if (typeof props.isOpen !== 'undefined') state.isOpen.value = props.isOpen |
||||||
|
if (typeof props.hasSidebar !== 'undefined') state.hasSidebar.value = props.hasSidebar |
||||||
|
} |
||||||
|
|
||||||
|
return state |
||||||
|
} |
@ -1,66 +0,0 @@ |
|||||||
import type { TableType } from 'nocodb-sdk' |
|
||||||
import { UITypes } from 'nocodb-sdk' |
|
||||||
import { useToast } from 'vue-toastification' |
|
||||||
import { useProject } from './useProject' |
|
||||||
import { useNuxtApp } from '#app' |
|
||||||
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' |
|
||||||
|
|
||||||
export function useTableCreate(onTableCreate?: (tableMeta: TableType) => void) { |
|
||||||
const table = reactive<{ title: string; table_name: string; columns: string[] }>({ |
|
||||||
title: '', |
|
||||||
table_name: '', |
|
||||||
columns: { |
|
||||||
id: true, |
|
||||||
title: true, |
|
||||||
created_at: true, |
|
||||||
updated_at: true, |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
const { sqlUi, project, tables } = useProject() |
|
||||||
|
|
||||||
const toast = useToast() |
|
||||||
|
|
||||||
const { $api } = useNuxtApp() |
|
||||||
|
|
||||||
const createTable = async () => { |
|
||||||
try { |
|
||||||
if (!sqlUi?.value) return |
|
||||||
const columns = sqlUi?.value?.getNewTableColumns().filter((col) => { |
|
||||||
if (col.column_name === 'id' && table.columns.id_ag) { |
|
||||||
Object.assign(col, sqlUi?.value?.getDataTypeForUiType({ uidt: UITypes.ID }, 'AG')) |
|
||||||
col.dtxp = sqlUi?.value?.getDefaultLengthForDatatype(col.dt) |
|
||||||
col.dtxs = sqlUi?.value?.getDefaultScaleForDatatype(col.dt) |
|
||||||
return true |
|
||||||
} |
|
||||||
return !!table.columns[col.column_name] |
|
||||||
}) |
|
||||||
|
|
||||||
const tableMeta = await $api.dbTable.create(project?.value?.id as string, { |
|
||||||
...table, |
|
||||||
columns, |
|
||||||
}) |
|
||||||
|
|
||||||
onTableCreate?.(tableMeta) |
|
||||||
} catch (e: any) { |
|
||||||
toast.error(await extractSdkResponseErrorMsg(e)) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
watch( |
|
||||||
() => table.title, |
|
||||||
(title) => { |
|
||||||
table.table_name = `${project?.value?.prefix || ''}${title}` |
|
||||||
}, |
|
||||||
) |
|
||||||
|
|
||||||
const generateUniqueTitle = () => { |
|
||||||
let c = 1 |
|
||||||
while (tables?.value?.some((t) => t.title === `Sheet${c}`)) { |
|
||||||
c++ |
|
||||||
} |
|
||||||
table.title = `Sheet${c}` |
|
||||||
} |
|
||||||
|
|
||||||
return { table, createTable, generateUniqueTitle, tables, project } |
|
||||||
} |
|
@ -0,0 +1,101 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { breakpointsTailwind } from '@vueuse/core' |
||||||
|
import { navigateTo } from '#app' |
||||||
|
import { computed, useBreakpoints, useGlobal, useProject, useRoute } from '#imports' |
||||||
|
|
||||||
|
/** get current breakpoints (for enabling sidebar) */ |
||||||
|
const breakpoints = useBreakpoints(breakpointsTailwind) |
||||||
|
|
||||||
|
const { signOut, signedIn, isLoading, user } = useGlobal() |
||||||
|
|
||||||
|
const { project } = useProject() |
||||||
|
|
||||||
|
const route = useRoute() |
||||||
|
|
||||||
|
const email = computed(() => user.value?.email ?? '---') |
||||||
|
|
||||||
|
const logout = () => { |
||||||
|
signOut() |
||||||
|
navigateTo('/signin') |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-layout id="nc-app" has-sider> |
||||||
|
<div id="nc-sidebar-left" /> |
||||||
|
|
||||||
|
<a-layout class="!flex-col"> |
||||||
|
<a-layout-header class="flex !bg-primary items-center text-white pl-1 pr-4 shadow-lg"> |
||||||
|
<div |
||||||
|
v-if="route.name === 'index'" |
||||||
|
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 justify-center"> |
||||||
|
<div v-show="isLoading" class="flex items-center gap-2 ml-3"> |
||||||
|
{{ $t('general.loading') }} |
||||||
|
|
||||||
|
<MdiReload :class="{ 'animate-infinite animate-spin': isLoading }" /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="flex-1" /> |
||||||
|
|
||||||
|
<a-tooltip placement="left"> |
||||||
|
<template #title> Switch language </template> |
||||||
|
|
||||||
|
<div class="flex pr-4 items-center"> |
||||||
|
<GeneralLanguage class="cursor-pointer text-2xl" /> |
||||||
|
</div> |
||||||
|
</a-tooltip> |
||||||
|
|
||||||
|
<template v-if="signedIn"> |
||||||
|
<a-dropdown :trigger="['click']"> |
||||||
|
<MdiDotsVertical class="md:text-xl cursor-pointer nc-user-menu" @click.prevent /> |
||||||
|
|
||||||
|
<template #overlay> |
||||||
|
<a-menu class="!py-0 nc-user-menu dark:(!bg-gray-800) leading-8 !rounded"> |
||||||
|
<a-menu-item key="0" class="!rounded-t"> |
||||||
|
<nuxt-link v-t="['c:navbar:user:email']" class="group flex items-center no-underline py-2" to="/user"> |
||||||
|
<MdiAt class="mt-1 group-hover:text-success" /> |
||||||
|
|
||||||
|
<span class="prose group-hover:text-black nc-user-menu-email">{{ email }}</span> |
||||||
|
</nuxt-link> |
||||||
|
</a-menu-item> |
||||||
|
|
||||||
|
<a-menu-divider class="!m-0" /> |
||||||
|
|
||||||
|
<a-menu-item key="1" class="!rounded-b"> |
||||||
|
<div v-t="['a:navbar:user:sign-out']" class="group flex items-center py-2" @click="logout"> |
||||||
|
<MdiLogout class="dark:text-white group-hover:(!text-red-500)" /> |
||||||
|
|
||||||
|
<span class="prose font-semibold text-gray-500 group-hover:text-black nc-user-menu-signout"> |
||||||
|
{{ $t('general.signOut') }} |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
</a-menu> |
||||||
|
</template> |
||||||
|
</a-dropdown> |
||||||
|
</template> |
||||||
|
</a-layout-header> |
||||||
|
|
||||||
|
<div class="w-full" 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> |
File diff suppressed because it is too large
Load Diff
@ -1,29 +1,234 @@ |
|||||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||||
import { useTabs } from '#imports' |
import { navigateTo, provideSidebar, ref, useProject, useRoute, useSidebar, useTabs, useUIPermission } from '#imports' |
||||||
import { TabType } from '~/composables' |
import { TabType } from '~/composables' |
||||||
|
import { openLink } from '~/utils' |
||||||
|
|
||||||
const route = useRoute() |
const route = useRoute() |
||||||
const { loadProject, loadTables } = useProject(route.params.projectId as string) |
|
||||||
|
const { project, loadProject, loadTables } = useProject(route.params.projectId as string) |
||||||
|
|
||||||
const { addTab, clearTabs } = useTabs() |
const { addTab, clearTabs } = useTabs() |
||||||
const { $state } = useNuxtApp() |
|
||||||
|
const { isUIAllowed } = useUIPermission() |
||||||
|
|
||||||
|
// set old sidebar state |
||||||
|
useSidebar({ isOpen: true }) |
||||||
|
|
||||||
|
// create a new sidebar state |
||||||
|
const { isOpen, toggle } = provideSidebar({ isOpen: true }) |
||||||
|
|
||||||
|
const dialogOpen = ref(false) |
||||||
|
|
||||||
|
const openDialogKey = ref<string>() |
||||||
|
|
||||||
clearTabs() |
clearTabs() |
||||||
|
|
||||||
if (!route.params.type) { |
if (!route.params.type) { |
||||||
addTab({ type: TabType.AUTH, title: 'Team & Auth' }) |
addTab({ type: TabType.AUTH, title: 'Team & Auth' }) |
||||||
} |
} |
||||||
|
|
||||||
|
function toggleDialog(value?: boolean, key?: string) { |
||||||
|
dialogOpen.value = value ?? !dialogOpen.value |
||||||
|
openDialogKey.value = key |
||||||
|
} |
||||||
|
|
||||||
await loadProject(route.params.projectId as string) |
await loadProject(route.params.projectId as string) |
||||||
await loadTables() |
|
||||||
|
|
||||||
$state.sidebarOpen.value = true |
await loadTables() |
||||||
</script> |
</script> |
||||||
|
|
||||||
<template> |
<template> |
||||||
<NuxtLayout> |
<NuxtLayout id="content" class="flex"> |
||||||
<template #sidebar> |
<template #sidebar> |
||||||
<DashboardTreeView /> |
<a-layout-sider |
||||||
|
:collapsed="!isOpen" |
||||||
|
width="250" |
||||||
|
collapsed-width="50" |
||||||
|
class="relative shadow-md h-full z-1" |
||||||
|
:trigger="null" |
||||||
|
collapsible |
||||||
|
theme="light" |
||||||
|
> |
||||||
|
<div style="height: var(--header-height)" class="flex items-center !bg-primary text-white px-1 gap-2"> |
||||||
|
<div |
||||||
|
v-if="isOpen" |
||||||
|
class="w-[40px] min-w-[40px] transition-all duration-200 p-1 cursor-pointer transform hover:scale-105" |
||||||
|
@click="navigateTo('/')" |
||||||
|
> |
||||||
|
<img alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<a-dropdown :trigger="['click']"> |
||||||
|
<div |
||||||
|
:style="{ width: isOpen ? 'calc(100% - 40px) pr-2' : '100%' }" |
||||||
|
:class="[isOpen ? '' : 'justify-center']" |
||||||
|
class="group cursor-pointer flex gap-4 items-center" |
||||||
|
> |
||||||
|
<template v-if="isOpen"> |
||||||
|
<div class="text-xl font-semibold truncate">{{ project.title }}</div> |
||||||
|
|
||||||
|
<MdiChevronDown class="min-w-[28.5px] group-hover:text-pink-500 text-2xl" /> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template v-else> |
||||||
|
<MdiFolder class="text-primary cursor-pointer transform hover:scale-105 text-2xl" /> |
||||||
|
</template> |
||||||
|
</div> |
||||||
|
|
||||||
|
<template #overlay> |
||||||
|
<a-menu class="ml-6 !w-[300px] !text-sm !p-0 !rounded"> |
||||||
|
<a-menu-item-group> |
||||||
|
<template #title> |
||||||
|
<div class="group select-none flex items-center gap-4 py-1"> |
||||||
|
<MdiFolder class="group-hover:text-pink-500 text-xl" /> |
||||||
|
|
||||||
|
<div class="flex flex-col"> |
||||||
|
<div class="text-lg group-hover:(!text-primary) font-semibold truncate">{{ project.title }}</div> |
||||||
|
|
||||||
|
<div class="text-xs group-hover:text-pink-500 truncate font-italic">{{ project.id }}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
</template> |
</template> |
||||||
|
|
||||||
|
<a-menu-item key="copy"> |
||||||
|
<div class="nc-project-menu-item group"> |
||||||
|
<MdiContentCopy class="group-hover:text-pink-500" /> |
||||||
|
Copy Project Info |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
|
||||||
|
<a-menu-item key="api"> |
||||||
|
<div |
||||||
|
v-if="isUIAllowed('apiDocs')" |
||||||
|
v-t="['e:api-docs']" |
||||||
|
class="nc-project-menu-item group" |
||||||
|
@click.stop="openLink(`/api/v1/db/meta/projects/${route.params.projectId}/swagger`)" |
||||||
|
> |
||||||
|
<MdiApi class="group-hover:text-pink-500" /> |
||||||
|
Swagger: Rest APIs |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
|
||||||
|
<a-menu-divider /> |
||||||
|
|
||||||
|
<a-menu-item key="teamAndAuth"> |
||||||
|
<div |
||||||
|
v-if="isUIAllowed('settings')" |
||||||
|
v-t="['c:navdraw:project-settings']" |
||||||
|
class="nc-project-menu-item group" |
||||||
|
@click="toggleDialog(true, 'teamAndAuth')" |
||||||
|
> |
||||||
|
<MdiAccountGroup class="group-hover:text-pink-500" /> |
||||||
|
Team & Auth |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
|
||||||
|
<a-menu-item key="appStore"> |
||||||
|
<div |
||||||
|
v-if="isUIAllowed('settings')" |
||||||
|
v-t="['c:navdraw:project-settings']" |
||||||
|
class="nc-project-menu-item group" |
||||||
|
@click="toggleDialog(true, 'appStore')" |
||||||
|
> |
||||||
|
<MdiStore class="group-hover:text-pink-500" /> |
||||||
|
App Store |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
|
||||||
|
<a-menu-item key="metaData"> |
||||||
|
<div |
||||||
|
v-if="isUIAllowed('settings')" |
||||||
|
v-t="['c:navdraw:project-settings']" |
||||||
|
class="nc-project-menu-item group" |
||||||
|
@click="toggleDialog(true, 'metaData')" |
||||||
|
> |
||||||
|
<MdiTableBorder class="group-hover:text-pink-500" /> |
||||||
|
Project Metadata |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
|
||||||
|
<a-menu-item key="audit"> |
||||||
|
<div |
||||||
|
v-if="isUIAllowed('settings')" |
||||||
|
v-t="['c:navdraw:project-settings']" |
||||||
|
class="nc-project-menu-item group" |
||||||
|
@click="toggleDialog(true, 'audit')" |
||||||
|
> |
||||||
|
<MdiNotebookCheckOutline class="group-hover:text-pink-500" /> |
||||||
|
Audit |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
|
||||||
|
<a-menu-divider /> |
||||||
|
|
||||||
|
<a-sub-menu key="preview-as"> |
||||||
|
<template #title> |
||||||
|
<div class="nc-project-menu-item group"> |
||||||
|
<MdiContentCopy class="group-hover:text-pink-500" /> |
||||||
|
Preview Project As |
||||||
|
|
||||||
|
<div class="flex-1" /> |
||||||
|
|
||||||
|
<MaterialSymbolsChevronRightRounded |
||||||
|
class="transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template #expandIcon></template> |
||||||
|
|
||||||
|
<a-menu-item> Foo </a-menu-item> |
||||||
|
</a-sub-menu> |
||||||
|
</a-menu-item-group> |
||||||
|
</a-menu> |
||||||
|
</template> |
||||||
|
</a-dropdown> |
||||||
|
</div> |
||||||
|
|
||||||
|
<a-tooltip placement="right"> |
||||||
|
<template #title> Toggle table list </template> |
||||||
|
|
||||||
|
<div |
||||||
|
class="group color-transition cursor-pointer hover:ring active:ring-pink-500 z-1 flex items-center absolute top-1/2 right-[-0.75rem] shadow bg-gray-100 rounded-full" |
||||||
|
> |
||||||
|
<MaterialSymbolsChevronLeftRounded |
||||||
|
v-if="isOpen" |
||||||
|
class="transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400" |
||||||
|
@click="toggle(false)" |
||||||
|
/> |
||||||
|
|
||||||
|
<MaterialSymbolsChevronRightRounded |
||||||
|
v-else |
||||||
|
class="transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400" |
||||||
|
@click="toggle(true)" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</a-tooltip> |
||||||
|
|
||||||
|
<DashboardTreeView v-show="isOpen" /> |
||||||
|
</a-layout-sider> |
||||||
|
</template> |
||||||
|
|
||||||
|
<dashboard-settings-modal v-model="dialogOpen" :open-key="openDialogKey" /> |
||||||
|
|
||||||
<NuxtPage /> |
<NuxtPage /> |
||||||
</NuxtLayout> |
</NuxtLayout> |
||||||
</template> |
</template> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.nc-project-menu-item { |
||||||
|
@apply cursor-pointer flex items-center gap-2 py-2 hover:text-primary after:(content-[''] absolute top-0 left-0 bottom-0 right-0 w-full h-full bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5)); |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.ant-dropdown-menu-item-group-title) { |
||||||
|
@apply border-b-1; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.ant-dropdown-menu-item-group-list) { |
||||||
|
@apply m-0; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.ant-dropdown-menu-item) { |
||||||
|
@apply !py-0 active:(ring ring-pink-500); |
||||||
|
} |
||||||
|
</style> |
||||||
|
@ -0,0 +1,4 @@ |
|||||||
|
declare module '@ckpack/vue-color' { |
||||||
|
import type { Component } from '@vue/runtime-core' |
||||||
|
const Sketch: Component |
||||||
|
} |
Loading…
Reference in new issue