Browse Source

chore(gui-v2): update styles

pull/3023/head
braks 2 years ago
parent
commit
65f0b1db1a
  1. 142
      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. 41
      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. 102
      packages/nc-gui-v2/pages/index/user/index/index.vue
  10. 157
      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

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

@ -1,24 +1,24 @@
<script lang="ts" setup>
import { breakpointsTailwind } from '@vueuse/core'
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) */
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 route = useRoute()
console.log(route.name)
const sidebar = ref<HTMLDivElement>()
const email = computed(() => user?.email ?? '---')
const globalSearch = ref('')
const email = computed(() => user.value?.email ?? '---')
const logout = () => {
signOut()
@ -32,63 +32,68 @@ const logout = () => {
:collapsed="!isOpen"
width="50"
collapsed-width="0"
class="!bg-primary h-full shadow-lg"
class="nc-sidebar-left !bg-primary h-full"
:trigger="null"
collapsible
theme="light"
>
<a-dropdown :trigger="['click']">
<div class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105 border-b-1 border-r-1">
<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="!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" />&nbsp;
<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">
<mdi-logout class="dark:text-white group-hover:(!text-red-500)" />&nbsp;
<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 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">
<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;
<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">
<mdi-logout class="dark:text-white group-hover:(!text-red-500)" />&nbsp;
<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 flex-col items-center w-full">
<div
:class="[route.name.includes('nc-projectId') ? 'bg-pink-500' : '']"
class="flex w-full justify-center items-center h-12 group p-2"
>
<MdiDatabase class="cursor-pointer transform hover:scale-105 text-2xl" />
</div>
<div id="sidebar" ref="sidebar" class="text-white flex-auto flex flex-col items-center w-full">
<a-tooltip placement="right">
<template #title> Switch language </template>
<template #title>My Projects</template>
<div class="flex w-full justify-center items-center h-12 group p-2">
<general-language class="cursor-pointer text-2xl" />
<div :class="[route.name === 'index' ? 'active' : '']" class="nc-sidebar-left-item" @click="navigateTo('/')">
<MdiFolder class="cursor-pointer transform hover:scale-105 text-2xl" />
</div>
</a-tooltip>
<a-tooltip placement="right">
<template #title>Project {{ route.params.projectId }}</template>
<div
: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>
</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>
</a-layout-sider>
<a-layout class="!flex-col">
<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 v-show="isLoading" class="flex items-center gap-2 ml-3">
@ -97,7 +102,9 @@ const logout = () => {
</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">
<a-tooltip placement="bottom">
<template #title> Go back </template>
@ -116,8 +123,29 @@ const logout = () => {
@click="router.go(+1)"
/>
</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 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>
<div class="w-full h-full">
@ -126,3 +154,31 @@ const logout = () => {
</a-layout>
</a-layout>
</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
.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 {

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

@ -2,10 +2,8 @@
import type { TableType } from 'nocodb-sdk'
import Sortable from 'sortablejs'
import { useToast } from 'vue-toastification'
import SettingsModal from './settings/SettingsModal.vue'
import { computed, useProject, useTable, useTabs, useUIPermission, watchEffect } from '#imports'
import { useProject, useTable, useTabs, watchEffect } from '#imports'
import { useNuxtApp, useRoute } from '#app'
import MdiSettingIcon from '~icons/mdi/cog'
import MdiTable from '~icons/mdi/table'
import MdiView from '~icons/mdi/eye-circle-outline'
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 MdiDrag from '~icons/mdi/drag-vertical'
import MdiMenuIcon from '~icons/mdi/dots-vertical'
import MdiAPIDocIcon from '~icons/mdi/open-in-new'
const { addTab } = useTabs()
const toast = useToast()
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useUIPermission()
const route = useRoute()
const { tables, loadTables } = useProject(route.params.projectId as string)
const { closeTab } = useTabs()
const { deleteTable } = useTable()
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 tableCreateDlg = ref(false)
const tableDeleteDlg = ref(false)
const menuRef = $ref<HTMLLIElement>()
let key = $ref(0)
let sortable: Sortable
// 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 filteredTables = $computed(() => {
return tables?.value?.filter((table) => !filterQuery || table?.title.toLowerCase()?.includes(filterQuery.toLowerCase()))
})
const contextMenuTarget = reactive<{ type?: 'table' | 'main'; value?: any }>({})
const setMenuContext = (type: 'table' | 'main', value?: any) => {
contextMenuTarget.type = type
contextMenuTarget.value = value
@ -125,16 +120,20 @@ const setMenuContext = (type: 'table' | 'main', value?: any) => {
}
const renameTableDlg = ref(false)
const renameTableMeta = ref()
const showRenameTableDlg = (table: TableType, rightClick = false) => {
$e(rightClick ? 'c:table:rename:navdraw:right-click' : 'c:table:rename:navdraw:options')
renameTableMeta.value = table
renameTableDlg.value = true
}
const reloadTables = async () => {
$e('a:table:refresh:navdraw')
await loadTables()
}
const addTableTab = (table: TableType) => {
$e('a:table:open')
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 :key="key" ref="menuRef" class="border-none sortable-list">
<div
v-for="table in tables"
v-for="table of tables"
:key="table.id"
v-t="['a:table:open']"
: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>
</template>
</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" />
<DlgTableRename v-if="renameTableMeta" v-model="renameTableDlg" :table-meta="renameTableMeta" />
</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 MultipleTableIcon from '~icons/mdi/table-multiple'
import NootbookOutline from '~icons/mdi/notebook-outline'
import { useVModel, watch } from '#imports'
interface Props {
show: boolean
modelValue: boolean
openKey?: string
}
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 = {
teamAndAuth: {
@ -41,11 +45,11 @@ const tabsInfo: TabGroup = {
subTabs: {
usersManagement: {
title: 'Users Management',
body: () => UserManagement,
body: UserManagement,
},
apiTokenManagement: {
title: 'API Token Management',
body: () => ApiTokenManagement,
body: ApiTokenManagement,
},
},
},
@ -55,7 +59,7 @@ const tabsInfo: TabGroup = {
subTabs: {
new: {
title: 'Apps',
body: () => AppStore,
body: AppStore,
},
},
},
@ -65,11 +69,11 @@ const tabsInfo: TabGroup = {
subTabs: {
metaData: {
title: 'Metadata',
body: () => Metadata,
body: Metadata,
},
acl: {
title: 'UI Access Control',
body: () => UIAcl,
body: UIAcl,
},
},
},
@ -79,7 +83,7 @@ const tabsInfo: TabGroup = {
subTabs: {
audit: {
title: 'Audit',
body: () => AuditTab,
body: AuditTab,
},
},
},
@ -88,7 +92,7 @@ const tabsInfo: TabGroup = {
const firstKeyOfObject = (obj: object) => Object.keys(obj)[0]
// 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]])
let selectedSubTabKeys = $ref<string[]>([firstKeyOfObject(selectedTab.subTabs)])
@ -100,18 +104,27 @@ watch(
selectedSubTabKeys = [firstKeyOfObject(tabsInfo[newTabKey].subTabs)]
},
)
watch(
() => props.openKey,
(nextOpenKey) => {
selectedTabKeys = [Object.keys(tabsInfo).find((key) => key === nextOpenKey) || firstKeyOfObject(tabsInfo)]
},
)
</script>
<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-layout class="mt-3 modal-body">
<a-layout class="mt-3 modal-body flex">
<!-- Side tabs -->
<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">
<div class="flex flex-row items-center space-x-2">
<component :is="tab.icon" class="flex" />
<div class="flex select-none">
{{ tab.title }}
</div>
@ -121,14 +134,14 @@ watch(
</a-layout-sider>
<!-- 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-item v-for="(tab, key) of selectedTab.subTabs" :key="key" class="select-none">
{{ tab.title }}
</a-menu-item>
</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>
</a-modal>
@ -136,6 +149,6 @@ watch(
<style scoped>
.modal-body {
@apply h-[70vh];
@apply min-h-[75vh];
}
</style>

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

@ -206,7 +206,7 @@ function onDeleted() {
<style lang="scss">
.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 > * {

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

@ -75,25 +75,28 @@ function onCreate(view: GridType | FormType | KanbanType | GalleryType) {
collapsiple
collapsed-width="50"
width="250"
:class="[sidebarCollapsed ? 'collapsed !bg-primary' : '!bg-white']"
class="relative shadow h-full"
class="relative shadow-md h-full"
theme="light"
>
<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"
>
<MaterialSymbolsChevronRightRounded
v-if="sidebarOpen"
class="toggle transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400"
@click="sidebarOpen = false"
/>
<MaterialSymbolsChevronLeftRounded
v-else
class="toggle transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400"
@click="sidebarOpen = true"
/>
</div>
<a-tooltip placement="left">
<template #title> Toggle sidebar </template>
<div
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
v-if="sidebarOpen"
class="transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400"
@click="sidebarOpen = false"
/>
<MaterialSymbolsChevronLeftRounded
v-else
class="transform group-hover:(scale-115 text-pink-500) text-xl text-gray-400"
@click="sidebarOpen = true"
/>
</div>
</a-tooltip>
<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>
<style scoped>
.collapsed :deep(.nc-icon:not(.toggle)) {
@apply !text-white;
}
:deep(.ant-menu-title-content) {
@apply w-full;
}

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

@ -16,7 +16,7 @@ const { $e } = useNuxtApp()
const { api, isLoading } = useApi()
useSidebar({ hasSidebar: false })
useSidebar({ hasSidebar: true, isOpen: true })
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 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>
<template>
<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 />
</NuxtLayout>
</template>

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

@ -2,13 +2,11 @@
import { useI18n } from 'vue-i18n'
import { message } from 'ant-design-vue'
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 MdiKeyChange from '~icons/mdi/key-change'
const { $api } = useNuxtApp()
const { isOpen } = useSidebar()
const { api, isLoading } = useApi()
const { t } = useI18n()
@ -53,7 +51,7 @@ const passwordChange = async () => {
error = null
try {
const { msg } = await $api.auth.passwordChange({
const { msg } = await api.auth.passwordChange({
currentPassword: form.currentPassword,
newPassword: form.password,
})
@ -71,57 +69,61 @@ const resetError = () => {
</script>
<template>
<a-form ref="formValidator" layout="vertical" :model="form" class="change-password h-full w-full" @finish="passwordChange">
<div class="md:relative flex flex-col gap-2 w-full h-full p-8 lg:(max-w-1/2)" :class="{ 'mx-auto': isOpen }">
<h1 class="prose-2xl font-bold mb-4">{{ $t('activity.changePwd') }}</h1>
<Transition name="layout">
<div v-if="error" class="self-center mb-4 bg-red-500 text-white rounded-lg w-3/4 p-1">
<div class="flex items-center gap-2 justify-center"><MaterialSymbolsWarning /> {{ error }}</div>
<div class="mt-4 w-1/2 mx-auto">
<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>
<Transition name="layout">
<div v-if="error" class="self-center mb-4 bg-red-500 text-white rounded-lg w-3/4 p-1">
<div class="flex items-center gap-2 justify-center"><MaterialSymbolsWarning /> {{ error }}</div>
</div>
</Transition>
<a-form-item :label="$t('placeholder.password.current')" name="currentPassword" :rules="formRules.currentPassword">
<a-input-password
v-model:value="form.currentPassword"
size="large"
class="password"
:placeholder="$t('placeholder.password.current')"
@focus="resetError"
/>
</a-form-item>
<a-form-item :label="$t('placeholder.password.new')" name="password" :rules="formRules.password">
<a-input-password
v-model:value="form.password"
size="large"
class="password"
:placeholder="$t('placeholder.password.new')"
@focus="resetError"
/>
</a-form-item>
<a-form-item :label="$t('placeholder.password.confirm')" name="passwordRepeat" :rules="formRules.passwordRepeat">
<a-input-password
v-model:value="form.passwordRepeat"
size="large"
class="password"
:placeholder="$t('placeholder.password.confirm')"
@focus="resetError"
/>
</a-form-item>
<div class="flex flex-wrap gap-4 items-center mt-4 md:justify-between w-full">
<button class="submit" type="submit">
<span class="flex items-center gap-2"><MdiKeyChange /> {{ $t('activity.changePwd') }}</span>
</button>
</div>
</Transition>
<a-form-item :label="$t('placeholder.password.current')" name="currentPassword" :rules="formRules.currentPassword">
<a-input-password
v-model:value="form.currentPassword"
size="large"
class="password"
:placeholder="$t('placeholder.password.current')"
@focus="resetError"
/>
</a-form-item>
<a-form-item :label="$t('placeholder.password.new')" name="password" :rules="formRules.password">
<a-input-password
v-model:value="form.password"
size="large"
class="password"
:placeholder="$t('placeholder.password.new')"
@focus="resetError"
/>
</a-form-item>
<a-form-item :label="$t('placeholder.password.confirm')" name="passwordRepeat" :rules="formRules.passwordRepeat">
<a-input-password
v-model:value="form.passwordRepeat"
size="large"
class="password"
:placeholder="$t('placeholder.password.confirm')"
@focus="resetError"
/>
</a-form-item>
<div class="flex flex-wrap gap-4 items-center mt-4 md:justify-between w-full">
<button class="submit" type="submit">
<span class="flex items-center gap-2"><MdiKeyChange /> {{ $t('activity.changePwd') }}</span>
</button>
</div>
</div>
</a-form>
</a-form>
</div>
</template>
<style lang="scss">
.change-password {
@apply border-1 shadow-md rounded;
.ant-input-affix-wrapper,
.ant-input {
@apply dark:(!bg-gray-700 !text-white) !appearance-none my-1 border-1 border-solid border-primary/50 rounded;

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

@ -1,9 +1,6 @@
<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 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()
@ -11,17 +8,29 @@ const { project, loadProject, loadTables } = useProject(route.params.projectId a
const { addTab, clearTabs } = useTabs()
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()
if (!route.params.type) {
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 loadTables()
@ -33,43 +42,145 @@ await loadTables()
:collapsed="!isOpen"
width="250"
collapsed-width="0"
class="relative shadow h-full !bg-gray-100/50"
class="relative shadow-md h-full"
:trigger="null"
collapsible
theme="light"
>
<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"
>
<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 placement="right">
<template #title> Toggle table list </template>
<div
: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
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 />
</a-layout-sider>
<teleport v-if="project" to="#header-start">
<a-dropdown :trigger="['click']">
<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>
<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>
</a-dropdown>
</teleport>
<dashboard-settings-modal v-model="dialogOpen" :open-key="openDialogKey" />
<NuxtPage />
</NuxtLayout>
</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>
<div class="nc-container pt-[9px]">
<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-tab-pane v-for="(tab, i) in tabs" :key="i" :tab="tab.title" />
@ -129,7 +129,7 @@ function openQuickImportDialog(type: string) {
</a-tabs>
</div>
<NuxtPage class="p-2" />
<NuxtPage class="px-4 py-2" />
</div>
<DlgTableCreate v-if="tableCreateDialog" v-model="tableCreateDialog" />

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

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

Loading…
Cancel
Save