Browse Source

chore(gui-v2): update styles

pull/3023/head
braks 2 years ago
parent
commit
65f0b1db1a
  1. 106
      packages/nc-gui-v2/app.vue
  2. 2
      packages/nc-gui-v2/assets/style-v2.scss
  3. 50
      packages/nc-gui-v2/components/dashboard/TreeView.vue
  4. 45
      packages/nc-gui-v2/components/dashboard/settings/Modal.vue
  5. 2
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue
  6. 17
      packages/nc-gui-v2/components/smartsheet/sidebar/index.vue
  7. 2
      packages/nc-gui-v2/pages/index/index.vue
  8. 40
      packages/nc-gui-v2/pages/index/user/index.vue
  9. 16
      packages/nc-gui-v2/pages/index/user/index/index.vue
  10. 129
      packages/nc-gui-v2/pages/nc/[projectId]/index.vue
  11. 4
      packages/nc-gui-v2/pages/nc/[projectId]/index/index.vue
  12. 4
      packages/nc-gui-v2/pages/signin.vue

106
packages/nc-gui-v2/app.vue

@ -1,24 +1,24 @@
<script lang="ts" setup> <script lang="ts" setup>
import { breakpointsTailwind } from '@vueuse/core' import { breakpointsTailwind } from '@vueuse/core'
import { navigateTo } from '#app' import { navigateTo } from '#app'
import { computed, provideSidebar, ref, useBreakpoints, useGlobal, useRoute, useRouter } from '#imports' import { computed, ref, useBreakpoints, useGlobal, useRoute, useRouter, useSidebar } from '#imports'
/** get current breakpoints (for enabling sidebar) */ /** get current breakpoints (for enabling sidebar) */
const breakpoints = useBreakpoints(breakpointsTailwind) const breakpoints = useBreakpoints(breakpointsTailwind)
const { signOut, signedIn, isLoading, user } = $(useGlobal()) const { signOut, signedIn, isLoading, user } = useGlobal()
const { isOpen } = provideSidebar({ isOpen: (signedIn && breakpoints.greater('md').value) || true }) const { isOpen } = useSidebar({ isOpen: signedIn.value && breakpoints.isGreater('md') })
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
console.log(route.name)
const sidebar = ref<HTMLDivElement>() const sidebar = ref<HTMLDivElement>()
const email = computed(() => user?.email ?? '---') const globalSearch = ref('')
const email = computed(() => user.value?.email ?? '---')
const logout = () => { const logout = () => {
signOut() signOut()
@ -32,21 +32,23 @@ const logout = () => {
:collapsed="!isOpen" :collapsed="!isOpen"
width="50" width="50"
collapsed-width="0" collapsed-width="0"
class="!bg-primary h-full shadow-lg" class="nc-sidebar-left !bg-primary h-full"
:trigger="null" :trigger="null"
collapsible collapsible
theme="light" theme="light"
> >
<a-dropdown :trigger="['click']"> <a-dropdown placement="bottom" :trigger="['click']">
<div class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105 border-b-1 border-r-1"> <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" /> <img width="35" alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" />
</div> </div>
<template v-if="signedIn" #overlay> <template v-if="signedIn" #overlay>
<a-menu class="!py-0 nc-user-menu min-w-32 dark:(!bg-gray-800) leading-8 !rounded"> <a-menu class="ml-2 !py-0 nc-user-menu min-w-32 dark:(!bg-gray-800) leading-8 !rounded">
<a-menu-item-group title="User Settings">
<a-menu-item key="0" class="!rounded-t"> <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"> <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" />&nbsp; <MdiAt class="mt-1 group-hover:text-success" />
&nbsp;
<span class="prose group-hover:text-black nc-user-menu-email">{{ email }}</span> <span class="prose group-hover:text-black nc-user-menu-email">{{ email }}</span>
</nuxt-link> </nuxt-link>
</a-menu-item> </a-menu-item>
@ -61,34 +63,37 @@ const logout = () => {
</span> </span>
</div> </div>
</a-menu-item> </a-menu-item>
</a-menu-item-group>
</a-menu> </a-menu>
</template> </template>
</a-dropdown> </a-dropdown>
<div id="sidebar" ref="sidebar" class="text-white flex flex-col items-center w-full"> <div id="sidebar" ref="sidebar" class="text-white flex-auto flex flex-col items-center w-full">
<div <a-tooltip placement="right">
:class="[route.name.includes('nc-projectId') ? 'bg-pink-500' : '']" <template #title>My Projects</template>
class="flex w-full justify-center items-center h-12 group p-2"
> <div :class="[route.name === 'index' ? 'active' : '']" class="nc-sidebar-left-item" @click="navigateTo('/')">
<MdiDatabase class="cursor-pointer transform hover:scale-105 text-2xl" /> <MdiFolder class="cursor-pointer transform hover:scale-105 text-2xl" />
</div> </div>
</a-tooltip>
<a-tooltip placement="right"> <a-tooltip placement="right">
<template #title> Switch language </template> <template #title>Project {{ route.params.projectId }}</template>
<div class="flex w-full justify-center items-center h-12 group p-2"> <div
<general-language class="cursor-pointer text-2xl" /> :class="[route.name.includes('nc-projectId') ? 'active' : 'pointer-events-none !text-gray-400']"
class="nc-sidebar-left-item"
@click="navigateTo(`/nc/${route.params.projectId}`)"
>
<MdiDatabase class="cursor-pointer transform hover:scale-105 text-2xl" />
</div> </div>
</a-tooltip> </a-tooltip>
<div class="flex w-full justify-center items-center h-12 group p-2">
<MdiLightningBoltOutline class="cursor-not-allowed text-2xl text-gray-400" />
</div>
</div> </div>
</a-layout-sider> </a-layout-sider>
<a-layout class="!flex-col"> <a-layout class="!flex-col">
<a-layout-header class="flex !bg-primary items-center text-white !px-[1px] shadow-lg"> <a-layout-header class="flex !bg-primary items-center text-white !px-[1px] shadow-lg">
<div id="header-start" class="w-[250px] flex items-center px-4 h-full" /> <div id="header-start" class="w-[250px] flex items-center px-1 h-full" />
<div class="hidden flex justify-center"> <div class="hidden flex justify-center">
<div v-show="isLoading" class="flex items-center gap-2 ml-3"> <div v-show="isLoading" class="flex items-center gap-2 ml-3">
@ -97,7 +102,9 @@ const logout = () => {
</div> </div>
</div> </div>
<div class="flex-1 text-white"> <div class="flex-1" />
<div v-if="signedIn" class="text-white">
<div class="flex items-center px-4 gap-4"> <div class="flex items-center px-4 gap-4">
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title> Go back </template> <template #title> Go back </template>
@ -116,8 +123,29 @@ const logout = () => {
@click="router.go(+1)" @click="router.go(+1)"
/> />
</a-tooltip> </a-tooltip>
<a-input
v-model:value="globalSearch"
class="nc-global-search group hover:ring active:ring-pink-500 focus:ring-pink-500 flex"
size="small"
placeholder="CMD + K"
>
<template #prefix>
<MdiMagnify class="transform text-gray-400 group-hover:(scale-105 text-pink-500)" />
</template>
</a-input>
</div> </div>
</div> </div>
<div class="flex-1" />
<a-tooltip placement="right">
<template #title> Switch language </template>
<div class="flex pr-4 items-center">
<general-language class="cursor-pointer text-2xl" />
</div>
</a-tooltip>
</a-layout-header> </a-layout-header>
<div class="w-full h-full"> <div class="w-full h-full">
@ -126,3 +154,31 @@ const logout = () => {
</a-layout> </a-layout>
</a-layout> </a-layout>
</template> </template>
<style lang="scss" scoped>
.nc-sidebar-left {
:deep(.ant-layout-sider-children) {
@apply flex flex-col items-center;
}
.nc-sidebar-left-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;
}
.nc-global-search {
@apply dark:(bg-gray-700 !text-white) !appearance-none my-1 border-1 border-solid border-primary/50 rounded;
}
</style>

2
packages/nc-gui-v2/assets/style-v2.scss

@ -75,7 +75,7 @@ html {
// menu item styling // menu item styling
.nc-menu-item { .nc-menu-item {
@apply cursor-pointer text-xs flex align-center gap-2 px-4 py-3 relative after:(content-[''] absolute top-0 left-0 w-full h-full right 0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5)); @apply cursor-pointer text-xs flex items-center gap-2 px-4 py-3 after:(content-[''] absolute top-0 left-0 bottom-0 w-full h-full right-0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5));
} }
.nc-sidebar-right-item { .nc-sidebar-right-item {

50
packages/nc-gui-v2/components/dashboard/TreeView.vue

@ -2,10 +2,8 @@
import type { TableType } from 'nocodb-sdk' import type { TableType } from 'nocodb-sdk'
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
import { useToast } from 'vue-toastification' import { useToast } from 'vue-toastification'
import SettingsModal from './settings/SettingsModal.vue' import { useProject, useTable, useTabs, watchEffect } from '#imports'
import { computed, useProject, useTable, useTabs, useUIPermission, watchEffect } from '#imports'
import { useNuxtApp, useRoute } from '#app' import { useNuxtApp, useRoute } from '#app'
import MdiSettingIcon from '~icons/mdi/cog'
import MdiTable from '~icons/mdi/table' import MdiTable from '~icons/mdi/table'
import MdiView from '~icons/mdi/eye-circle-outline' import MdiView from '~icons/mdi/eye-circle-outline'
import MdiTableLarge from '~icons/mdi/table-large' import MdiTableLarge from '~icons/mdi/table-large'
@ -13,15 +11,17 @@ import MdiMenuDown from '~icons/mdi/chevron-down'
import MdiPlus from '~icons/mdi/plus-circle-outline' import MdiPlus from '~icons/mdi/plus-circle-outline'
import MdiDrag from '~icons/mdi/drag-vertical' import MdiDrag from '~icons/mdi/drag-vertical'
import MdiMenuIcon from '~icons/mdi/dots-vertical' import MdiMenuIcon from '~icons/mdi/dots-vertical'
import MdiAPIDocIcon from '~icons/mdi/open-in-new'
const { addTab } = useTabs() const { addTab } = useTabs()
const toast = useToast() const toast = useToast()
const { $api, $e } = useNuxtApp() const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const route = useRoute() const route = useRoute()
const { tables, loadTables } = useProject(route.params.projectId as string) const { tables, loadTables } = useProject(route.params.projectId as string)
const { closeTab } = useTabs()
const { deleteTable } = useTable() const { deleteTable } = useTable()
const tablesById = $computed<Record<string, TableType>>(() => const tablesById = $computed<Record<string, TableType>>(() =>
@ -31,12 +31,14 @@ const tablesById = $computed<Record<string, TableType>>(() =>
}, {}), }, {}),
) )
const settingsDlg = ref(false)
const showTableList = ref(true) const showTableList = ref(true)
const tableCreateDlg = ref(false) const tableCreateDlg = ref(false)
const tableDeleteDlg = ref(false)
const menuRef = $ref<HTMLLIElement>() const menuRef = $ref<HTMLLIElement>()
let key = $ref(0) let key = $ref(0)
let sortable: Sortable let sortable: Sortable
// todo: replace with vuedraggable // todo: replace with vuedraggable
@ -103,21 +105,14 @@ const icon = (table: TableType) => {
} }
} }
const apiLink = computed(
() =>
// new URL(
`/api/v1/db/meta/projects/${route.params.projectId}/swagger`,
// todo: get siteUrl
// this.$store.state.project.appInfo && this.$store.state.project.appInfo.ncSiteUrl
// ),
)
const filterQuery = $ref('') const filterQuery = $ref('')
const filteredTables = $computed(() => { const filteredTables = $computed(() => {
return tables?.value?.filter((table) => !filterQuery || table?.title.toLowerCase()?.includes(filterQuery.toLowerCase())) return tables?.value?.filter((table) => !filterQuery || table?.title.toLowerCase()?.includes(filterQuery.toLowerCase()))
}) })
const contextMenuTarget = reactive<{ type?: 'table' | 'main'; value?: any }>({}) const contextMenuTarget = reactive<{ type?: 'table' | 'main'; value?: any }>({})
const setMenuContext = (type: 'table' | 'main', value?: any) => { const setMenuContext = (type: 'table' | 'main', value?: any) => {
contextMenuTarget.type = type contextMenuTarget.type = type
contextMenuTarget.value = value contextMenuTarget.value = value
@ -125,16 +120,20 @@ const setMenuContext = (type: 'table' | 'main', value?: any) => {
} }
const renameTableDlg = ref(false) const renameTableDlg = ref(false)
const renameTableMeta = ref() const renameTableMeta = ref()
const showRenameTableDlg = (table: TableType, rightClick = false) => { const showRenameTableDlg = (table: TableType, rightClick = false) => {
$e(rightClick ? 'c:table:rename:navdraw:right-click' : 'c:table:rename:navdraw:options') $e(rightClick ? 'c:table:rename:navdraw:right-click' : 'c:table:rename:navdraw:options')
renameTableMeta.value = table renameTableMeta.value = table
renameTableDlg.value = true renameTableDlg.value = true
} }
const reloadTables = async () => { const reloadTables = async () => {
$e('a:table:refresh:navdraw') $e('a:table:refresh:navdraw')
await loadTables() await loadTables()
} }
const addTableTab = (table: TableType) => { const addTableTab = (table: TableType) => {
$e('a:table:open') $e('a:table:open')
addTab({ title: table.title, id: table.id, type: table.type as any }) addTab({ title: table.title, id: table.id, type: table.type as any })
@ -173,7 +172,7 @@ const addTableTab = (table: TableType) => {
<div class="transition-height duration-200 overflow-hidden" :class="{ 'h-100': showTableList, 'h-0': !showTableList }"> <div class="transition-height duration-200 overflow-hidden" :class="{ 'h-100': showTableList, 'h-0': !showTableList }">
<div :key="key" ref="menuRef" class="border-none sortable-list"> <div :key="key" ref="menuRef" class="border-none sortable-list">
<div <div
v-for="table in tables" v-for="table of tables"
:key="table.id" :key="table.id"
v-t="['a:table:open']" v-t="['a:table:open']"
:class="[{ hidden: !filteredTables?.includes(table) }, `nc-project-tree-tbl nc-project-tree-tbl-${table.title}`]" :class="[{ hidden: !filteredTables?.includes(table) }, `nc-project-tree-tbl nc-project-tree-tbl-${table.title}`]"
@ -223,22 +222,7 @@ const addTableTab = (table: TableType) => {
</a-menu> </a-menu>
</template> </template>
</a-dropdown> </a-dropdown>
<div class="w-full h-[1px] bg-gray-200" />
<a v-if="isUIAllowed('apiDocs')" v-t="['e:api-docs']" class="nc-treeview-footer-item" :href="apiLink" target="_blank">
<MdiAPIDocIcon class="mr-2" />
<span> {{ $t('title.apiDocs') }}</span>
</a>
<div
v-if="isUIAllowed('settings')"
v-t="['c:navdraw:project-settings']"
class="nc-treeview-footer-item nc-team-settings"
@click="settingsDlg = true"
>
<MdiSettingIcon class="mr-2" />
<span> {{ $t('title.teamAndSettings') }}</span>
</div>
<SettingsModal :show="settingsDlg" @closed="settingsDlg = false" />
<DlgTableCreate v-if="tableCreateDlg" v-model="tableCreateDlg" /> <DlgTableCreate v-if="tableCreateDlg" v-model="tableCreateDlg" />
<DlgTableRename v-if="renameTableMeta" v-model="renameTableDlg" :table-meta="renameTableMeta" /> <DlgTableRename v-if="renameTableMeta" v-model="renameTableDlg" :table-meta="renameTableMeta" />
</div> </div>

45
packages/nc-gui-v2/components/dashboard/settings/SettingsModal.vue → packages/nc-gui-v2/components/dashboard/settings/Modal.vue

@ -10,9 +10,11 @@ import StoreFrontOutline from '~icons/mdi/storefront-outline'
import TeamFillIcon from '~icons/ri/team-fill' import TeamFillIcon from '~icons/ri/team-fill'
import MultipleTableIcon from '~icons/mdi/table-multiple' import MultipleTableIcon from '~icons/mdi/table-multiple'
import NootbookOutline from '~icons/mdi/notebook-outline' import NootbookOutline from '~icons/mdi/notebook-outline'
import { useVModel, watch } from '#imports'
interface Props { interface Props {
show: boolean modelValue: boolean
openKey?: string
} }
interface SubTabGroup { interface SubTabGroup {
@ -30,9 +32,11 @@ interface TabGroup {
} }
} }
const { show } = defineProps<Props>() const props = defineProps<Props>()
const emits = defineEmits(['closed']) const emits = defineEmits(['update:modelValue'])
const vModel = useVModel(props, 'modelValue', emits)
const tabsInfo: TabGroup = { const tabsInfo: TabGroup = {
teamAndAuth: { teamAndAuth: {
@ -41,11 +45,11 @@ const tabsInfo: TabGroup = {
subTabs: { subTabs: {
usersManagement: { usersManagement: {
title: 'Users Management', title: 'Users Management',
body: () => UserManagement, body: UserManagement,
}, },
apiTokenManagement: { apiTokenManagement: {
title: 'API Token Management', title: 'API Token Management',
body: () => ApiTokenManagement, body: ApiTokenManagement,
}, },
}, },
}, },
@ -55,7 +59,7 @@ const tabsInfo: TabGroup = {
subTabs: { subTabs: {
new: { new: {
title: 'Apps', title: 'Apps',
body: () => AppStore, body: AppStore,
}, },
}, },
}, },
@ -65,11 +69,11 @@ const tabsInfo: TabGroup = {
subTabs: { subTabs: {
metaData: { metaData: {
title: 'Metadata', title: 'Metadata',
body: () => Metadata, body: Metadata,
}, },
acl: { acl: {
title: 'UI Access Control', title: 'UI Access Control',
body: () => UIAcl, body: UIAcl,
}, },
}, },
}, },
@ -79,7 +83,7 @@ const tabsInfo: TabGroup = {
subTabs: { subTabs: {
audit: { audit: {
title: 'Audit', title: 'Audit',
body: () => AuditTab, body: AuditTab,
}, },
}, },
}, },
@ -88,7 +92,7 @@ const tabsInfo: TabGroup = {
const firstKeyOfObject = (obj: object) => Object.keys(obj)[0] const firstKeyOfObject = (obj: object) => Object.keys(obj)[0]
// Array of keys of tabs which are selected. In our case will be only one. // Array of keys of tabs which are selected. In our case will be only one.
const selectedTabKeys = $ref<string[]>([firstKeyOfObject(tabsInfo)]) let selectedTabKeys = $ref<string[]>([firstKeyOfObject(tabsInfo)])
const selectedTab = $computed(() => tabsInfo[selectedTabKeys[0]]) const selectedTab = $computed(() => tabsInfo[selectedTabKeys[0]])
let selectedSubTabKeys = $ref<string[]>([firstKeyOfObject(selectedTab.subTabs)]) let selectedSubTabKeys = $ref<string[]>([firstKeyOfObject(selectedTab.subTabs)])
@ -100,18 +104,27 @@ watch(
selectedSubTabKeys = [firstKeyOfObject(tabsInfo[newTabKey].subTabs)] selectedSubTabKeys = [firstKeyOfObject(tabsInfo[newTabKey].subTabs)]
}, },
) )
watch(
() => props.openKey,
(nextOpenKey) => {
selectedTabKeys = [Object.keys(tabsInfo).find((key) => key === nextOpenKey) || firstKeyOfObject(tabsInfo)]
},
)
</script> </script>
<template> <template>
<a-modal :footer="null" :visible="show" width="max(90vw, 600px)" @cancel="emits('closed')"> <a-modal v-model:visible="vModel" :footer="null" width="max(90vw, 600px)" @cancel="emits('update:modelValue', false)">
<a-typography-title class="ml-4 mb-2 select-none" type="secondary" :level="5">SETTINGS</a-typography-title> <a-typography-title class="ml-4 mb-2 select-none" type="secondary" :level="5">SETTINGS</a-typography-title>
<a-layout class="mt-3 modal-body">
<a-layout class="mt-3 modal-body flex">
<!-- Side tabs --> <!-- Side tabs -->
<a-layout-sider theme="light"> <a-layout-sider theme="light">
<a-menu v-model:selectedKeys="selectedTabKeys" class="h-full" mode="inline" :open-keys="[]"> <a-menu v-model:selected-keys="selectedTabKeys" class="h-full" mode="inline" :open-keys="[]">
<a-menu-item v-for="(tab, key) of tabsInfo" :key="key"> <a-menu-item v-for="(tab, key) of tabsInfo" :key="key">
<div class="flex flex-row items-center space-x-2"> <div class="flex flex-row items-center space-x-2">
<component :is="tab.icon" class="flex" /> <component :is="tab.icon" class="flex" />
<div class="flex select-none"> <div class="flex select-none">
{{ tab.title }} {{ tab.title }}
</div> </div>
@ -121,14 +134,14 @@ watch(
</a-layout-sider> </a-layout-sider>
<!-- Sub Tabs --> <!-- Sub Tabs -->
<a-layout-content class="h-full px-4 scrollbar-thumb-gray-500"> <a-layout-content class="h-auto px-4 scrollbar-thumb-gray-500">
<a-menu v-model:selectedKeys="selectedSubTabKeys" :open-keys="[]" mode="horizontal"> <a-menu v-model:selectedKeys="selectedSubTabKeys" :open-keys="[]" mode="horizontal">
<a-menu-item v-for="(tab, key) of selectedTab.subTabs" :key="key" class="select-none"> <a-menu-item v-for="(tab, key) of selectedTab.subTabs" :key="key" class="select-none">
{{ tab.title }} {{ tab.title }}
</a-menu-item> </a-menu-item>
</a-menu> </a-menu>
<component :is="selectedSubTab.body()" class="px-2 py-6" /> <component :is="selectedSubTab.body" class="px-2 py-6" />
</a-layout-content> </a-layout-content>
</a-layout> </a-layout>
</a-modal> </a-modal>
@ -136,6 +149,6 @@ watch(
<style scoped> <style scoped>
.modal-body { .modal-body {
@apply h-[70vh]; @apply min-h-[75vh];
} }
</style> </style>

2
packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue

@ -206,7 +206,7 @@ function onDeleted() {
<style lang="scss"> <style lang="scss">
.nc-views-menu { .nc-views-menu {
@apply flex-1 max-h-[20vh] overflow-y-scroll scrollbar-thin-primary; @apply flex-1 max-h-[30vh] overflow-y-scroll scrollbar-thin-primary;
.ghost, .ghost,
.ghost > * { .ghost > * {

17
packages/nc-gui-v2/components/smartsheet/sidebar/index.vue

@ -75,25 +75,28 @@ function onCreate(view: GridType | FormType | KanbanType | GalleryType) {
collapsiple collapsiple
collapsed-width="50" collapsed-width="50"
width="250" width="250"
:class="[sidebarCollapsed ? 'collapsed !bg-primary' : '!bg-white']" class="relative shadow-md h-full"
class="relative shadow h-full"
theme="light" theme="light"
> >
<a-tooltip placement="left">
<template #title> Toggle sidebar </template>
<div <div
class="group color-transition cursor-pointer hover:ring active:ring-pink-500 z-1 flex items-center absolute top-9 left-[-1rem] shadow bg-gray-100 rounded-full" class="group color-transition cursor-pointer hover:ring active:ring-pink-500 z-1 flex items-center p-[1px] absolute top-9 left-[-1rem] shadow bg-gray-100 rounded-full"
> >
<MaterialSymbolsChevronRightRounded <MaterialSymbolsChevronRightRounded
v-if="sidebarOpen" v-if="sidebarOpen"
class="toggle transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400" class="transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400"
@click="sidebarOpen = false" @click="sidebarOpen = false"
/> />
<MaterialSymbolsChevronLeftRounded <MaterialSymbolsChevronLeftRounded
v-else v-else
class="toggle transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400" class="transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400"
@click="sidebarOpen = true" @click="sidebarOpen = true"
/> />
</div> </div>
</a-tooltip>
<Toolbar v-if="sidebarOpen" class="flex items-center py-3 px-3 justify-between border-b-1" /> <Toolbar v-if="sidebarOpen" class="flex items-center py-3 px-3 justify-between border-b-1" />
@ -134,10 +137,6 @@ function onCreate(view: GridType | FormType | KanbanType | GalleryType) {
</template> </template>
<style scoped> <style scoped>
.collapsed :deep(.nc-icon:not(.toggle)) {
@apply !text-white;
}
:deep(.ant-menu-title-content) { :deep(.ant-menu-title-content) {
@apply w-full; @apply w-full;
} }

2
packages/nc-gui-v2/pages/index/index.vue

@ -16,7 +16,7 @@ const { $e } = useNuxtApp()
const { api, isLoading } = useApi() const { api, isLoading } = useApi()
useSidebar({ hasSidebar: false }) useSidebar({ hasSidebar: true, isOpen: true })
const toast = useToast() const toast = useToast()

40
packages/nc-gui-v2/pages/index/user/index.vue

@ -6,50 +6,10 @@ import MdiFolderOutline from '~icons/mdi/folder-outline'
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const route = useRoute() const route = useRoute()
const navDrawerOptions = [
{
title: 'My NocoDB',
route: '/',
icon: MdiFolderOutline,
},
{
title: 'Settings',
route: '/user',
icon: MdiAccountCog,
},
]
const selectedKey = computed(() => [navDrawerOptions.findIndex((opt) => opt.route === route.path)])
</script> </script>
<template> <template>
<NuxtLayout> <NuxtLayout>
<template #sidebar>
<div class="flex flex-col h-full">
<a-menu :selected-keys="selectedKey" class="pr-4 dark:bg-gray-800 dark:text-white flex-1 border-0">
<a-menu-item
v-for="(option, index) in navDrawerOptions"
:key="index"
class="!rounded-r-lg"
@click="navigateTo(option.route)"
>
<div class="flex items-center gap-4">
<component :is="option.icon" />
<span class="font-semibold">
{{ option.title }}
</span>
</div>
</a-menu-item>
</a-menu>
<general-social />
<general-sponsors :nav="true" />
</div>
</template>
<NuxtPage /> <NuxtPage />
</NuxtLayout> </NuxtLayout>
</template> </template>

16
packages/nc-gui-v2/pages/index/user/index/index.vue

@ -2,13 +2,11 @@
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { extractSdkResponseErrorMsg } from '~/utils' import { extractSdkResponseErrorMsg } from '~/utils'
import { reactive, ref, useNuxtApp, useSidebar } from '#imports' import { reactive, ref, useApi } from '#imports'
import MaterialSymbolsWarning from '~icons/material-symbols/warning' import MaterialSymbolsWarning from '~icons/material-symbols/warning'
import MdiKeyChange from '~icons/mdi/key-change' import MdiKeyChange from '~icons/mdi/key-change'
const { $api } = useNuxtApp() const { api, isLoading } = useApi()
const { isOpen } = useSidebar()
const { t } = useI18n() const { t } = useI18n()
@ -53,7 +51,7 @@ const passwordChange = async () => {
error = null error = null
try { try {
const { msg } = await $api.auth.passwordChange({ const { msg } = await api.auth.passwordChange({
currentPassword: form.currentPassword, currentPassword: form.currentPassword,
newPassword: form.password, newPassword: form.password,
}) })
@ -71,8 +69,9 @@ const resetError = () => {
</script> </script>
<template> <template>
<a-form ref="formValidator" layout="vertical" :model="form" class="change-password h-full w-full" @finish="passwordChange"> <div class="mt-4 w-1/2 mx-auto">
<div class="md:relative flex flex-col gap-2 w-full h-full p-8 lg:(max-w-1/2)" :class="{ 'mx-auto': isOpen }"> <a-form ref="formValidator" layout="vertical" :model="form" class="change-password" @finish="passwordChange">
<div class="md:relative flex flex-col gap-2 w-full h-full p-8 w-full">
<h1 class="prose-2xl font-bold mb-4">{{ $t('activity.changePwd') }}</h1> <h1 class="prose-2xl font-bold mb-4">{{ $t('activity.changePwd') }}</h1>
<Transition name="layout"> <Transition name="layout">
@ -118,10 +117,13 @@ const resetError = () => {
</div> </div>
</div> </div>
</a-form> </a-form>
</div>
</template> </template>
<style lang="scss"> <style lang="scss">
.change-password { .change-password {
@apply border-1 shadow-md rounded;
.ant-input-affix-wrapper, .ant-input-affix-wrapper,
.ant-input { .ant-input {
@apply dark:(!bg-gray-700 !text-white) !appearance-none my-1 border-1 border-solid border-primary/50 rounded; @apply dark:(!bg-gray-700 !text-white) !appearance-none my-1 border-1 border-solid border-primary/50 rounded;

129
packages/nc-gui-v2/pages/nc/[projectId]/index.vue

@ -1,9 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { provideSidebar, useProject, useRoute, useSidebar, useTabs } from '#imports' import { provideSidebar, ref, useProject, useRoute, useSidebar, useTabs, useToggle, useUIPermission } from '#imports'
import { TabType } from '~/composables' import { TabType } from '~/composables'
import MaterialSymbolsChevronRightRounded from '~icons/material-symbols/chevron-right-rounded'
import MaterialSymbolsChevronLeftRounded from '~icons/material-symbols/chevron-left-rounded'
import MdiChevronDown from '~icons/mdi/chevron-down'
const route = useRoute() const route = useRoute()
@ -11,17 +8,29 @@ const { project, loadProject, loadTables } = useProject(route.params.projectId a
const { addTab, clearTabs } = useTabs() const { addTab, clearTabs } = useTabs()
const { isUIAllowed } = useUIPermission()
// set old sidebar state // set old sidebar state
useSidebar({ isOpen: true }) useSidebar({ isOpen: true })
// create a new sidebar state // create a new sidebar state
const { isOpen, toggle } = provideSidebar({ isOpen: true }) 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() await loadTables()
@ -33,13 +42,17 @@ await loadTables()
:collapsed="!isOpen" :collapsed="!isOpen"
width="250" width="250"
collapsed-width="0" collapsed-width="0"
class="relative shadow h-full !bg-gray-100/50" class="relative shadow-md h-full"
:trigger="null" :trigger="null"
collapsible collapsible
theme="light" theme="light"
> >
<a-tooltip placement="right">
<template #title> Toggle table list </template>
<div <div
class="group color-transition cursor-pointer hover:ring active:ring-pink-500 z-1 flex items-center absolute top-9 right-[-0.75rem] shadow bg-gray-100 rounded-full" :class="[isOpen ? 'right-[-0.75rem]' : 'right-[-1.75rem]']"
class="group color-transition cursor-pointer hover:ring active:ring-pink-500 z-1 flex items-center absolute top-9 shadow-md bg-gray-100 rounded-full"
> >
<MaterialSymbolsChevronLeftRounded <MaterialSymbolsChevronLeftRounded
v-if="isOpen" v-if="isOpen"
@ -53,23 +66,121 @@ await loadTables()
@click="toggle(true)" @click="toggle(true)"
/> />
</div> </div>
</a-tooltip>
<DashboardTreeView /> <DashboardTreeView />
</a-layout-sider> </a-layout-sider>
<teleport v-if="project" to="#header-start"> <teleport v-if="project" to="#header-start">
<a-dropdown :trigger="['click']"> <a-dropdown :trigger="['click']">
<div class="group cursor-pointer w-full flex justify-between items-center"> <div class="group cursor-pointer w-full flex justify-between items-center">
<div class="text-xl">{{ project.title }}</div> <div class="flex-auto text-xl truncate">{{ project.title }}</div>
<MdiChevronDown class="group-hover:text-pink-500 text-2xl" /> <MdiChevronDown class="min-w-[28.5px] group-hover:text-pink-500 text-2xl" />
</div> </div>
<template #overlay> <template #overlay>
<div>Foo</div> <a-menu class="ml-2 !p-0 min-w-32 leading-8 !rounded">
<a-menu-item-group title="Project Settings">
<a-menu-item>
<div class="nc-project-menu-item group">
<MdiContentCopy class="group-hover:text-pink-500" />
Copy Project Info
</div>
</a-menu-item>
<a-menu-item>
<a
v-if="isUIAllowed('apiDocs')"
v-t="['e:api-docs']"
:href="`/api/v1/db/meta/projects/${route.params.projectId}/swagger`"
target="_blank"
class="nc-project-menu-item group"
>
<MdiApi class="group-hover:text-pink-500" />
Swagger: Rest APIs
</a>
</a-menu-item>
<a-menu-item>
<div
v-if="isUIAllowed('settings')"
v-t="['c:navdraw:project-settings']"
class="nc-project-menu-item group"
@click="toggleDialog(true, 'teamAndAuth')"
>
<MdiAccountGroupIcon class="group-hover:text-pink-500" />
Team & Auth
</div>
</a-menu-item>
<a-menu-item>
<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>
<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>
<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-item>
<a-sub-menu>
<div class="nc-project-menu-item group">
<MdiContentCopy class="group-hover:text-pink-500" />
Preview Project As
</div>
<a-menu-item> Foo </a-menu-item>
</a-sub-menu>
</a-menu-item>
</a-menu-item-group>
</a-menu>
</template> </template>
</a-dropdown> </a-dropdown>
</teleport> </teleport>
<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-3 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) {
@apply !py-0 active:(ring ring-pink-500);
}
</style>

4
packages/nc-gui-v2/pages/nc/[projectId]/index/index.vue

@ -36,7 +36,7 @@ function openQuickImportDialog(type: string) {
<template> <template>
<div class="nc-container pt-[9px]"> <div class="nc-container pt-[9px]">
<div class="h-full w-full flex flex-col"> <div class="h-full w-full flex flex-col">
<div class="px-1"> <div class="px-2">
<a-tabs v-model:activeKey="activeTabIndex" type="editable-card" @edit="closeTab"> <a-tabs v-model:activeKey="activeTabIndex" type="editable-card" @edit="closeTab">
<a-tab-pane v-for="(tab, i) in tabs" :key="i" :tab="tab.title" /> <a-tab-pane v-for="(tab, i) in tabs" :key="i" :tab="tab.title" />
@ -129,7 +129,7 @@ function openQuickImportDialog(type: string) {
</a-tabs> </a-tabs>
</div> </div>
<NuxtPage class="p-2" /> <NuxtPage class="px-4 py-2" />
</div> </div>
<DlgTableCreate v-if="tableCreateDialog" v-model="tableCreateDialog" /> <DlgTableCreate v-if="tableCreateDialog" v-model="tableCreateDialog" />

4
packages/nc-gui-v2/pages/signin.vue

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import type { RuleObject } from 'ant-design-vue/es/form' import type { RuleObject } from 'ant-design-vue/es/form'
import { definePageMeta } from '#imports' import { definePageMeta, useSidebar } from '#imports'
import { extractSdkResponseErrorMsg } from '~/utils/errorUtils' import { extractSdkResponseErrorMsg } from '~/utils/errorUtils'
import { navigateTo, useNuxtApp } from '#app' import { navigateTo, useNuxtApp } from '#app'
import { isEmail } from '~/utils/validation' import { isEmail } from '~/utils/validation'
@ -12,6 +12,8 @@ const { $api, $state } = $(useNuxtApp())
const { t } = useI18n() const { t } = useI18n()
useSidebar({ hasSidebar: false })
definePageMeta({ definePageMeta({
requiresAuth: false, requiresAuth: false,
title: 'title.headLogin', title: 'title.headLogin',

Loading…
Cancel
Save