mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
381 lines
12 KiB
381 lines
12 KiB
<script lang="ts" setup> |
|
import { storeToRefs } from 'pinia' |
|
import type { WorkspaceType } from 'nocodb-sdk' |
|
import tinycolor from 'tinycolor2' |
|
import { onMounted, projectThemeColors, ref, useWorkspace } from '#imports' |
|
import { navigateTo } from '#app' |
|
|
|
const props = defineProps<{ |
|
isOpen: boolean |
|
}>() |
|
|
|
const workspaceStore = useWorkspace() |
|
|
|
const { saveTheme } = workspaceStore |
|
const { isWorkspaceOwner } = storeToRefs(workspaceStore) |
|
const { loadWorkspaces, clearWorkspaces } = workspaceStore |
|
|
|
const { signOut, signedIn, user, token } = useGlobal() |
|
|
|
const { copy } = useCopy(true) |
|
|
|
const email = computed(() => user.value?.email ?? '---') |
|
|
|
const { isUIAllowed } = useUIPermission() |
|
|
|
const { theme, defaultTheme } = useTheme() |
|
|
|
onMounted(async () => { |
|
// await loadWorkspaces() |
|
}) |
|
|
|
const workspaceModalVisible = ref(false) |
|
const isWorkspaceDropdownOpen = ref(false) |
|
const isAuthTokenCopied = ref(false) |
|
|
|
const createDlg = ref(false) |
|
|
|
const onWorkspaceCreate = async (workspace: WorkspaceType) => { |
|
createDlg.value = false |
|
await loadWorkspaces() |
|
navigateTo(`/${workspace.id}`) |
|
} |
|
|
|
const handleThemeColor = async (mode: 'swatch' | 'primary' | 'accent', color?: string) => { |
|
switch (mode) { |
|
case 'swatch': { |
|
if (color === defaultTheme.primaryColor) { |
|
return await saveTheme(defaultTheme) |
|
} |
|
|
|
const tcolor = tinycolor(color) |
|
if (tcolor.isValid()) { |
|
const complement = tcolor.complement() |
|
|
|
await saveTheme({ |
|
primaryColor: color, |
|
accentColor: complement.toHex8String(), |
|
}) |
|
} |
|
break |
|
} |
|
case 'primary': { |
|
const tcolor = tinycolor(color) |
|
|
|
if (tcolor.isValid()) { |
|
await saveTheme({ |
|
primaryColor: color, |
|
}) |
|
} |
|
break |
|
} |
|
case 'accent': { |
|
const tcolor = tinycolor(color) |
|
|
|
if (tcolor.isValid()) { |
|
await saveTheme({ |
|
accentColor: color, |
|
}) |
|
} |
|
break |
|
} |
|
} |
|
} |
|
|
|
const logout = async () => { |
|
await signOut() |
|
navigateTo('/signin') |
|
} |
|
|
|
const projectStore = useProject() |
|
|
|
const { isSharedBase } = storeToRefs(projectStore) |
|
|
|
// todo: temp |
|
const modalVisible = false |
|
|
|
const copyAuthToken = async () => { |
|
try { |
|
await copy(token.value!) |
|
isAuthTokenCopied.value = true |
|
} catch (e: any) { |
|
console.error(e) |
|
message.error(e.message) |
|
} |
|
} |
|
|
|
onKeyStroke('Escape', () => { |
|
if (isWorkspaceDropdownOpen.value) { |
|
isWorkspaceDropdownOpen.value = false |
|
} |
|
}) |
|
</script> |
|
|
|
<template> |
|
<div class="flex-grow min-w-20"> |
|
<a-dropdown |
|
v-model:visible="isWorkspaceDropdownOpen" |
|
class="h-full min-w-0 flex-1" |
|
:trigger="['click']" |
|
placement="bottom" |
|
overlay-class-name="nc-dropdown-workspace-menu" |
|
> |
|
<div |
|
:style="{ width: props.isOpen ? 'calc(100% - 40px) pr-2' : '100%' }" |
|
:class="[props.isOpen ? '' : 'justify-center']" |
|
data-testid="nc-workspace-menu" |
|
class="group cursor-pointer flex gap-1 items-center nc-workspace-menu overflow-hidden py-1.25 pr-0.25" |
|
> |
|
<slot name="brandIcon" /> |
|
<template v-if="props.isOpen"> |
|
Nocodb |
|
<div class="flex flex-grow"></div> |
|
<MdiCodeTags class="min-w-[17px] text-md transform rotate-90" /> |
|
</template> |
|
|
|
<template v-else> |
|
<MdiFolder class="text-primary cursor-pointer transform hover:scale-105 text-2xl" /> |
|
</template> |
|
</div> |
|
|
|
<template #overlay> |
|
<a-menu class="" @click="isWorkspaceDropdownOpen = false"> |
|
<a-menu-item-group class="!border-t-0"> |
|
<a-menu-divider /> |
|
|
|
<template v-if="!isSharedBase"> |
|
<!-- Copy Auth Token --> |
|
<a-menu-item key="copy"> |
|
<div |
|
v-e="['a:navbar:user:copy-auth-token']" |
|
class="nc-workspace-menu-item group !gap-x-2" |
|
@click.stop="copyAuthToken" |
|
> |
|
<GeneralIcon v-if="isAuthTokenCopied" icon="check" class="group-hover:text-black" /> |
|
<GeneralIcon v-else icon="copy" class="group-hover:text-black" /> |
|
<div v-if="isAuthTokenCopied"> |
|
{{ $t('activity.account.authTokenCopied') }} |
|
</div> |
|
<div v-else> |
|
{{ $t('activity.account.authToken') }} |
|
</div> |
|
</div> |
|
</a-menu-item> |
|
|
|
<a-menu-divider v-if="false" /> |
|
|
|
<!-- Theme --> |
|
<template v-if="isUIAllowed('projectTheme') && false"> |
|
<a-sub-menu key="theme"> |
|
<template #title> |
|
<div class="nc-workspace-menu-item group"> |
|
<GeneralIcon icon="image" class="group-hover:text-accent" /> |
|
{{ $t('activity.account.themes') }} |
|
|
|
<div class="flex-1" /> |
|
|
|
<MaterialSymbolsChevronRightRounded |
|
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400" |
|
/> |
|
</div> |
|
</template> |
|
|
|
<template #expandIcon></template> |
|
|
|
<LazyGeneralColorPicker |
|
:model-value="theme.primaryColor" |
|
:colors="projectThemeColors" |
|
:row-size="9" |
|
:advanced="false" |
|
class="rounded-t" |
|
@input="handleThemeColor('swatch', $event)" |
|
/> |
|
|
|
<!-- Custom Theme --> |
|
<a-sub-menu key="theme-2"> |
|
<template #title> |
|
<div class="nc-workspace-menu-item group"> |
|
{{ $t('labels.customTheme') }} |
|
|
|
<div class="flex-1" /> |
|
|
|
<MaterialSymbolsChevronRightRounded |
|
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400" |
|
/> |
|
</div> |
|
</template> |
|
|
|
<!-- Primary Color --> |
|
<template #expandIcon></template> |
|
|
|
<a-sub-menu key="pick-primary"> |
|
<template #title> |
|
<div class="nc-workspace-menu-item group"> |
|
<ClarityColorPickerSolid class="group-hover:text-black" /> |
|
{{ $t('labels.primaryColor') }} |
|
</div> |
|
</template> |
|
|
|
<template #expandIcon></template> |
|
|
|
<LazyGeneralChromeWrapper @input="handleThemeColor('primary', $event)" /> |
|
</a-sub-menu> |
|
|
|
<!-- Accent Color --> |
|
<a-sub-menu key="pick-accent"> |
|
<template #title> |
|
<div class="nc-workspace-menu-item group"> |
|
<ClarityColorPickerSolid class="group-hover:text-black" /> |
|
{{ $t('labels.accentColor') }} |
|
</div> |
|
</template> |
|
|
|
<template #expandIcon></template> |
|
|
|
<LazyGeneralChromeWrapper @input="handleThemeColor('accent', $event)" /> |
|
</a-sub-menu> |
|
</a-sub-menu> |
|
</a-sub-menu> |
|
</template> |
|
|
|
<a-menu-divider v-if="false" /> |
|
|
|
<!-- Preview As --> |
|
<a-sub-menu v-if="isUIAllowed('previewAs') && false" key="preview-as"> |
|
<template #title> |
|
<div v-e="['c:navdraw:preview-as']" class="nc-workspace-menu-item group"> |
|
<GeneralIcon icon="preview" class="group-hover:text-black" /> |
|
{{ $t('activity.previewAs') }} |
|
|
|
<div class="flex-1" /> |
|
|
|
<MaterialSymbolsChevronRightRounded |
|
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400" |
|
/> |
|
</div> |
|
</template> |
|
|
|
<template #expandIcon></template> |
|
|
|
<LazyGeneralPreviewAs /> |
|
</a-sub-menu> |
|
</template> |
|
<!-- Language --> |
|
<a-sub-menu |
|
v-if="!isEeUI" |
|
key="language" |
|
class="lang-menu !py-0" |
|
popup-class-name="scrollbar-thin-dull min-w-50 max-h-90vh !overflow-auto" |
|
> |
|
<template #title> |
|
<div class="nc-workspace-menu-item group"> |
|
<GeneralIcon icon="translate" class="group-hover:text-black nc-language mr-0.1" /> |
|
{{ $t('labels.language') }} |
|
<div class="flex items-center text-gray-400 text-xs">(Community Translated)</div> |
|
<div class="flex-1" /> |
|
|
|
<MaterialSymbolsChevronRightRounded |
|
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400" |
|
/> |
|
</div> |
|
</template> |
|
|
|
<template #expandIcon></template> |
|
|
|
<LazyGeneralLanguageMenu /> |
|
</a-sub-menu> |
|
|
|
<!-- Account --> |
|
<template v-if="signedIn && !isSharedBase"> |
|
<a-sub-menu key="account"> |
|
<template #title> |
|
<div class="nc-workspace-menu-item group"> |
|
<GeneralIcon icon="account" class="group-hover:text-accent" /> |
|
{{ $t('labels.account') }} |
|
<div class="flex-1" /> |
|
|
|
<MaterialSymbolsChevronRightRounded |
|
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400" |
|
/> |
|
</div> |
|
</template> |
|
|
|
<template #expandIcon></template> |
|
|
|
<a-menu-item key="0" class="!rounded-t"> |
|
<nuxt-link v-e="['c:navbar:user:email']" class="nc-workspace-menu-item group !no-underline" to="/account/users"> |
|
<GeneralIcon icon="at" class="mt-1 group-hover:text-accent" /> |
|
<div class="prose-sm group-hover:text-primary"> |
|
<div>Account</div> |
|
<div class="text-xs text-gray-500">{{ email }}</div> |
|
</div> |
|
</nuxt-link> |
|
</a-menu-item> |
|
|
|
<a-menu-item key="1" class="!rounded-b"> |
|
<div v-e="['a:navbar:user:sign-out']" class="nc-workspace-menu-item group" @click="logout"> |
|
<GeneralIcon icon="signout" class="group-hover:(!text-accent)" /> |
|
|
|
<span class="prose-sm nc-user-menu-signout"> |
|
{{ $t('general.signOut') }} |
|
</span> |
|
</div> |
|
</a-menu-item> |
|
</a-sub-menu> |
|
</template> |
|
</a-menu-item-group> |
|
</a-menu> |
|
</template> |
|
</a-dropdown> |
|
<GeneralModal v-model:visible="workspaceModalVisible" :class="{ active: modalVisible }" width="80%" :footer="null"> |
|
<div class="relative flex flex-col px-6 py-2"> |
|
<div class="absolute right-4 top-4 z-20"> |
|
<a-button type="text" class="!p-1 !h-7 !rounded" @click="workspaceModalVisible = false"> |
|
<component :is="iconMap.close" /> |
|
</a-button> |
|
</div> |
|
<a-tabs v-model:activeKey="tab"> |
|
<template v-if="isWorkspaceOwner"> |
|
<a-tab-pane key="collab" tab="Collaborators" class="w-full"> |
|
<WorkspaceCollaboratorsList class="h-full" /> |
|
</a-tab-pane> |
|
<!-- <a-tab-pane key="settings" tab="Settings" class="w-full"> |
|
<div class="min-h-50 flex items-center justify-center">Not available</div> |
|
</a-tab-pane> --> |
|
</template> |
|
</a-tabs> |
|
</div> |
|
</GeneralModal> |
|
|
|
<WorkspaceCreateDlg v-model="createDlg" @success="onWorkspaceCreate" /> |
|
</div> |
|
</template> |
|
|
|
<style scoped lang="scss"> |
|
.nc-workspace-title-input { |
|
@apply flex-grow py-2 px-3 outline-none hover:(bg-gray-50) focus:(bg-gray-50) font-medium rounded text-md text-defaault; |
|
} |
|
|
|
.nc-menu-sub-head { |
|
@apply pt-2 pb-2 text-gray-500 text-sm px-5; |
|
} |
|
|
|
.nc-workspace-menu-item { |
|
@apply flex items-center pl-2 py-2 gap-2 text-sm hover:text-black; |
|
} |
|
|
|
:deep(.ant-dropdown-menu-item-group-title) { |
|
@apply hidden; |
|
} |
|
|
|
:deep(.ant-tabs-nav) { |
|
@apply !mb-0; |
|
} |
|
|
|
:deep(.ant-dropdown-menu-submenu-title) { |
|
@apply !py-0; |
|
.nc-icon { |
|
@apply !text-xs; |
|
} |
|
} |
|
</style>
|
|
|