Browse Source

refactor(gui-v2): new layout

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/3236/head
Pranav C 2 years ago
parent
commit
7f849189d4
  1. 7
      packages/nc-gui-v2/assets/style-v2.scss
  2. 3
      packages/nc-gui-v2/components.d.ts
  3. 128
      packages/nc-gui-v2/components/dashboard/TreeView.vue
  4. 4
      packages/nc-gui-v2/components/general/ShareBaseButton.vue
  5. 2
      packages/nc-gui-v2/components/smartsheet/Toolbar.vue
  6. 6
      packages/nc-gui-v2/components/smartsheet/sidebar/index.vue
  7. 18
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/ToggleDrawer.vue
  8. 7
      packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/index.vue
  9. 6
      packages/nc-gui-v2/components/tabs/Smartsheet.vue
  10. 2
      packages/nc-gui-v2/layouts/base.vue
  11. 41
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue
  12. 183
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index.vue

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

@ -3,6 +3,7 @@
:root {
--header-height: 50px;
--toolbar-height: 48px;
}
.ant-layout-header {
@ -51,7 +52,7 @@ h1, h2, h3, h4, h5, h6, p, label, button, textarea, select {
.nc-menu-item {
@apply cursor-pointer text-xs flex items-center gap-2 px-4 py-3 relative 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));
&.disabled {
&.disabled {
@apply text-black text-opacity-25 bg-[#f5f5f5] cursor-not-allowed text-shadow-none box-shadow-none border-[#d9d9d9];
}
}
@ -85,7 +86,7 @@ h1, h2, h3, h4, h5, h6, p, label, button, textarea, select {
svg {
@apply z-1 text-xl p-1 text-gray-500;
}
.disabled {
@apply cursor-not-allowed border-none;
}
@ -209,4 +210,4 @@ h1, h2, h3, h4, h5, h6, p, label, button, textarea, select {
&:active::after {
@apply ring ring-pink-500;
}
}
}

3
packages/nc-gui-v2/components.d.ts vendored

@ -97,6 +97,7 @@ declare module '@vue/runtime-core' {
MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default']
MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default']
MdiAt: typeof import('~icons/mdi/at')['default']
MdiBackburger: typeof import('~icons/mdi/backburger')['default']
MdiBugOutline: typeof import('~icons/mdi/bug-outline')['default']
MdiCalculator: typeof import('~icons/mdi/calculator')['default']
MdiCalendarMonth: typeof import('~icons/mdi/calendar-month')['default']
@ -104,6 +105,7 @@ declare module '@vue/runtime-core' {
MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default']
MdiChat: typeof import('~icons/mdi/chat')['default']
MdiCheck: typeof import('~icons/mdi/check')['default']
MdiChevronDoubleLeft: typeof import('~icons/mdi/chevron-double-left')['default']
MdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
MdiClose: typeof import('~icons/mdi/close')['default']
MdiCloseBox: typeof import('~icons/mdi/close-box')['default']
@ -158,6 +160,7 @@ declare module '@vue/runtime-core' {
MdiLogin: typeof import('~icons/mdi/login')['default']
MdiLogout: typeof import('~icons/mdi/logout')['default']
MdiMagnify: typeof import('~icons/mdi/magnify')['default']
MdiMenu: typeof import('~icons/mdi/menu')['default']
MdiMenuDown: typeof import('~icons/mdi/menu-down')['default']
MdiMicrosoftTeams: typeof import('~icons/mdi/microsoft-teams')['default']
MdiMinusCircleOutline: typeof import('~icons/mdi/minus-circle-outline')['default']

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

@ -4,12 +4,14 @@ import Sortable from 'sortablejs'
import { Empty } from 'ant-design-vue'
import { useNuxtApp } from '#app'
import { computed, useProject, useTable, useTabs, useUIPermission, watchEffect } from '#imports'
import DlgAirtableImport from '~/components/dlg/AirtableImport.vue'
import DlgQuickImport from '~/components/dlg/QuickImport.vue'
import DlgTableCreate from '~/components/dlg/TableCreate.vue'
import { TabType } from '~/composables'
import MdiView from '~icons/mdi/eye-circle-outline'
import MdiTableLarge from '~icons/mdi/table-large'
import MdiMenuIcon from '~icons/mdi/dots-vertical'
import MdiDrag from '~icons/mdi/drag-vertical'
import GithubStarButton from '~/components/dashboard/GithubStarButton.vue'
const { addTab } = useTabs()
@ -135,21 +137,45 @@ const addTableTab = (table: TableType) => {
const activeTable = computed(() => {
return [TabType.TABLE, TabType.VIEW].includes(activeTab.value?.type) ? activeTab.value.title : null
})
function openQuickImportDialog(type: string) {
$e(`a:actions:import-${type}`)
const isOpen = ref(true)
const { close } = useDialog(DlgQuickImport, {
'modelValue': isOpen,
'importType': type,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
function openAirtableImportDialog() {
$e('a:actions:import-airtable')
const isOpen = ref(true)
const { close } = useDialog(DlgAirtableImport, {
'modelValue': isOpen,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
</script>
<template>
<div class="nc-treeview-container flex flex-col">
<div class="px-6 py-[8.5px] border-b-1 nc-filter-input">
<div class="flex items-center bg-gray-50 rounded relative">
<a-input
v-model:value="filterQuery"
class="nc-filter-input !bg-transparent"
:placeholder="$t('placeholder.searchProjectTree')"
/>
<MdiSearch class="nc-filter-input-icon text-gray-400 mx-3 absolute right-[-4px] top-[7px]" />
</div>
</div>
<a-dropdown :trigger="['contextmenu']">
<div
class="pt-2 pl-2 pb-2 flex-1 overflow-y-auto flex flex-column scrollbar-thin-dull"
@ -168,6 +194,77 @@ const activeTable = computed(() => {
</span>
</div>
<div style="direction: ltr" class="flex-1">
<div
class="group flex items-center gap-2 pl-5 pr-3 py-2 text-primary/70 hover:(text-primary/100) cursor-pointer select-none"
@click="tableCreateDlg = true"
>
<MdiPlus />
<span class="text-gray-500 group-hover:(text-primary/100) flex-1">{{ $t('tooltip.addTable') }}</span>
<a-dropdown :trigger="['click']" @click.stop>
<MdiDotsVertical class="transition-opacity opacity-0 group-hover:opacity-100" />
<template #overlay>
<a-menu class="nc-add-project-menu !py-0 ml-6 rounded text-sm">
<a-menu-item-group title="QUICK IMPORT FROM" class="!px-0 !mx-0">
<a-menu-item
v-if="isUIAllowed('airtableImport')"
key="quick-import-airtable"
@click="openAirtableImportDialog"
>
<div class="color-transition nc-project-menu-item group">
<MdiTableLarge class="group-hover:text-pink-500" />
<!-- TODO: i18n -->
Airtable
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('csvImport')" key="quick-import-csv" @click="openQuickImportDialog('csv')">
<div class="color-transition nc-project-menu-item group">
<MdiFileDocumentOutline class="group-hover:text-pink-500" />
<!-- TODO: i18n -->
CSV file
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('jsonImport')" key="quick-import-json" @click="openQuickImportDialog('json')">
<div class="color-transition nc-project-menu-item group">
<MdiCodeJson class="group-hover:text-pink-500" />
<!-- TODO: i18n -->
JSON file
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('excelImport')"
key="quick-import-excel"
@click="openQuickImportDialog('excel')"
>
<div class="color-transition nc-project-menu-item group">
<MdiFileExcel class="group-hover:text-pink-500" />
<!-- TODO: i18n -->
Microsoft Excel
</div>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider class="my-0" />
<a-menu-item v-if="isUIAllowed('importRequest')" key="add-new-table" class="py-1 rounded-b">
<a
v-t="['e:datasource:import-request']"
href="https://github.com/nocodb/nocodb/issues/2052"
target="_blank"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)"
>
<MdiOpenInNew class="group-hover:text-pink-500" />
<!-- TODO: i18n -->
Request a data source you need?
</a>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
<div v-if="tables.length" class="transition-height duration-200 overflow-hidden">
<div :key="key" ref="menuRef" class="border-none sortable-list">
<div
@ -276,7 +373,12 @@ const activeTable = computed(() => {
<a-divider class="mt-0 mb-2" />
<div class="items-center flex justify-center mb-1">
<GithubStarButton />
<!--
Todo : move the component
<GithubStarButton />
-->
<GeneralShareBaseButton class="!mr-0" />
</div>
<DlgTableCreate v-if="tableCreateDlg" v-model="tableCreateDlg" />

4
packages/nc-gui-v2/components/general/ShareBaseButton.vue

@ -9,7 +9,7 @@ const { isUIAllowed } = useUIPermission()
</script>
<template>
<div class="flex items-center mr-4">
<div class="flex items-center">
<a-button
v-if="
isUIAllowed('newUser') &&
@ -20,7 +20,7 @@ const { isUIAllowed } = useUIPermission()
"
size="middle"
type="primary"
class="!bg-white !text-primary rounded"
class="rounded"
@click="showUserModal = true"
>
<div class="flex items-center space-x-1">

2
packages/nc-gui-v2/components/smartsheet/Toolbar.vue

@ -7,7 +7,7 @@ const isPublic = inject(IsPublicInj, ref(false))
</script>
<template>
<div class="nc-table-toolbar w-full py-1 flex gap-1 items-center h-[48px] px-2 border-b" style="z-index: 7">
<div class="nc-table-toolbar w-full py-1 flex gap-1 items-center h-[var(--toolbar-height)] px-2 border-b" style="z-index: 7">
<SmartsheetToolbarFieldsMenu v-if="isGrid || isGallery" :show-system-fields="false" class="ml-1" />
<SmartsheetToolbarColumnFilterMenu v-if="isGrid || isGallery" />

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

@ -125,7 +125,11 @@ function onCreate(view: GridType | FormType | KanbanType | GalleryType) {
</Transition>
</a-tooltip>
<Toolbar v-if="isOpen" :class="{ 'flex items-center py-3 px-3 justify-between border-b-1': !isForm }" />
<Toolbar
v-if="isOpen"
class="min-h-[var(--toolbar-height)] max-h-[var(--toolbar-height)]"
:class="{ 'flex items-center py-3 px-3 justify-between border-b-1': !isForm }"
/>
<Toolbar v-else class="py-3 px-2 max-w-[50px] flex !flex-col-reverse gap-4 items-center mt-[-1px]">
<template #start>

18
packages/nc-gui-v2/components/smartsheet/sidebar/toolbar/ToggleDrawer.vue

@ -0,0 +1,18 @@
<script setup lang="ts">
/** Sidebar visible */
const { isOpen, toggle } = useSidebar({ storageKey: 'nc-right-sidebar' })
</script>
<template>
<a-tooltip :placement="isOpen ? 'bottomRight' : 'left'">
<template #title> Toggle sidebar </template>
<div class="nc-sidebar-right-item hover:after:bg-primary/75 group nc-sidebar-add-row">
<MdiChevronDoubleLeft
class="cursor-pointer group-hover:(!text-white) transform transition-transform"
:class="{ 'rotate-180': isOpen }"
@click="toggle(!isOpen)"
/>
</div>
</a-tooltip>
</template>

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

@ -5,6 +5,7 @@ import Reload from './Reload.vue'
import ExportCache from './ExportCache.vue'
import DeleteCache from './DeleteCache.vue'
import DebugMeta from './DebugMeta.vue'
import ToggleDrawer from './ToggleDrawer.vue'
import { IsFormInj } from '#imports'
const { isUIAllowed } = useUIPermission()
@ -14,6 +15,8 @@ const isForm = inject(IsFormInj)
const debug = $ref(false)
const clickCount = $ref(0)
const { isOpen } = useSidebar({ storageKey: 'nc-right-sidebar' })
</script>
<template>
@ -53,6 +56,10 @@ const clickCount = $ref(0)
<AddRow v-if="isUIAllowed('xcDatatableEditable')" @click.stop />
<div :class="{ 'w-[calc(100%_+_16px)] h-[1px] bg-gray-200 mt-1 -ml-1': !isOpen, 'dot': isOpen }" />
<ToggleDrawer />
<slot name="end" />
</div>
<div v-else>

6
packages/nc-gui-v2/components/tabs/Smartsheet.vue

@ -84,11 +84,9 @@ watch(isLocked, (nextValue) => (treeViewIsLockedInj.value = nextValue), { immedi
<SmartsheetForm v-else-if="isForm" />
</div>
</div>
<teleport to="#content">
<SmartsheetSidebar />
</teleport>
</template>
</div>
<SmartsheetSidebar />
</div>
</template>

2
packages/nc-gui-v2/layouts/base.vue

@ -23,7 +23,7 @@ const logout = () => {
<a-layout class="!flex-col">
<Transition name="layout">
<a-layout-header
v-if="!route.meta.public && signedIn"
v-if="!route.meta.public && signedIn && !route.meta.hideHeader"
class="flex !bg-primary items-center text-white pl-4 pr-5 shadow-lg"
>
<div

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

@ -97,6 +97,10 @@ const copyAuthToken = async () => {
message.error(e.message)
}
}
definePageMeta({
hideHeader: true,
})
</script>
<template>
@ -107,7 +111,7 @@ const copyAuthToken = async () => {
:collapsed="!isOpen"
width="250"
collapsed-width="50"
class="relative shadow-md h-full z-1"
class="relative shadow-md h-full z-1 nc-left-sidebar"
:trigger="null"
collapsible
theme="light"
@ -144,14 +148,20 @@ const copyAuthToken = async () => {
</template>
</div>
<a-dropdown v-else class="h-full" :trigger="['click']" placement="bottom">
<a-dropdown v-else class="h-full min-w-0 flex-1" :trigger="['click']" placement="bottom">
<div
:style="{ width: isOpen ? 'calc(100% - 40px) pr-2' : '100%' }"
:class="[isOpen ? '' : 'justify-center']"
class="group cursor-pointer flex gap-4 items-center nc-project-menu overflow-hidden"
class="group cursor-pointer flex gap-1 items-center nc-project-menu overflow-hidden"
>
<template v-if="isOpen">
<div class="text-xl font-semibold truncate">{{ project.title }}</div>
<a-tooltip v-if="project.title?.length > 12" placement="bottom">
<div class="text-lg font-semibold truncate">{{ project.title }}</div>
<template #title>
<div class="text-sm">{{ project.title }}</div>
</template>
</a-tooltip>
<div v-else class="text-lg font-semibold truncate">{{ project.title }}</div>
<MdiChevronDown class="min-w-[28.5px] group-hover:text-pink-500 text-2xl" />
</template>
@ -246,6 +256,13 @@ const copyAuthToken = async () => {
</a-menu>
</template>
</a-dropdown>
<div class="nc-sidebar-left-toggle-icon hover:after:bg-primary/75 group nc-sidebar-add-row flex align-center px-2">
<MdiBackburger
class="cursor-pointer transform transition-transform duration-500"
:class="{ 'rotate-180': !isOpen }"
@click="toggle(!isOpen)"
/>
</div>
</div>
<a-tooltip :mouse-enter-delay="1" placement="right">
@ -295,4 +312,20 @@ const copyAuthToken = async () => {
:deep(.ant-dropdown-menu-item) {
@apply !py-0 active:(ring ring-pink-500);
}
:global(#nc-sidebar-left .ant-layout-sider-collapsed) {
@apply !w-0 !max-w-0 !min-w-0 overflow-x-hidden;
}
.nc-left-sidebar {
.nc-sidebar-left-toggle-icon {
@apply opacity-0 transition-opactity duration-200 transition-color text-white/80 hover:text-white/100;
.nc-left-sidebar {
@apply !border-r-0;
}
}
&:hover .nc-sidebar-left-toggle-icon {
@apply opacity-100;
}
}
</style>

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

@ -1,19 +1,14 @@
<script setup lang="ts">
import type { TabItem } from '~/composables'
import { TabMetaInj, provide, ref, useDialog, useNuxtApp, useTabs, useUIPermission } from '#imports'
import DlgTableCreate from '~/components/dlg/TableCreate.vue'
import DlgAirtableImport from '~/components/dlg/AirtableImport.vue'
import DlgQuickImport from '~/components/dlg/QuickImport.vue'
import { TabType } from '~/composables'
import { TabMetaInj, provide, useSidebar, useTabs } from '#imports'
import { TabType, useGlobal } from '~/composables'
import MdiAirTableIcon from '~icons/mdi/table-large'
import MdiView from '~icons/mdi/eye-circle-outline'
import MdiAccountGroup from '~icons/mdi/account-group'
const { $e } = useNuxtApp()
const { tabs, activeTabIndex, activeTab, closeTab } = useTabs()
const { isUIAllowed } = useUIPermission()
const { isLoading } = useGlobal()
provide(TabMetaInj, activeTab)
@ -28,63 +23,21 @@ const icon = (tab: TabItem) => {
}
}
function openQuickImportDialog(type: string) {
$e(`a:actions:import-${type}`)
const isOpen = ref(true)
const { close } = useDialog(DlgQuickImport, {
'modelValue': isOpen,
'importType': type,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
function openTableCreateDialog() {
$e('a:actions:create-table')
const isOpen = ref(true)
const { close } = useDialog(DlgTableCreate, {
'modelValue': isOpen,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
function openAirtableImportDialog() {
$e('a:actions:import-airtable')
const isOpen = ref(true)
const { close } = useDialog(DlgAirtableImport, {
'modelValue': isOpen,
'onUpdate:modelValue': closeDialog,
})
function closeDialog() {
isOpen.value = false
close(1000)
}
}
const { isOpen, toggle } = useSidebar()
</script>
<template>
<div class="h-full w-full nc-container pt-[9px]">
<div class="h-full w-full nc-container">
<div class="h-full w-full flex flex-col">
<div>
<div class="flex items-end !min-h-[50px] bg-primary/100">
<div v-if="!isOpen" class="nc-sidebar-left-toggle-icon hover:after:bg-primary/75 group nc-sidebar-add-row py-2 px-3">
<MdiMenu
class="cursor-pointer transform transition-transform duration-500 text-white"
:class="{ 'rotate-180': !isOpen }"
@click="toggle(!isOpen)"
/>
</div>
<a-tabs v-model:activeKey="activeTabIndex" class="nc-root-tabs" type="editable-card" @edit="closeTab(activeTabIndex)">
<a-tab-pane v-for="(tab, i) in tabs" :key="i">
<template #tab>
@ -95,87 +48,15 @@ function openAirtableImportDialog() {
</div>
</template>
</a-tab-pane>
<template #leftExtra>
<a-dropdown v-if="isUIAllowed('addOrImport')" :trigger="['click']">
<div
class="cursor-pointer color-transition group hover:text-primary text-sm flex items-center gap-2 py-[9.5px] px-[20px]"
>
<MdiPlusBoxOutline class="group-hover:text-pink-500" />
Add / Import
</div>
<template #overlay>
<a-menu class="nc-add-project-menu !py-0 ml-6 rounded text-sm">
<a-menu-item v-if="isUIAllowed('addTable')" key="add-new-table" @click="openTableCreateDialog">
<div class="color-transition nc-project-menu-item after:(!rounded-t) group">
<MdiTable class="group-hover:text-pink-500" />
<!-- Add new table -->
{{ $t('tooltip.addTable') }}
</div>
</a-menu-item>
<a-menu-item-group title="QUICK IMPORT FROM" class="!px-0 !mx-0">
<a-menu-item
v-if="isUIAllowed('airtableImport')"
key="quick-import-airtable"
@click="openAirtableImportDialog"
>
<div class="color-transition nc-project-menu-item group">
<MdiTableLarge class="group-hover:text-pink-500" />
<!-- TODO: i18n -->
Airtable
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('csvImport')" key="quick-import-csv" @click="openQuickImportDialog('csv')">
<div class="color-transition nc-project-menu-item group">
<MdiFileDocumentOutline class="group-hover:text-pink-500" />
<!-- TODO: i18n -->
CSV file
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('jsonImport')" key="quick-import-json" @click="openQuickImportDialog('json')">
<div class="color-transition nc-project-menu-item group">
<MdiCodeJson class="group-hover:text-pink-500" />
<!-- TODO: i18n -->
JSON file
</div>
</a-menu-item>
<a-menu-item
v-if="isUIAllowed('excelImport')"
key="quick-import-excel"
@click="openQuickImportDialog('excel')"
>
<div class="color-transition nc-project-menu-item group">
<MdiFileExcel class="group-hover:text-pink-500" />
<!-- TODO: i18n -->
Microsoft Excel
</div>
</a-menu-item>
</a-menu-item-group>
<a-menu-divider class="my-0" />
<a-menu-item v-if="isUIAllowed('importRequest')" key="add-new-table" class="py-1 rounded-b">
<a
v-t="['e:datasource:import-request']"
href="https://github.com/nocodb/nocodb/issues/2052"
target="_blank"
class="prose-sm hover:(!text-primary !opacity-100) color-transition nc-project-menu-item group after:(!rounded-b)"
>
<MdiOpenInNew class="group-hover:text-pink-500" />
<!-- TODO: i18n -->
Request a data source you need?
</a>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
</a-tabs>
<span class="flex-1" />
<div class="flex justify-center align-self-center mr-2">
<div v-show="isLoading" class="flex items-center gap-2 ml-3 text-white">
{{ $t('general.loading') }}
<MdiReload :class="{ 'animate-infinite animate-spin': isLoading }" />
</div>
</div>
</div>
<div class="w-full min-h-[300px] flex-auto">
@ -187,24 +68,36 @@ function openAirtableImportDialog() {
<style scoped lang="scss">
.nc-container {
height: calc(100vh - var(--header-height));
height: 100vh;
flex: 1 1 100%;
}
:deep(.nc-root-tabs) {
& > .ant-tabs-nav {
@apply !mb-0;
@apply !mb-0 before:(!border-b-0);
.ant-tabs-extra-content {
@apply !bg-white/0;
}
& > .ant-tabs-nav-wrap > .ant-tabs-nav-list {
& > .ant-tabs-nav-add {
.ant-tabs-nav-add {
@apply !hidden;
}
& > .ant-tabs-tab-active {
@apply font-weight-medium;
}
& > .ant-tabs-tab {
@apply border-0;
}
& > .ant-tabs-tab:not(.ant-tabs-tab-active) {
@apply bg-gray-100 text-gray-500;
//@apply bg-gray-100 text-gray-500;
@apply bg-white/10 text-white/90;
.ant-tabs-tab-remove {
@apply !text-white;
}
}
}
}

Loading…
Cancel
Save