Browse Source

Merge pull request #3091 from nocodb/feat/gui-v2-preview-as

vue3: Add preview as option
pull/3099/head
Pranav C 2 years ago committed by GitHub
parent
commit
c6fdd69129
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui-v2/components.d.ts
  2. 114
      packages/nc-gui-v2/components/general/PreviewAs.vue
  3. 4
      packages/nc-gui-v2/composables/useApi/interceptors.ts
  4. 1
      packages/nc-gui-v2/composables/useGlobal/state.ts
  5. 1
      packages/nc-gui-v2/composables/useGlobal/types.ts
  6. 15
      packages/nc-gui-v2/composables/useUIPermission/index.ts
  7. 4
      packages/nc-gui-v2/pages/nc/[projectId]/index.vue

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

@ -84,6 +84,7 @@ declare module '@vue/runtime-core' {
MdiChat: typeof import('~icons/mdi/chat')['default']
MdiCheck: typeof import('~icons/mdi/check')['default']
MdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
MdiClose: typeof import('~icons/mdi/close')['default']
MdiCloseCircle: typeof import('~icons/mdi/close-circle')['default']
MdiCloseThick: typeof import('~icons/mdi/close-thick')['default']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
@ -95,6 +96,7 @@ declare module '@vue/runtime-core' {
MdiDrag: typeof import('~icons/mdi/drag')['default']
MdiDragVertical: typeof import('~icons/mdi/drag-vertical')['default']
MdiEmail: typeof import('~icons/mdi/email')['default']
MdiExitToApp: typeof import('~icons/mdi/exit-to-app')['default']
MdiEyeOffOutline: typeof import('~icons/mdi/eye-off-outline')['default']
MdiFlag: typeof import('~icons/mdi/flag')['default']
MdiFolder: typeof import('~icons/mdi/folder')['default']

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

@ -0,0 +1,114 @@
<script lang="ts" setup>
import { onUnmounted, useEventListener, useGlobal, watch } from '#imports'
import { useState } from '#app'
import MdiAccountStar from '~icons/mdi/account-star'
import MdiAccountHardHat from '~icons/mdi/account-hard-hat'
import MdiAccountEdit from '~icons/mdi/account-edit'
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`,
}))
const roleList = [{ title: 'editor' }, { title: 'commenter' }, { title: 'viewer' }]
const { previewAs } = useGlobal()
const roleIcon = {
owner: MdiAccountStar,
creator: MdiAccountHardHat,
editor: MdiAccountEdit,
viewer: MdiEyeOutline,
commenter: MdiCommentAccountOutline,
}
const divMove = (e: MouseEvent) => {
position.value = { y: `${e.clientY - 10}px`, x: `${e.clientX - 18}px` }
}
const mouseUp = () => {
window.removeEventListener('mousemove', divMove, true)
}
useEventListener(window, 'mouseup', mouseUp, false)
const mouseDown = () => {
window.addEventListener('mousemove', divMove, true)
}
onUnmounted(() => {
window.removeEventListener('mousemove', divMove, true)
})
/** reload page on previewas change */
watch(previewAs, () => window.location.reload())
</script>
<template>
<div
v-if="float"
v-show="previewAs"
class="floating-reset-btn nc-floating-preview-btn p-4"
:style="{ top: position.y, left: position.x }"
>
<MdiDrag style="cursor: move" class="text-white" @mousedown="mouseDown" />
<div class="divider" />
<div class="pointer flex items-center gap-4">
<span>Preview as :</span>
<a-radio-group v-model:value="previewAs" name="radioGroup">
<a-radio v-for="role in roleList" :key="role.title" class="capitalize !text-white" :value="role.title"
>{{ role.title }}
</a-radio>
</a-radio-group>
<div class="divider -ml-4" />
<div class="flex items-center gap-2 cursor-pointer" @click="previewAs = null">
<MdiExitToApp />
Exit
</div>
</div>
</div>
<template v-else>
<template v-for="role of roleList" :key="role.title">
<a-menu-item :class="`pointer nc-preview-${role.title}`" @click="previewAs = role.title">
<div class="p-1 flex gap-2 items-center">
<component :is="roleIcon[role.title]" />
<span class="capitalize" :class="{ 'x-active--text': role.title === previewAs }">{{ role.title }}</span>
</div>
</a-menu-item>
</template>
<template v-if="previewAs">
<a-menu-item @click="previewAs = null">
<div class="p-1 flex gap-2 items-center">
<mdi-close />
<!-- Reset Preview -->
<span class="text-capitalize text-xs whitespace-nowrap">{{ $t('activity.resetReview') }}</span>
</div>
</a-menu-item>
</template>
</template>
</template>
<style scoped>
.floating-reset-btn {
@apply z-1000 index-100 fixed text-white
@apply flex items-center overflow-hidden whitespace-nowrap gap-4 rounded shadow-md;
background-color: #4351e7;
}
:deep(.ant-radio) {
@apply transform scale-80;
}
.divider {
@apply h-5 w-2px bg-white/50;
}
</style>

4
packages/nc-gui-v2/composables/useApi/interceptors.ts

@ -13,8 +13,8 @@ export function addAxiosInterceptors(api: Api<any>) {
if (state.token.value) config.headers['xc-auth'] = state.token.value
if (!config.url?.endsWith('/user/me') && !config.url?.endsWith('/admin/roles')) {
// config.headers['xc-preview'] = store.state.users.previewAs
if (!config.url?.endsWith('/user/me') && !config.url?.endsWith('/admin/roles') && state.previewAs?.value) {
config.headers['xc-preview'] = state.previewAs.value
}
if (!config.url?.endsWith('/user/me') && !config.url?.endsWith('/admin/roles')) {

1
packages/nc-gui-v2/composables/useGlobal/state.ts

@ -57,6 +57,7 @@ export function useGlobalState(storageKey = 'nocodb-gui-v2'): State {
isHidden: false,
},
filterAutoSave: true,
previewAs: null,
}
/** saves a reactive state, any change to these values will write/delete to localStorage */

1
packages/nc-gui-v2/composables/useGlobal/types.ts

@ -22,6 +22,7 @@ export interface StoredState {
darkMode: boolean
feedbackForm: FeedbackForm
filterAutoSave: boolean
previewAs: string | null
}
export type State = ToRefs<Omit<StoredState, 'token'>> & {

15
packages/nc-gui-v2/composables/useUIPermission/index.ts

@ -7,7 +7,7 @@ export function useUIPermission() {
const { $state } = useNuxtApp()
const projectRoles = useState<Record<string, boolean>>(USER_PROJECT_ROLES, () => ({}))
const isUIAllowed = (permission: Permission, _skipPreviewAs = false) => {
const isUIAllowed = (permission: Permission, skipPreviewAs = false) => {
const user = $state.user
let userRoles = user?.value?.roles || {}
// if string populate key-value paired object
@ -19,17 +19,16 @@ export function useUIPermission() {
}
// merge user role and project specific user roles
const roles = {
let roles = {
...userRoles,
...(projectRoles?.value || {}),
}
// todo: handle preview as
// if (state.previewAs && !skipPreviewAs) {
// roles = {
// [state.previewAs]: true
// };
// }
if ($state.previewAs.value && !skipPreviewAs) {
roles = {
[$state.previewAs.value]: true,
}
}
return Object.entries<boolean>(roles).some(([role, hasRole]) => {
const rolePermission = rolePermissions[role as keyof typeof rolePermissions] as '*' | Record<Permission, true>

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

@ -177,7 +177,7 @@ await loadTables()
<template #expandIcon></template>
<a-menu-item> Foo </a-menu-item>
<GeneralPreviewAs />
</a-sub-menu>
</a-menu-item-group>
</a-menu>
@ -212,6 +212,8 @@ await loadTables()
<dashboard-settings-modal v-model="dialogOpen" :open-key="openDialogKey" />
<NuxtPage />
<GeneralPreviewAs float />
</NuxtLayout>
</template>

Loading…
Cancel
Save