Browse Source

Merge pull request #3703 from nocodb/chore/cleanup

pull/3783/head
Braks 2 years ago committed by GitHub
parent
commit
5050f00882
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/.eslintrc.js
  2. 4
      packages/nc-gui/app.vue
  3. 28
      packages/nc-gui/components/general/PreviewAs.vue
  4. 2
      packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue
  5. 4
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  6. 3
      packages/nc-gui/components/smartsheet/sidebar/index.vue
  7. 6
      packages/nc-gui/components/tabs/Smartsheet.vue
  8. 2
      packages/nc-gui/composables/index.ts
  9. 9
      packages/nc-gui/composables/useAttachment.ts
  10. 11
      packages/nc-gui/composables/useGlobal/index.ts
  11. 4
      packages/nc-gui/composables/useGlobal/types.ts
  12. 35
      packages/nc-gui/composables/useProject.ts
  13. 65
      packages/nc-gui/composables/useRoles/index.ts
  14. 2
      packages/nc-gui/composables/useSharedFormViewStore.ts
  15. 51
      packages/nc-gui/composables/useSidebar/index.ts
  16. 20
      packages/nc-gui/composables/useTheme/index.ts
  17. 48
      packages/nc-gui/composables/useUIPermission/index.ts
  18. 42
      packages/nc-gui/composables/useUIPermission/rolePermissions.ts
  19. 4
      packages/nc-gui/layouts/default.vue
  20. 1
      packages/nc-gui/lib/enums.ts
  21. 6
      packages/nc-gui/lib/types.ts
  22. 9
      packages/nc-gui/plugins/tele.ts
  23. 1
      packages/nc-gui/utils/index.ts

2
packages/nc-gui/.eslintrc.js

@ -14,5 +14,5 @@ module.exports = {
extends: ['@antfu', 'plugin:prettier/recommended'], extends: ['@antfu', 'plugin:prettier/recommended'],
plugins: ['prettier'], plugins: ['prettier'],
rules: baseRules, rules: baseRules,
ignorePatterns: ['!*.d.ts'], ignorePatterns: ['!*.d.ts', 'components.d.ts'],
} }

4
packages/nc-gui/app.vue

@ -1,11 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, provideTheme, useRoute } from '#imports' import { computed, useRoute } from '#imports'
const route = useRoute() const route = useRoute()
const disableBaseLayout = computed(() => route.path.startsWith('/nc/view') || route.path.startsWith('/nc/form')) const disableBaseLayout = computed(() => route.path.startsWith('/nc/view') || route.path.startsWith('/nc/form'))
provideTheme()
</script> </script>
<template> <template>

28
packages/nc-gui/components/general/PreviewAs.vue

@ -1,29 +1,26 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onUnmounted, useEventListener, useGlobal, useState, watch } from '#imports' import { onUnmounted, ref, useEventListener, useGlobal, useI18n, useNuxtApp, watch } from '#imports'
import MdiAccountStar from '~icons/mdi/account-star' import MdiAccountStar from '~icons/mdi/account-star'
import MdiAccountHardHat from '~icons/mdi/account-hard-hat' import MdiAccountHardHat from '~icons/mdi/account-hard-hat'
import MdiAccountEdit from '~icons/mdi/account-edit' import MdiAccountEdit from '~icons/mdi/account-edit'
import MdiEyeOutline from '~icons/mdi/eye-outline' import MdiEyeOutline from '~icons/mdi/eye-outline'
import MdiCommentAccountOutline from '~icons/mdi/comment-account-outline' import MdiCommentAccountOutline from '~icons/mdi/comment-account-outline'
import { ProjectRole } from '~/lib'
const { float } = defineProps<{ float?: boolean }>() const { float } = defineProps<{ float?: boolean }>()
const position = useState('preview-as-position', () => ({
y: `${window.innerHeight - 100}px`,
x: `${window.innerWidth / 2 - 250}px`,
}))
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
const { t } = useI18n() const { t } = useI18n()
const { previewAs } = useGlobal()
const roleList = [ const roleList = [
{ value: 'editor', label: t('objects.roleType.editor') }, { value: ProjectRole.Editor, label: t('objects.roleType.editor') },
{ value: 'commenter', label: t('objects.roleType.commenter') }, { value: ProjectRole.Commenter, label: t('objects.roleType.commenter') },
{ value: 'viewer', label: t('objects.roleType.viewer') }, { value: ProjectRole.Viewer, label: t('objects.roleType.viewer') },
] ]
const { previewAs } = useGlobal()
const roleIcon = { const roleIcon = {
owner: MdiAccountStar, owner: MdiAccountStar,
creator: MdiAccountHardHat, creator: MdiAccountHardHat,
@ -32,6 +29,11 @@ const roleIcon = {
commenter: MdiCommentAccountOutline, commenter: MdiCommentAccountOutline,
} }
const position = ref({
y: `${window.innerHeight - 100}px`,
x: `${window.innerWidth / 2 - 250}px`,
})
const divMove = (e: MouseEvent) => { const divMove = (e: MouseEvent) => {
position.value = { y: `${e.clientY - 10}px`, x: `${e.clientX - 18}px` } position.value = { y: `${e.clientY - 10}px`, x: `${e.clientX - 18}px` }
} }
@ -49,7 +51,7 @@ onUnmounted(() => {
window.removeEventListener('mousemove', divMove, true) window.removeEventListener('mousemove', divMove, true)
}) })
/** reload page on previewas change */ /** reload page on preview-as change */
watch(previewAs, (newRole) => { watch(previewAs, (newRole) => {
$e('a:navdraw:preview', { role: newRole }) $e('a:navdraw:preview', { role: newRole })
window.location.reload() window.location.reload()
@ -63,7 +65,7 @@ watch(previewAs, (newRole) => {
class="floating-reset-btn nc-floating-preview-btn p-4" class="floating-reset-btn nc-floating-preview-btn p-4"
:style="{ top: position.y, left: position.x }" :style="{ top: position.y, left: position.x }"
> >
<MdiDrag style="cursor: move" class="text-white" @mousedown="mouseDown" /> <MdiDrag class="cursor-move text-white" @mousedown="mouseDown" />
<div class="divider" /> <div class="divider" />

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

@ -32,8 +32,6 @@ interface Emits {
(event: 'openModal', data: { type: ViewTypes; title?: string; copyViewId?: string }): void (event: 'openModal', data: { type: ViewTypes; title?: string; copyViewId?: string }): void
(event: 'deleted'): void (event: 'deleted'): void
(event: 'sorted'): void
} }
const { $e } = useNuxtApp() const { $e } = useNuxtApp()

4
packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -2,7 +2,7 @@
import type { ViewType, ViewTypes } from 'nocodb-sdk' import type { ViewType, ViewTypes } from 'nocodb-sdk'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import type { WritableComputedRef } from '@vue/reactivity' import type { WritableComputedRef } from '@vue/reactivity'
import { IsLockedInj, onKeyStroke, useDebounceFn, useNuxtApp, useUIPermission, useVModel, viewIcons } from '#imports' import { IsLockedInj, inject, onKeyStroke, useDebounceFn, useNuxtApp, useUIPermission, useVModel, viewIcons } from '#imports'
interface Props { interface Props {
view: ViewType view: ViewType
@ -21,7 +21,7 @@ const props = defineProps<Props>()
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
const vModel = useVModel(props, 'view', emits) as WritableComputedRef<any> const vModel = useVModel(props, 'view', emits) as WritableComputedRef<ViewType & { is_default: boolean }>
const { $e } = useNuxtApp() const { $e } = useNuxtApp()

3
packages/nc-gui/components/smartsheet/sidebar/index.vue

@ -14,6 +14,7 @@ import {
useRoute, useRoute,
useRouter, useRouter,
useSidebar, useSidebar,
useUIPermission,
useViews, useViews,
watch, watch,
} from '#imports' } from '#imports'
@ -114,7 +115,7 @@ function onCreate(view: ViewType) {
class="min-h-[var(--toolbar-height)] max-h-[var(--toolbar-height)] flex items-center py-3 px-3 justify-between border-b-1" class="min-h-[var(--toolbar-height)] max-h-[var(--toolbar-height)] flex items-center py-3 px-3 justify-between border-b-1"
/> />
<div v-if="isOpen" class="flex-1 flex flex-col min-h-0"> <div v-if="isOpen" class="flex-1 flex flex-col min-h-0">
<MenuTop @open-modal="openModal" @deleted="loadViews" @sorted="loadViews" /> <MenuTop @open-modal="openModal" @deleted="loadViews" />
<div v-if="isUIAllowed('virtualViewsCreateOrEdit')" class="!my-3 w-full border-b-1" /> <div v-if="isUIAllowed('virtualViewsCreateOrEdit')" class="!my-3 w-full border-b-1" />

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

@ -11,9 +11,9 @@ import {
ReloadViewDataHookInj, ReloadViewDataHookInj,
ReloadViewMetaHookInj, ReloadViewMetaHookInj,
computed, computed,
createEventHook,
inject, inject,
provide, provide,
provideSidebar,
useMetas, useMetas,
useProvideSmartsheetStore, useProvideSmartsheetStore,
watch, watch,
@ -35,14 +35,14 @@ const fields = ref<ColumnType[]>([])
provide(TabMetaInj, ref(activeTab)) provide(TabMetaInj, ref(activeTab))
const meta = computed<TableType>(() => metas.value?.[activeTab?.id as string]) const meta = computed<TableType>(() => metas.value?.[activeTab?.id as string])
const reloadEventHook = createEventHook<void>() const reloadEventHook = createEventHook()
const reloadViewMetaEventHook = createEventHook<void>() const reloadViewMetaEventHook = createEventHook<void>()
const openNewRecordFormHook = createEventHook<void>() const openNewRecordFormHook = createEventHook<void>()
const { isGallery, isGrid, isForm, isLocked } = useProvideSmartsheetStore(activeView, meta) const { isGallery, isGrid, isForm, isLocked } = useProvideSmartsheetStore(activeView, meta)
// provide the sidebar injection state // provide the sidebar injection state
provideSidebar('nc-right-sidebar', { useStorage: true, isOpen: true }) useSidebar('nc-right-sidebar', { useStorage: true, isOpen: true })
// todo: move to store // todo: move to store
provide(MetaInj, meta) provide(MetaInj, meta)

2
packages/nc-gui/composables/index.ts

@ -2,10 +2,10 @@ export * from './useApi'
export * from './useDialog' export * from './useDialog'
export * from './useGlobal' export * from './useGlobal'
export * from './useInjectionState' export * from './useInjectionState'
export * from './useRoles'
export * from './useSidebar' export * from './useSidebar'
export * from './useTheme' export * from './useTheme'
export * from './useUIPermission' export * from './useUIPermission'
export * from './useAttachment'
export * from './useColors' export * from './useColors'
export * from './useColumn' export * from './useColumn'
export * from './useGridViewColumnWidth' export * from './useGridViewColumnWidth'

9
packages/nc-gui/composables/useAttachment.ts

@ -1,9 +0,0 @@
// todo: implement useAttachment
export function useAttachment() {
const localFilesState = reactive([])
const attachments = ref([])
const uploadFile = () => {}
return { uploadFile, localFilesState, attachments }
}

11
packages/nc-gui/composables/useGlobal/index.ts

@ -2,7 +2,7 @@ import { useGlobalState } from './state'
import { useGlobalActions } from './actions' import { useGlobalActions } from './actions'
import type { UseGlobalReturn } from './types' import type { UseGlobalReturn } from './types'
import { useGlobalGetters } from './getters' import { useGlobalGetters } from './getters'
import { useNuxtApp, watch } from '#imports' import { createGlobalState, useNuxtApp, watch } from '#imports'
/** /**
* Global state is injected by {@link import('~/plugins/state') state} plugin into our nuxt app (available as `$state`). * Global state is injected by {@link import('~/plugins/state') state} plugin into our nuxt app (available as `$state`).
@ -34,11 +34,8 @@ import { useNuxtApp, watch } from '#imports'
* console.log(state.isLoading.value) // isLoading = true if any api request is still running * console.log(state.isLoading.value) // isLoading = true if any api request is still running
* ``` * ```
*/ */
export const useGlobal = (): UseGlobalReturn => { export const useGlobal = createGlobalState((): UseGlobalReturn => {
const { $state, provide } = useNuxtApp() const { provide } = useNuxtApp()
/** If state already exists, return it */
if (typeof $state !== 'undefined') return $state
const state = useGlobalState() const state = useGlobalState()
@ -84,4 +81,4 @@ export const useGlobal = (): UseGlobalReturn => {
provide('state', globalState) provide('state', globalState)
return globalState return globalState
} })

4
packages/nc-gui/composables/useGlobal/types.ts

@ -1,7 +1,7 @@
import type { ComputedRef, Ref, ToRefs } from 'vue' import type { ComputedRef, Ref, ToRefs } from 'vue'
import type { WritableComputedRef } from '@vue/reactivity' import type { WritableComputedRef } from '@vue/reactivity'
import type { JwtPayload } from 'jwt-decode' import type { JwtPayload } from 'jwt-decode'
import type { Language, User } from '~/lib' import type { Language, ProjectRole, User } from '~/lib'
import type { useCounter } from '#imports' import type { useCounter } from '#imports'
export interface FeedbackForm { export interface FeedbackForm {
@ -33,7 +33,7 @@ export interface StoredState {
darkMode: boolean darkMode: boolean
feedbackForm: FeedbackForm feedbackForm: FeedbackForm
filterAutoSave: boolean filterAutoSave: boolean
previewAs: string | null previewAs: ProjectRole | null
includeM2M: boolean includeM2M: boolean
currentVersion: string | null currentVersion: string | null
latestRelease: string | null latestRelease: string | null

35
packages/nc-gui/composables/useProject.ts

@ -3,7 +3,6 @@ import type { OracleUi, ProjectType, TableType } from 'nocodb-sdk'
import { SqlUiFactory } from 'nocodb-sdk' import { SqlUiFactory } from 'nocodb-sdk'
import { isString } from '@vueuse/core' import { isString } from '@vueuse/core'
import { import {
USER_PROJECT_ROLES,
computed, computed,
createEventHook, createEventHook,
ref, ref,
@ -11,12 +10,12 @@ import {
useGlobal, useGlobal,
useInjectionState, useInjectionState,
useNuxtApp, useNuxtApp,
useRoles,
useRoute, useRoute,
useState,
useTheme, useTheme,
watch, watch,
} from '#imports' } from '#imports'
import type { ProjectMetaInfo, Roles } from '~/lib' import type { ProjectMetaInfo } from '~/lib'
import type { ThemeConfig } from '@/composables/useTheme' import type { ThemeConfig } from '@/composables/useTheme'
const [setup, use] = useInjectionState((_projectId?: MaybeRef<string>) => { const [setup, use] = useInjectionState((_projectId?: MaybeRef<string>) => {
@ -30,14 +29,14 @@ const [setup, use] = useInjectionState((_projectId?: MaybeRef<string>) => {
const { setTheme, theme } = useTheme() const { setTheme, theme } = useTheme()
const { projectRoles, loadProjectRoles } = useRoles()
const projectLoadedHook = createEventHook<ProjectType>() const projectLoadedHook = createEventHook<ProjectType>()
const project = ref<ProjectType>({}) const project = ref<ProjectType>({})
const tables = ref<TableType[]>([]) const tables = ref<TableType[]>([])
const projectRoles = useState<Roles>(USER_PROJECT_ROLES, () => ({}))
const projectMetaInfo = ref<ProjectMetaInfo | undefined>() const projectMetaInfo = ref<ProjectMetaInfo | undefined>()
const projectId = computed(() => (_projectId ? unref(_projectId) : (route.params.projectId as string))) const projectId = computed(() => (_projectId ? unref(_projectId) : (route.params.projectId as string)))
@ -72,26 +71,6 @@ const [setup, use] = useInjectionState((_projectId?: MaybeRef<string>) => {
} }
} }
async function loadProjectRoles() {
projectRoles.value = {}
if (isSharedBase.value) {
const user = await api.auth.me(
{},
{
headers: {
'xc-shared-base-id': route.params.projectId,
},
},
)
projectRoles.value = user.roles
} else if (project.value.id) {
const user = await api.auth.me({ project_id: project.value.id })
projectRoles.value = user.roles
}
}
async function loadTables() { async function loadTables() {
if (project.value.id) { if (project.value.id) {
const tablesResponse = await api.dbTable.list(project.value.id, { const tablesResponse = await api.dbTable.list(project.value.id, {
@ -121,7 +100,11 @@ const [setup, use] = useInjectionState((_projectId?: MaybeRef<string>) => {
return return
} }
await loadProjectRoles() await loadProjectRoles(
project.value.id || (route.params.projectId as string),
isSharedBase.value,
route.params.projectId as string,
)
await loadTables() await loadTables()

65
packages/nc-gui/composables/useRoles/index.ts

@ -0,0 +1,65 @@
import { isString } from '@vueuse/core'
import { computed, createSharedComposable, ref, useApi, useGlobal } from '#imports'
import type { ProjectRole, Role, Roles } from '~/lib'
/**
* Provides the roles a user currently has
*
* * `userRoles` - the roles a user has outside of projects
* * `projectRoles` - the roles a user has in the current project (if one was loaded)
* * `allRoles` - all roles a user has (userRoles + projectRoles)
* * `hasRole` - a function to check if a user has a specific role
* * `loadProjectRoles` - a function to load the project roles for a specific project (by id)
*/
export const useRoles = createSharedComposable(() => {
const { user } = useGlobal()
const { api } = useApi()
const projectRoles = ref<Roles<ProjectRole>>({})
const userRoles = computed<Roles<Role>>(() => {
let roles = user.value?.roles ?? {}
// if string populate key-value paired object
if (isString(roles)) {
roles = roles.split(',').reduce<Roles>((acc, role) => {
acc[role] = true
return acc
}, {})
}
return roles
})
const allRoles = computed<Roles>(() => ({
...userRoles.value,
...projectRoles.value,
}))
async function loadProjectRoles(projectId: string, isSharedBase?: boolean, sharedBaseId?: string) {
projectRoles.value = {}
if (isSharedBase) {
const user = await api.auth.me(
{},
{
headers: {
'xc-shared-base-id': sharedBaseId,
},
},
)
projectRoles.value = user.roles
} else if (projectId) {
const user = await api.auth.me({ project_id: projectId })
projectRoles.value = user.roles
}
}
function hasRole(role: Role | ProjectRole | string) {
return allRoles.value[role]
}
return { allRoles, userRoles, projectRoles, loadProjectRoles, hasRole }
})

2
packages/nc-gui/composables/useSharedFormViewStore.ts

@ -30,7 +30,7 @@ const [useProvideSharedFormStore, useSharedFormStore] = useInjectionState((share
const sharedView = ref<ViewType>() const sharedView = ref<ViewType>()
const sharedFormView = ref<FormType>() const sharedFormView = ref<FormType>()
const meta = ref<TableType>() const meta = ref<TableType>()
const columns = ref<(ColumnType & { required?: boolean; show?: boolean })[]>() const columns = ref<(ColumnType & { required?: boolean; show?: boolean; label?: string })[]>()
const { api, isLoading } = useApi() const { api, isLoading } = useApi()

51
packages/nc-gui/composables/useSidebar/index.ts

@ -1,5 +1,5 @@
import { useStorage } from '@vueuse/core' import { useStorage } from '@vueuse/core'
import { MemStorage, onScopeDispose, useInjectionState, watch } from '#imports' import { MemStorage, onScopeDispose, ref, syncRef, toRefs, watch } from '#imports'
interface UseSidebarProps { interface UseSidebarProps {
hasSidebar?: boolean hasSidebar?: boolean
@ -8,14 +8,12 @@ interface UseSidebarProps {
} }
/** /**
* Injection state for sidebars * States for sidebars
* *
* Use `provideSidebar` to provide the injection state on current component level (will affect all children injections) * Requires an id to work, id should correspond to the sidebar state you want to create or fetch
* Use `useSidebar` to use the injection state on current component level * If `useSidebar` was not called before it will create a new state if no state can be found for the specified id
*
* If `provideSidebar` is not called explicitly, `useSidebar` will trigger the provider if no injection state can be found
*/ */
const [setupSidebarStore, useSidebarStore] = useInjectionState(() => new MemStorage(), 'SidebarStore') const sidebarStorage = new MemStorage()
const createSidebar = (id: string, props: UseSidebarProps = {}) => { const createSidebar = (id: string, props: UseSidebarProps = {}) => {
const isOpen = ref(props.isOpen ?? false) const isOpen = ref(props.isOpen ?? false)
@ -59,52 +57,25 @@ const createSidebar = (id: string, props: UseSidebarProps = {}) => {
} }
} }
const useSidebarStorage = () => { export function useSidebar(id: string, props: UseSidebarProps = {}) {
let sidebarStorage = useSidebarStore() if (!id) throw new Error('useSidebar requires an id')
if (!sidebarStorage) {
sidebarStorage = setupSidebarStore()
}
return sidebarStorage
}
export const provideSidebar = (id: string, props: UseSidebarProps = {}) => {
const sidebarStorage = useSidebarStorage()
onScopeDispose(() => {
sidebarStorage.remove(id)
})
if (!sidebarStorage.has(id)) { if (!sidebarStorage.has(id)) {
const sidebar = createSidebar(id, props) const sidebar = createSidebar(id, props)
sidebarStorage.set(id, sidebar) sidebarStorage.set(id, sidebar)
return sidebar onScopeDispose(() => {
} else { sidebarStorage.remove(id)
const sidebar = sidebarStorage.get(id) })
if (props.isOpen !== undefined) sidebar.isOpen.value = props.isOpen
if (props.hasSidebar !== undefined) sidebar.hasSidebar.value = props.hasSidebar
return sidebar return sidebar
} } else {
}
export function useSidebar(id: string, props: UseSidebarProps = {}) {
if (!id) throw new Error('useSidebar requires an id')
const sidebarStorage = useSidebarStorage()
if (sidebarStorage.has(id)) {
const sidebar = sidebarStorage.get(id) const sidebar = sidebarStorage.get(id)
if (props.isOpen !== undefined) sidebar.isOpen.value = props.isOpen if (props.isOpen !== undefined) sidebar.isOpen.value = props.isOpen
if (props.hasSidebar !== undefined) sidebar.hasSidebar.value = props.hasSidebar if (props.hasSidebar !== undefined) sidebar.hasSidebar.value = props.hasSidebar
return sidebar return sidebar
} else {
return provideSidebar(id, props)
} }
} }

20
packages/nc-gui/composables/useTheme/index.ts

@ -1,14 +1,14 @@
import { ConfigProvider } from 'ant-design-vue' import { ConfigProvider } from 'ant-design-vue'
import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider' import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider'
import tinycolor from 'tinycolor2' import tinycolor from 'tinycolor2'
import { hexToRGB, themeV2Colors, useCssVar, useInjectionState } from '#imports' import { createGlobalState, hexToRGB, ref, themeV2Colors, useCssVar } from '#imports'
export interface ThemeConfig extends AntTheme { export interface ThemeConfig extends AntTheme {
primaryColor: string primaryColor: string
accentColor: string accentColor: string
} }
const [setup, use] = useInjectionState((config?: Partial<ThemeConfig>) => { export const useTheme = createGlobalState((config?: Partial<ThemeConfig>) => {
const primaryColor = useCssVar('--color-primary', typeof document !== 'undefined' ? document.documentElement : null) const primaryColor = useCssVar('--color-primary', typeof document !== 'undefined' ? document.documentElement : null)
const accentColor = useCssVar('--color-accent', typeof document !== 'undefined' ? document.documentElement : null) const accentColor = useCssVar('--color-accent', typeof document !== 'undefined' ? document.documentElement : null)
@ -46,18 +46,4 @@ const [setup, use] = useInjectionState((config?: Partial<ThemeConfig>) => {
theme: currentTheme, theme: currentTheme,
setTheme, setTheme,
} }
}, 'theme') })
export const provideTheme = setup
export function useTheme(config?: Partial<ThemeConfig>) {
const theme = use()
if (!theme) {
return setup(config)
} else {
if (config) theme.setTheme(config)
}
return theme
}

48
packages/nc-gui/composables/useUIPermission/index.ts

@ -1,46 +1,34 @@
import { isString } from '@vueuse/core' import { isString } from '@vueuse/core'
import type { Permission } from './rolePermissions' import type { Permission } from './rolePermissions'
import rolePermissions from './rolePermissions' import rolePermissions from './rolePermissions'
import { USER_PROJECT_ROLES, computed, useGlobal, useState } from '#imports' import { createSharedComposable, useGlobal, useRoles } from '#imports'
import type { Role, Roles } from '~/lib' import type { ProjectRole, Role } from '~/lib'
export function useUIPermission() { const hasPermission = (role: Role | ProjectRole, hasRole: boolean, permission: Permission | string) => {
const { user, previewAs } = useGlobal() const rolePermission = rolePermissions[role]
const projectRoles = useState<Record<string, boolean>>(USER_PROJECT_ROLES, () => ({})) if (!hasRole || !rolePermission) return false
const baseRoles = computed(() => { if (isString(rolePermission) && rolePermission === '*') return true
let userRoles = isString(user.value?.roles) ? user.value?.roles : ({ ...(user.value?.roles || {}) } as Roles)
// if string populate key-value paired object return rolePermission[permission as keyof typeof rolePermission]
if (typeof userRoles === 'string') { }
userRoles = userRoles.split(',').reduce<Record<string, boolean>>((acc, role) => {
acc[role] = true
return acc
}, {})
}
// merge user role and project specific user roles export const useUIPermission = createSharedComposable(() => {
return { const { previewAs } = useGlobal()
...userRoles, const { allRoles } = useRoles()
...projectRoles.value,
}
})
const isUIAllowed = (permission: Permission | string, skipPreviewAs = false) => { const isUIAllowed = (permission: Permission | string, skipPreviewAs = false) => {
let roles = baseRoles.value as Record<string, any>
if (previewAs.value && !skipPreviewAs) { if (previewAs.value && !skipPreviewAs) {
roles = { const hasPreviewPermission = hasPermission(previewAs.value, true, permission)
[previewAs.value as Role]: true,
} if (hasPreviewPermission) return true
} }
return Object.entries<boolean>(roles).some(([role, hasRole]) => { return Object.entries(allRoles.value).some(([role, hasRole]) =>
const rolePermission = rolePermissions[role as keyof typeof rolePermissions] as '*' | Record<Permission, true> hasPermission(role as Role | ProjectRole, hasRole, permission),
return hasRole && (rolePermission === '*' || rolePermission?.[permission as Permission]) )
})
} }
return { isUIAllowed } return { isUIAllowed }
} })

42
packages/nc-gui/composables/useUIPermission/rolePermissions.ts

@ -1,13 +1,24 @@
import { ProjectRole, Role } from '~/lib'
const rolePermissions = { const rolePermissions = {
// general role permissions
/** todo: enable wildcard permission /** todo: enable wildcard permission
* limited permission due to unexpected behaviour in shared base if opened in same window */ * limited permission due to unexpected behaviour in shared base if opened in same window */
super: { [Role.Super]: {
projectTheme: true, projectTheme: true,
}, },
creator: '*', [Role.Admin]: {},
owner: '*', [Role.Guest]: {},
guest: {}, [Role.User]: {
editor: { projectCreate: true,
projectActions: true,
projectSettings: true,
},
// Project role permissions
[ProjectRole.Creator]: '*',
[ProjectRole.Owner]: '*',
[ProjectRole.Editor]: {
smartSheet: true, smartSheet: true,
xcDatatableEditable: true, xcDatatableEditable: true,
column: true, column: true,
@ -25,32 +36,25 @@ const rolePermissions = {
projectSettings: true, projectSettings: true,
newUser: false, newUser: false,
}, },
commenter: { [ProjectRole.Commenter]: {
smartSheet: true, smartSheet: true,
column: true, column: true,
rowComments: true, rowComments: true,
projectSettings: true, projectSettings: true,
}, },
viewer: { [ProjectRole.Viewer]: {
smartSheet: true, smartSheet: true,
column: true, column: true,
projectSettings: true, projectSettings: true,
}, },
user: {
projectCreate: true,
projectActions: true,
projectSettings: true,
},
} as const } as const
export default rolePermissions type RolePermissions = Omit<typeof rolePermissions, 'creator' | 'owner' | 'guest' | 'admin'>
type GetKeys<T> = T extends Record<string, any> ? keyof T : never type GetKeys<T> = T extends Record<string, any> ? keyof T : never
export type Permission<T extends typeof rolePermissions = typeof rolePermissions, K extends keyof T = keyof T> = K extends export type Permission<K extends keyof RolePermissions = keyof RolePermissions> = RolePermissions[K] extends Record<string, any>
| 'creator' ? GetKeys<RolePermissions[K]>
| 'owner'
? T[K]
: never | T[K] extends Record<string, any>
? GetKeys<T[K]>
: never : never
export default rolePermissions

4
packages/nc-gui/layouts/default.vue

@ -1,12 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useTitle } from '@vueuse/core' import { useTitle } from '@vueuse/core'
import { provideSidebar, useI18n, useRoute } from '#imports' import { useI18n, useRoute, useSidebar } from '#imports'
const route = useRoute() const route = useRoute()
const { te, t } = useI18n() const { te, t } = useI18n()
const { hasSidebar } = provideSidebar('nc-left-sidebar') const { hasSidebar } = useSidebar('nc-left-sidebar')
useTitle(route.meta?.title && te(route.meta.title) ? `${t(route.meta.title)} | NocoDB` : 'NocoDB') useTitle(route.meta?.title && te(route.meta.title) ? `${t(route.meta.title)} | NocoDB` : 'NocoDB')
</script> </script>

1
packages/nc-gui/lib/enums.ts

@ -2,6 +2,7 @@ export enum Role {
Super = 'super', Super = 'super',
Admin = 'admin', Admin = 'admin',
User = 'user', User = 'user',
Guest = 'guest',
} }
export enum ProjectRole { export enum ProjectRole {

6
packages/nc-gui/lib/types.ts

@ -1,13 +1,13 @@
import type { FilterType } from 'nocodb-sdk' import type { FilterType } from 'nocodb-sdk'
import type { I18n } from 'vue-i18n' import type { I18n } from 'vue-i18n'
import type { Role } from './enums' import type { ProjectRole, Role } from './enums'
export interface User { export interface User {
id: string id: string
email: string email: string
firstname: string | null firstname: string | null
lastname: string | null lastname: string | null
roles: Roles roles: Roles | string
invite_token?: string invite_token?: string
project_id?: string project_id?: string
} }
@ -31,7 +31,7 @@ export interface Field {
system?: boolean system?: boolean
} }
export type Roles = Record<Role | string, boolean> | string export type Roles<T extends Role | ProjectRole = Role | ProjectRole> = Record<T | string, boolean>
export type Filter = FilterType & { status?: 'update' | 'delete' | 'create'; parentId?: string; readOnly?: boolean } export type Filter = FilterType & { status?: 'update' | 'delete' | 'create'; parentId?: string; readOnly?: boolean }

9
packages/nc-gui/plugins/tele.ts

@ -41,15 +41,6 @@ export default defineNuxtPlugin(async (nuxtApp) => {
}) })
}) })
/**
* unreachable code?
if (socket) {
socket.emit('page', {
path: route.matched[0].path + (route.query && route.query.type ? `?type=${route.query.type}` : ''),
})
}
*/
const tele = { const tele = {
emit(evt: string, data: Record<string, any>) { emit(evt: string, data: Record<string, any>) {
// debugger // debugger

1
packages/nc-gui/utils/index.ts

@ -18,3 +18,4 @@ export * from './currencyUtils'
export * from './dataUtils' export * from './dataUtils'
export * from './userUtils' export * from './userUtils'
export * from './stringUtils' export * from './stringUtils'
export * from './memStorage'

Loading…
Cancel
Save