Browse Source

Merge pull request #3171 from nocodb/refactor/project-list

chore(gui-v2): style and import cleanups
pull/3213/head
navi 2 years ago committed by GitHub
parent
commit
26c9f9bb2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 41
      packages/nc-gui-v2/assets/style-v2.scss
  2. 3
      packages/nc-gui-v2/components.d.ts
  3. 5
      packages/nc-gui-v2/components/general/PreviewAs.vue
  4. 85
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue
  5. 15
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue
  6. 16
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/index.vue
  7. 15
      packages/nc-gui-v2/pages/[projectType]/view/[viewId].vue
  8. 2
      packages/nc-gui-v2/pages/forgot-password.vue
  9. 186
      packages/nc-gui-v2/pages/index/index.vue
  10. 2
      packages/nc-gui-v2/pages/signin.vue
  11. 4
      packages/nc-gui-v2/pages/signup/[[token]].vue

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

@ -17,6 +17,10 @@ main {
@apply m-0 h-full w-full bg-white dark:(bg-black text-white);
}
html {
overflow-y: auto !important;
}
main {
@apply flex-0 w-full relative scrollbar-thin-dull;
overflow-x: hidden;
@ -43,16 +47,21 @@ h1, h2, h3, h4, h5, h6, p, label, button, textarea, select {
@apply color-transition;
}
html {
overflow-y: auto !important;
}
// menu item styling
.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));
}
.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));
&:hover {
.nc-icon {
@apply text-pink-500;
}
}
}
.nc-sidebar-right-item {
@apply relative flex items-center;
@ -136,9 +145,9 @@ html {
@apply transition-opacity duration-300 ease-in-out;
}
.page-enter,
.page-enter-active,
.page-leave-active,
.layout-enter,
.layout-enter-active,
.layout-leave-active {
@apply opacity-0;
}
@ -166,4 +175,22 @@ html {
.glow-enter,
.glow-leave-active {
@apply opacity-0;
}
.scaling-btn {
@apply z-1 relative color-transition border border-gray-300 rounded-md p-3 bg-gray-100/50 text-white bg-primary;
&::after {
@apply rounded-md absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary;
content: '';
z-index: -1;
}
&:hover::after {
@apply transform scale-110 ring ring-pink-500;
}
&:active::after {
@apply ring ring-pink-500;
}
}

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

@ -103,6 +103,7 @@ declare module '@vue/runtime-core' {
MdiCheck: typeof import('~icons/mdi/check')['default']
MdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
MdiClose: typeof import('~icons/mdi/close')['default']
MdiCloseBox: typeof import('~icons/mdi/close-box')['default']
MdiCloseCircle: typeof import('~icons/mdi/close-circle')['default']
MdiCloseThick: typeof import('~icons/mdi/close-thick')['default']
MdiCodeJson: typeof import('~icons/mdi/code-json')['default']
@ -134,6 +135,7 @@ declare module '@vue/runtime-core' {
MdiFunction: typeof import('~icons/mdi/function')['default']
MdiGestureDoubleTap: typeof import('~icons/mdi/gesture-double-tap')['default']
MdiGithub: typeof import('~icons/mdi/github')['default']
MdiGridLarge: typeof import('~icons/mdi/grid-large')['default']
MdiHeart: typeof import('~icons/mdi/heart')['default']
MdiHook: typeof import('~icons/mdi/hook')['default']
MdiInformation: typeof import('~icons/mdi/information')['default']
@ -154,6 +156,7 @@ declare module '@vue/runtime-core' {
MdiPlus: typeof import('~icons/mdi/plus')['default']
MdiPlusBoxOutline: typeof import('~icons/mdi/plus-box-outline')['default']
MdiPlusOutline: typeof import('~icons/mdi/plus-outline')['default']
MdiPlusRoundedOutline: typeof import('~icons/mdi/plus-rounded-outline')['default']
MdiRefresh: typeof import('~icons/mdi/refresh')['default']
MdiReload: typeof import('~icons/mdi/reload')['default']
MdiRocketLaunchOutline: typeof import('~icons/mdi/rocket-launch-outline')['default']

5
packages/nc-gui-v2/components/general/PreviewAs.vue

@ -7,6 +7,7 @@ import MdiEyeOutline from '~icons/mdi/eye-outline'
import MdiCommentAccountOutline from '~icons/mdi/comment-account-outline'
const { float } = defineProps<{ float?: boolean }>()
const position = useState('preview-as-position', () => ({
y: `${window.innerHeight - 100}px`,
x: `${window.innerWidth / 2 - 250}px`,
@ -98,10 +99,6 @@ watch(previewAs, () => window.location.reload())
</template>
<style 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));
}
.floating-reset-btn {
@apply z-1000 index-100 fixed text-white
@apply flex items-center overflow-hidden whitespace-nowrap gap-4 rounded shadow-md;

85
packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue

@ -2,31 +2,42 @@
import type { FilterType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import FieldListAutoCompleteDropdown from './FieldListAutoCompleteDropdown.vue'
import { useNuxtApp } from '#app'
import { inject, useViewFilters } from '#imports'
import { comparisonOpList } from '~/utils/filterUtils'
import { ActiveViewInj, MetaInj, ReloadViewDataHookInj } from '~/context'
import MdiDeleteIcon from '~icons/mdi/close-box'
import MdiAddIcon from '~icons/mdi/plus'
import type { Filter } from '~/lib/types'
const {
nested = false,
parentId,
autoSave = true,
hookId = null,
modelValue,
} = defineProps<{ nested?: boolean; parentId?: string; autoSave: boolean; hookId?: string; modelValue?: Filter[] }>()
import {
ActiveViewInj,
MetaInj,
ReloadViewDataHookInj,
comparisonOpList,
computed,
inject,
ref,
useNuxtApp,
useViewFilters,
watch,
} from '#imports'
import type { Filter } from '~/lib'
interface Props {
nested?: boolean
parentId?: string
autoSave: boolean
hookId?: string
modelValue?: Filter[]
}
const { nested = false, parentId, autoSave = true, hookId = null, modelValue } = defineProps<Props>()
const emit = defineEmits(['update:filtersLength'])
const meta = inject(MetaInj)
const logicalOps = [
{ value: 'and', text: 'AND' },
{ value: 'or', text: 'OR' },
]
const activeView = inject(ActiveViewInj)
const meta = inject(MetaInj)!
const reloadDataHook = inject(ReloadViewDataHookInj)
const activeView = inject(ActiveViewInj)!
// todo: replace with inject or get from state
const reloadDataHook = inject(ReloadViewDataHookInj)!
const { $e } = useNuxtApp()
@ -35,11 +46,13 @@ const { filters, deleteFilter, saveOrUpdate, loadFilters, addFilter, addFilterGr
parentId,
computed(() => autoSave),
() => {
reloadDataHook?.trigger()
reloadDataHook.trigger()
},
modelValue,
)
const nestedFilters = ref()
const filterUpdateCondition = (filter: FilterType, i: number) => {
saveOrUpdate(filter, i)
$e('a:filter:update', {
@ -63,12 +76,14 @@ const filterUpdateCondition = (filter: FilterType, i: number) => {
// return true
// })
const columns = computed(() => meta?.value?.columns)
const columns = computed(() => meta.value?.columns)
const types = computed(() => {
if (!meta?.value?.columns?.length) {
if (!meta.value?.columns?.length) {
return {}
}
return meta?.value?.columns?.reduce((obj: any, col: any) => {
return meta.value?.columns?.reduce((obj: any, col: any) => {
switch (col.uidt) {
case UITypes.Number:
case UITypes.Decimal:
@ -83,22 +98,15 @@ const types = computed(() => {
})
watch(
() => (activeView?.value as any)?.id,
() => activeView.value?.id,
(n, o) => {
if (n !== o) loadFilters(hookId as string)
},
{ immediate: true },
)
const nestedFilters = ref()
const logicalOps = [
{ value: 'and', text: 'AND' },
{ value: 'or', text: 'OR' },
]
watch(
() => filters?.value?.length,
() => filters.value.length,
(length) => {
emit('update:filtersLength', length ?? 0)
},
@ -106,7 +114,10 @@ watch(
const applyChanges = async (hookId?: string) => {
await sync(hookId)
for (const nestedFilter of nestedFilters?.value || []) {
if (!nestedFilters.value.length) return
for (const nestedFilter of nestedFilters.value) {
if (nestedFilter.parentId) {
await nestedFilter.applyChanges(hookId, true)
}
@ -128,7 +139,7 @@ defineExpose({
<template v-for="(filter, i) in filters" :key="filter.id || i">
<template v-if="filter.status !== 'delete'">
<template v-if="filter.is_group">
<MdiDeleteIcon
<MdiCloseBox
v-if="!filter.readOnly"
:key="i"
small
@ -174,7 +185,7 @@ defineExpose({
mdi-close-box
</v-icon> -->
<MdiDeleteIcon
<MdiCloseBox
v-if="!filter.readOnly"
class="nc-filter-item-remove-btn text-grey align-self-center"
@click.stop="deleteFilter(filter, i)"
@ -260,7 +271,7 @@ defineExpose({
<a-button class="elevation-0 text-capitalize" type="primary" ghost @click.stop="addFilter">
<div class="flex align-center gap-1">
<!-- <v-icon small color="grey"> mdi-plus </v-icon> -->
<MdiAddIcon />
<MdiPlus />
<!-- Add Filter -->
{{ $t('activity.addFilter') }}
</div>
@ -268,7 +279,7 @@ defineExpose({
<a-button class="text-capitalize !text-gray-500" @click.stop="addFilterGroup">
<div class="flex align-center gap-1">
<!-- <v-icon small color="grey"> mdi-plus </v-icon> -->
<MdiAddIcon />
<MdiPlus />
Add Filter Group
<!-- todo: add i18n {{ $t('activity.addFilterGroup') }} -->
</div>

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

@ -8,12 +8,14 @@ import {
ref,
useClipboard,
useElementHover,
useGlobal,
useProject,
useRoute,
useTabs,
useUIPermission,
} from '#imports'
import { TabType } from '~/composables'
const route = useRoute()
const { appInfo, token } = useGlobal()
@ -66,11 +68,13 @@ const isHovered = useElementHover(sidebar)
const copyProjectInfo = async () => {
try {
await loadProjectMetaInfo()
copy(
await copy(
Object.entries(projectMetaInfo.value!)
.map(([k, v]) => `${k}: **${v}**`)
.join('\n'),
)
message.info('Copied project info to clipboard')
} catch (e: any) {
console.log(e)
@ -80,7 +84,8 @@ const copyProjectInfo = async () => {
const copyAuthToken = async () => {
try {
copy(token.value!)
await copy(token.value!)
message.info('Copied auth token to clipboard')
} catch (e: any) {
console.log(e)
@ -138,7 +143,7 @@ const copyAuthToken = async () => {
<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"
class="group cursor-pointer flex gap-4 items-center nc-project-menu overflow-hidden"
>
<template v-if="isOpen">
<div class="text-xl font-semibold truncate">{{ project.title }}</div>
@ -274,10 +279,6 @@ const copyAuthToken = async () => {
</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;
}

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

@ -1,17 +1,3 @@
<script>
export default {
name: 'Index',
}
</script>
<template>
<div class="nc-main-tab">
<span>Welcome to NocoDB!</span>
</div>
<div class="h-full w-full prose text-3xl text-gray-400 flex items-center justify-center">Welcome to NocoDB!</div>
</template>
<style scoped>
.nc-main-tab {
@apply w-full text-3xl text-gray-400 flex align-center justify-center;
}
</style>

15
packages/nc-gui-v2/pages/[projectType]/view/[viewId].vue

@ -1,7 +1,17 @@
<script setup lang="ts">
import { ReadonlyInj, ReloadViewDataHookInj, useRoute } from '#imports'
import {
ReadonlyInj,
ReloadViewDataHookInj,
createEventHook,
definePageMeta,
provide,
ref,
useRoute,
useSharedView,
} from '#imports'
definePageMeta({
public: true,
requiresAuth: false,
layout: 'shared-view',
})
@ -10,7 +20,7 @@ const route = useRoute()
const reloadEventHook = createEventHook<void>()
provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, ref(true))
provide(ReadonlyInj, true)
const { loadSharedView } = useSharedView()
const showPassword = ref(false)
@ -27,6 +37,7 @@ try {
<div v-if="showPassword">
<SharedViewAskPassword v-model="showPassword" />
</div>
<SharedViewGrid v-else />
</NuxtLayout>
</template>

2
packages/nc-gui-v2/pages/forgot-password.vue

@ -129,7 +129,7 @@ function resetError() {
}
.submit {
@apply z-1 relative color-transition border border-gray-300 rounded-md p-3 bg-gray-100/50 text-white bg-primary;
@apply z-1 relative color-transition border border-gray-300 rounded-md p-3 text-white;
&::after {
@apply rounded-md absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary;

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

@ -69,103 +69,119 @@ onMounted(() => {
</div>
<div class="min-w-2/4 flex-auto">
<a-card :loading="isLoading" class="!rounded-lg shadow">
<h1 class="text-center text-4xl p-2 nc-project-page-title flex items-center justify-center gap-2 text-gray-600">
<a-card class="transition-all duration-300 ease-out !rounded-lg shadow">
<h1 class="flex items-center justify-center gap-2 leading-8 mb-8">
<!-- My Projects -->
<b>{{ $t('title.myProject') }}</b>
<MdiRefresh
v-t="['a:project:refresh']"
class="text-sm text-gray-500 hover:text-primary mt-1 cursor-pointer"
@click="loadProjects"
/>
<span class="text-4xl">{{ $t('title.myProject') }}</span>
<a-tooltip title="Reload projects">
<span
class="transition-all duration-200 h-full flex items-center group hover:ring active:(ring ring-pink-500) rounded-full mt-1"
:class="isLoading ? 'animate-spin ring ring-gray-200' : ''"
>
<MdiRefresh
v-t="['a:project:refresh']"
class="text-xl text-gray-500 group-hover:text-pink-500 cursor-pointer"
:class="isLoading ? '!text-primary' : ''"
@click="loadProjects"
/>
</span>
</a-tooltip>
</h1>
<div class="order-1 flex mb-6">
<a-input-search
v-model:value="filterQuery"
class="max-w-[200px] nc-project-page-search"
class="max-w-[250px] nc-project-page-search rounded"
:placeholder="$t('activity.searchProject')"
/>
<div class="flex-grow" />
<a-dropdown v-if="isUIAllowed('projectCreate', true)" @click.stop>
<a-button class="nc-new-project-menu !shadow">
<div class="flex align-center">
<a-dropdown v-if="isUIAllowed('projectCreate', true)" :trigger="['click']">
<button class="nc-new-project-menu">
<div class="flex items-center w-full">
{{ $t('title.newProj') }}
<MdiMenuDown class="menu-icon" />
</div>
</a-button>
</button>
<template #overlay>
<a-menu>
<div
v-t="['c:project:create:xcdb']"
class="grid grid-cols-12 cursor-pointer hover:bg-gray-200 flex items-center p-2 nc-create-xc-db-project"
@click="navigateTo('/project/create')"
>
<MdiPlus class="col-span-2 mr-1 mt-[1px] text-primary text-lg" />
<div class="col-span-10 text-sm xl:text-md">{{ $t('activity.createProject') }}</div>
</div>
<div
v-t="['c:project:create:extdb']"
class="grid grid-cols-12 cursor-pointer hover:bg-gray-200 flex items-center p-2 nc-create-external-db-project"
@click="navigateTo('/project/create-external')"
>
<MdiDatabaseOutline class="col-span-2 mr-1 mt-[1px] text-green-500 text-lg" />
<div class="col-span-10 text-sm xl:text-md" v-html="$t('activity.createProjectExtended.extDB')" />
</div>
<a-menu-item>
<div
v-t="['c:project:create:xcdb']"
class="nc-project-menu-item gap-4"
@click="navigateTo('/project/create')"
>
<MdiPlusOutline class="text-lg" />
<div>{{ $t('activity.createProject') }}</div>
</div>
</a-menu-item>
<a-menu-item>
<div
v-t="['c:project:create:extdb']"
class="nc-project-menu-item gap-4"
@click="navigateTo('/project/create-external')"
>
<MdiDatabaseOutline class="text-lg" />
<div v-html="$t('activity.createProjectExtended.extDB')" />
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
<div v-if="isLoading">
<a-skeleton />
</div>
<a-table
v-else
:custom-row="
(record) => ({
onClick: () => {
$e('a:project:open')
navigateTo(`/nc/${record.id}`)
},
})
"
:data-source="filteredProjects"
:pagination="{ position: ['bottomCenter'] }"
>
<!-- Title -->
<a-table-column key="title" :title="$t('general.title')" data-index="title">
<template #default="{ text }">
<div
class="capitalize !w-[400px] overflow-hidden overflow-ellipsis whitespace-nowrap nc-project-row"
:title="text"
>
{{ text }}
</div>
</template>
</a-table-column>
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div class="flex align-center">
<MdiEditOutline
v-t="['c:project:edit:rename']"
class="nc-action-btn"
@click.stop="navigateTo(`/project/${text}`)"
/>
<MdiDeleteOutline class="nc-action-btn" @click.stop="deleteProject(record)" />
</div>
</template>
</a-table-column>
</a-table>
<TransitionGroup name="layout" mode="out-in">
<div v-if="isLoading">
<a-skeleton />
</div>
<a-table
v-else
:custom-row="
(record) => ({
onClick: () => {
$e('a:project:open')
navigateTo(`/nc/${record.id}`)
},
class: ['group'],
})
"
:data-source="filteredProjects"
:pagination="{ position: ['bottomCenter'] }"
>
<!-- Title -->
<a-table-column key="title" :title="$t('general.title')" data-index="title">
<template #default="{ text }">
<div
class="capitalize color-transition group-hover:text-pink-500 !w-[400px] overflow-hidden overflow-ellipsis whitespace-nowrap"
>
{{ text }}
</div>
</template>
</a-table-column>
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div class="flex items-center gap-2">
<MdiEditOutline
v-t="['c:project:edit:rename']"
class="nc-action-btn"
@click.stop="navigateTo(`/project/${text}`)"
/>
<MdiDeleteOutline class="nc-action-btn" @click.stop="deleteProject(record)" />
</div>
</template>
</a-table-column>
</a-table>
</TransitionGroup>
</a-card>
</div>
@ -182,7 +198,25 @@ onMounted(() => {
<style scoped>
.nc-action-btn {
@apply text-gray-500 hover:text-primary mr-2 cursor-pointer p-2 w-[30px] h-[30px] hover:bg-gray-300/50 rounded-full;
@apply text-gray-500 hover:(text-pink-500 ring) active:(ring ring-pink-500) cursor-pointer p-2 w-[30px] h-[30px] hover:bg-gray-300/50 rounded-full;
}
.nc-new-project-menu {
@apply cursor-pointer z-1 relative color-transition rounded-md px-3 py-2 text-white;
&::after {
@apply rounded-md absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary;
content: '';
z-index: -1;
}
&:hover::after {
@apply transform scale-110 ring ring-pink-500;
}
&:active::after {
@apply ring ring-pink-500;
}
}
:deep(.ant-table-cell) {

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

@ -167,7 +167,7 @@ function resetError() {
}
.submit {
@apply z-1 relative color-transition border border-gray-300 rounded-md p-3 bg-gray-100/50 text-white bg-primary;
@apply z-1 relative color-transition border border-gray-300 rounded-md p-3 text-white;
&::after {
@apply rounded-md absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary;

4
packages/nc-gui-v2/pages/signup/[[token]].vue

@ -162,7 +162,7 @@ function resetError() {
<div class="prose-sm mt-4 text-gray-500">
By signing up, you agree to the
<a class="prose-sm text-pink-500 underline" target="_blank" href="https://nocodb.com/policy-nocodb">Terms of Service</a>
<a class="prose-sm text-gray-500 underline" target="_blank" href="https://nocodb.com/policy-nocodb">Terms of Service</a>
</div>
</div>
</NuxtLayout>
@ -186,7 +186,7 @@ function resetError() {
}
.submit {
@apply z-1 relative color-transition border border-gray-300 rounded-md p-3 bg-gray-100/50 text-white bg-primary;
@apply z-1 relative color-transition border border-gray-300 rounded-md p-3 text-white;
&::after {
@apply rounded-md absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary;

Loading…
Cancel
Save