mirror of https://github.com/nocodb/nocodb
189 changed files with 2745 additions and 3910 deletions
After Width: | Height: | Size: 685 B |
@ -0,0 +1,60 @@
|
||||
<script setup lang="ts"> |
||||
const workspaceStore = useWorkspace() |
||||
const projectStore = useProject() |
||||
|
||||
const { isLeftSidebarOpen } = storeToRefs(useSidebarStore()) |
||||
|
||||
const { isSharedBase } = storeToRefs(projectStore) |
||||
const { activeWorkspace, isWorkspaceLoading } = storeToRefs(workspaceStore) |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
class="flex items-center px-2 nc-sidebar-header py-1.2 w-full border-b-1 border-gray-200 group" |
||||
:data-workspace-title="activeWorkspace?.title" |
||||
style="height: var(--topbar-height)" |
||||
> |
||||
<div v-if="!isWorkspaceLoading" class="flex flex-row items-center w-full"> |
||||
<WorkspaceMenu /> |
||||
|
||||
<div class="flex flex-grow min-w-1"></div> |
||||
|
||||
<NcTooltip |
||||
class="flex opacity-0 group-hover:opacity-100 transition-opacity duration-50" |
||||
:class="{ |
||||
'!opacity-100': !isLeftSidebarOpen, |
||||
}" |
||||
placement="bottom" |
||||
hide-on-click |
||||
> |
||||
<template #title> |
||||
{{ |
||||
isLeftSidebarOpen |
||||
? `${$t('general.hide')} ${$t('objects.sidebar').toLowerCase()}` |
||||
: `${$t('general.show')} ${$t('objects.sidebar').toLowerCase()}` |
||||
}} |
||||
</template> |
||||
<NcButton |
||||
type="text" |
||||
size="small" |
||||
class="nc-sidebar-left-toggle-icon !text-gray-700 !hover:text-gray-800 !hover:bg-gray-200" |
||||
@click="isLeftSidebarOpen = !isLeftSidebarOpen" |
||||
> |
||||
<div class="flex items-center text-inherit"> |
||||
<GeneralIcon |
||||
icon="doubleLeftArrow" |
||||
class="duration-150 transition-all !text-lg -mt-0.5" |
||||
:class="{ |
||||
'transform rotate-180': !isLeftSidebarOpen, |
||||
}" |
||||
/> |
||||
</div> |
||||
</NcButton> |
||||
</NcTooltip> |
||||
</div> |
||||
<div v-else class="flex flex-row items-center w-full mt-0.25 ml-2.5 gap-x-3"> |
||||
<a-skeleton-input :active="true" class="!w-6 !h-6 !rounded overflow-hidden" /> |
||||
<a-skeleton-input :active="true" class="!w-40 !h-6 !rounded overflow-hidden" /> |
||||
</div> |
||||
</div> |
||||
</template> |
@ -0,0 +1,86 @@
|
||||
<script setup lang="ts"> |
||||
const workspaceStore = useWorkspace() |
||||
const projectStore = useProject() |
||||
|
||||
const { appInfo } = useGlobal() |
||||
|
||||
const { isWorkspaceLoading, isWorkspaceOwnerOrCreator, isWorkspaceSettingsPageOpened } = storeToRefs(workspaceStore) |
||||
|
||||
const { navigateToWorkspaceSettings } = workspaceStore |
||||
|
||||
const { isSharedBase } = storeToRefs(projectStore) |
||||
|
||||
const isCreateProjectOpen = ref(false) |
||||
|
||||
const navigateToSettings = () => { |
||||
// TODO: Handle cloud case properly |
||||
navigateToWorkspaceSettings() |
||||
|
||||
// if (appInfo.value.baseHostName) { |
||||
// window.location.href = `https://app.${appInfo.value.baseHostName}/dashboard` |
||||
// } else { |
||||
// } |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<template v-if="isWorkspaceLoading"> |
||||
<div class="flex flex-col w-full gap-y-3.75 ml-3 mt-3.75"> |
||||
<div v-if="appInfo.ee" class="flex flex-row items-center w-full gap-x-3"> |
||||
<a-skeleton-input :active="true" class="!w-4 !h-4 !rounded overflow-hidden" /> |
||||
<a-skeleton-input :active="true" class="!w-40 !h-4 !rounded overflow-hidden" /> |
||||
</div> |
||||
<div class="flex flex-row items-center w-full gap-x-3"> |
||||
<a-skeleton-input :active="true" class="!w-4 !h-4 !rounded overflow-hidden" /> |
||||
<a-skeleton-input :active="true" class="!w-40 !h-4 !rounded overflow-hidden" /> |
||||
</div> |
||||
<div class="flex flex-row items-center w-full gap-x-3"> |
||||
<a-skeleton-input :active="true" class="!w-4 !h-4 !rounded overflow-hidden" /> |
||||
<a-skeleton-input :active="true" class="!w-40 !h-4 !rounded overflow-hidden" /> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<template v-else-if="!isSharedBase"> |
||||
<div class="flex flex-col p-1 gap-y-0.5 mt-0.25"> |
||||
<DashboardSidebarTopSectionHeader /> |
||||
|
||||
<NcButton |
||||
v-if="isWorkspaceOwnerOrCreator" |
||||
type="text" |
||||
size="small" |
||||
class="nc-sidebar-top-button" |
||||
data-testid="nc-sidebar-team-settings-btn" |
||||
:centered="false" |
||||
:class="{ |
||||
'!text-brand-500 !bg-brand-50 !hover:bg-brand-50': isWorkspaceSettingsPageOpened, |
||||
'!hover:bg-gray-200': !isWorkspaceSettingsPageOpened, |
||||
}" |
||||
@click="navigateToSettings" |
||||
> |
||||
<div class="flex items-center gap-2"> |
||||
<GeneralIcon icon="settings" class="!h-4" /> |
||||
<div>Team & Settings</div> |
||||
</div> |
||||
</NcButton> |
||||
<WorkspaceCreateProjectBtn |
||||
v-model:is-open="isCreateProjectOpen" |
||||
modal |
||||
type="text" |
||||
class="nc-sidebar-top-button !hover:bg-gray-200" |
||||
data-testid="nc-sidebar-create-project-btn" |
||||
> |
||||
<div class="gap-x-2 flex flex-row w-full items-center !font-normal"> |
||||
<GeneralIcon icon="plus" /> |
||||
|
||||
<div class="flex">{{ $t('title.newProj') }}</div> |
||||
</div> |
||||
</WorkspaceCreateProjectBtn> |
||||
</div> |
||||
</template> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.nc-sidebar-top-button { |
||||
@apply w-full !rounded-md !font-normal !px-3; |
||||
} |
||||
</style> |
@ -0,0 +1,188 @@
|
||||
<script lang="ts" setup> |
||||
import GithubButton from 'vue-github-button' |
||||
|
||||
const { user, signOut, token, appInfo } = useGlobal() |
||||
|
||||
const { clearWorkspaces } = useWorkspace() |
||||
|
||||
const { leftSidebarState } = storeToRefs(useSidebarStore()) |
||||
|
||||
const { copy } = useCopy(true) |
||||
|
||||
const name = computed(() => `${user.value?.firstname ?? ''} ${user.value?.lastname ?? ''}`.trim()) |
||||
|
||||
const isMenuOpen = ref(false) |
||||
|
||||
const isAuthTokenCopied = ref(false) |
||||
|
||||
const isLoggingOut = ref(false) |
||||
|
||||
const logout = async () => { |
||||
isLoggingOut.value = true |
||||
try { |
||||
await signOut(false) |
||||
|
||||
await clearWorkspaces() |
||||
|
||||
await navigateTo('/signin') |
||||
} catch (e) { |
||||
console.error(e) |
||||
} finally { |
||||
isLoggingOut.value = false |
||||
} |
||||
} |
||||
|
||||
const onCopy = async () => { |
||||
try { |
||||
await copy(token.value!) |
||||
isAuthTokenCopied.value = true |
||||
} catch (e: any) { |
||||
console.error(e) |
||||
message.error(e.message) |
||||
} |
||||
} |
||||
|
||||
watch(isMenuOpen, () => { |
||||
if (isAuthTokenCopied.value) { |
||||
isAuthTokenCopied.value = false |
||||
} |
||||
}) |
||||
|
||||
watch(leftSidebarState, () => { |
||||
if (leftSidebarState.value === 'peekCloseEnd') { |
||||
isMenuOpen.value = false |
||||
} |
||||
}) |
||||
|
||||
// This is a hack to prevent github button error (prevents navigateTo if user is not signed in) |
||||
const isMounted = ref(false) |
||||
|
||||
onMounted(() => { |
||||
isMounted.value = true |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="flex w-full flex-col p-1 border-t-1 border-gray-200 gap-y-2"> |
||||
<NcDropdown v-model:visible="isMenuOpen" placement="topLeft" overlay-class-name="!min-w-64"> |
||||
<div |
||||
class="flex flex-row py-2 px-3 gap-x-2 items-center hover:bg-gray-200 rounded-lg cursor-pointer h-10" |
||||
data-testid="nc-sidebar-userinfo" |
||||
> |
||||
<GeneralUserIcon /> |
||||
<div class="flex truncate"> |
||||
{{ name ? name : user?.email }} |
||||
</div> |
||||
<GeneralIcon icon="arrowUp" class="!min-w-5" /> |
||||
</div> |
||||
<template #overlay> |
||||
<NcMenu> |
||||
<NcMenuItem data-testid="nc-sidebar-user-logout" @click="logout"> |
||||
<GeneralLoader v-if="isLoggingOut" class="!ml-0.5 !mr-0.5 !max-h-4.5 !-mt-0.5" /> |
||||
<GeneralIcon v-else icon="signout" class="menu-icon" /> |
||||
Log Out</NcMenuItem |
||||
> |
||||
<NcDivider /> |
||||
<a href="https://docs.nocodb.com" target="_blank" class="!underline-transparent"> |
||||
<NcMenuItem> |
||||
<GeneralIcon icon="help" class="menu-icon" /> |
||||
Help Center</NcMenuItem |
||||
> |
||||
</a> |
||||
<NcDivider /> |
||||
<a href="https://discord.gg/5RgZmkW" target="_blank" class="!underline-transparent"> |
||||
<NcMenuItem class="social-icon-wrapper" |
||||
><GeneralIcon class="social-icon" icon="discord" />Join our Discord</NcMenuItem |
||||
> |
||||
</a> |
||||
<a href="https://www.reddit.com/r/NocoDB" target="_blank" class="!underline-transparent"> |
||||
<NcMenuItem class="social-icon-wrapper"><GeneralIcon class="social-icon" icon="reddit" />/r/NocoDB</NcMenuItem> |
||||
</a> |
||||
<a href="https://twitter.com/nocodb" target="_blank" class="!underline-transparent"> |
||||
<NcMenuItem class="social-icon-wrapper group" |
||||
><GeneralIcon class="text-gray-500 group-hover:text-gray-800" icon="twitter" />Twitter</NcMenuItem |
||||
> |
||||
</a> |
||||
<template v-if="!appInfo.ee"> |
||||
<NcDivider /> |
||||
<a-popover key="language" class="lang-menu !py-0" placement="rightBottom"> |
||||
<NcMenuItem> |
||||
<GeneralIcon icon="translate" class="group-hover:text-black nc-language ml-0.25 menu-icon" /> |
||||
{{ $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" /> |
||||
</NcMenuItem> |
||||
|
||||
<template #content> |
||||
<div class="bg-white max-h-50vh scrollbar-thin-dull min-w-50 !overflow-auto"> |
||||
<LazyGeneralLanguageMenu /> |
||||
</div> |
||||
</template> |
||||
</a-popover> |
||||
</template> |
||||
|
||||
<NcDivider /> |
||||
<NcMenuItem @click="onCopy" |
||||
><GeneralIcon v-if="isAuthTokenCopied" icon="check" class="group-hover:text-black menu-icon" /><GeneralIcon |
||||
v-else |
||||
icon="copy" |
||||
class="menu-icon" |
||||
/> |
||||
<template v-if="isAuthTokenCopied"> Copied Auth Token </template> |
||||
<template v-else> Copy Auth Token </template> |
||||
</NcMenuItem> |
||||
<nuxt-link v-e="['c:navbar:user:email']" class="!no-underline" to="/account/tokens"> |
||||
<NcMenuItem><GeneralIcon icon="settings" class="menu-icon" /> Account Settings</NcMenuItem> |
||||
</nuxt-link> |
||||
</NcMenu> |
||||
</template> |
||||
</NcDropdown> |
||||
|
||||
<div v-if="appInfo.ee" class="text-gray-500 text-xs pl-3">© 2023 NocoDB. Inc</div> |
||||
<div v-else-if="isMounted" class="flex flex-col gap-y-1 pt-1"> |
||||
<div class="flex items-start flex-row justify-center px-2 gap-2"> |
||||
<GithubButton href="https://github.com/nocodb/nocodb" data-icon="octicon-star" data-show-count="true" data-size="large"> |
||||
Star |
||||
</GithubButton> |
||||
</div> |
||||
|
||||
<div class="flex items-start flex-row justify-center gap-2"> |
||||
<GeneralJoinCloud class="color-transition px-2 text-gray-500 cursor-pointer select-none hover:text-accent" /> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.menu-icon { |
||||
@apply !min-h-4.5; |
||||
line-height: 1rem; |
||||
font-size: 1.125rem; |
||||
} |
||||
|
||||
:deep(.ant-popover-inner-content) { |
||||
@apply !p-0 !rounded-md; |
||||
} |
||||
.social-icon { |
||||
// Make icon black and white |
||||
filter: grayscale(100%); |
||||
|
||||
// Make icon red on hover |
||||
&:hover { |
||||
filter: grayscale(100%) invert(100%); |
||||
} |
||||
} |
||||
|
||||
.social-icon-wrapper { |
||||
.nc-icon { |
||||
@apply mr-0.15; |
||||
} |
||||
&:hover { |
||||
.social-icon { |
||||
filter: none !important; |
||||
} |
||||
} |
||||
} |
||||
</style> |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,53 @@
|
||||
<script lang="ts" setup> |
||||
const { isLeftSidebarOpen: _isLeftSidebarOpen } = storeToRefs(useSidebarStore()) |
||||
const isLeftSidebarOpen = ref(_isLeftSidebarOpen.value) |
||||
|
||||
watch(_isLeftSidebarOpen, (val) => { |
||||
if (val) { |
||||
isLeftSidebarOpen.value = true |
||||
} else { |
||||
setTimeout(() => { |
||||
isLeftSidebarOpen.value = false |
||||
}, 300) |
||||
} |
||||
}) |
||||
|
||||
const onClick = () => { |
||||
if (_isLeftSidebarOpen.value) return |
||||
|
||||
_isLeftSidebarOpen.value = !_isLeftSidebarOpen.value |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<NcTooltip |
||||
placement="topLeft" |
||||
hide-on-click |
||||
class="transition-all duration-100" |
||||
:class="{ |
||||
'!w-0 !opacity-0': isLeftSidebarOpen, |
||||
'!w-8 !opacity-100': !isLeftSidebarOpen, |
||||
}" |
||||
> |
||||
<template #title> |
||||
{{ |
||||
isLeftSidebarOpen |
||||
? `${$t('general.hide')} ${$t('objects.sidebar').toLowerCase()}` |
||||
: `${$t('general.show')} ${$t('objects.sidebar').toLowerCase()}` |
||||
}} |
||||
</template> |
||||
<NcButton |
||||
type="text" |
||||
size="small" |
||||
class="nc-sidebar-left-toggle-icon !text-gray-600 !hover:text-gray-800" |
||||
:class="{ |
||||
'invisible !w-0': isLeftSidebarOpen, |
||||
}" |
||||
@click="onClick" |
||||
> |
||||
<div class="flex items-center text-inherit"> |
||||
<GeneralIcon icon="doubleRightArrow" class="duration-150 transition-all !text-lg -mt-0.25" /> |
||||
</div> |
||||
</NcButton> |
||||
</NcTooltip> |
||||
</template> |
@ -0,0 +1,51 @@
|
||||
<script lang="ts" setup> |
||||
import type { UserType } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
hideLabel?: boolean |
||||
size?: 'small' | 'medium' |
||||
}>() |
||||
|
||||
const { user } = useGlobal() |
||||
|
||||
const backgroundColor = computed(() => (user.value?.id ? stringToColour(user.value?.id) : '#FFFFFF')) |
||||
|
||||
const size = computed(() => props.size || 'medium') |
||||
|
||||
const firstName = computed(() => user.value?.firstname ?? '') |
||||
const lastName = computed(() => user.value?.lastname ?? '') |
||||
const email = computed(() => user.value?.email ?? '') |
||||
|
||||
const usernameInitials = computed(() => { |
||||
if (firstName.value && lastName.value) { |
||||
return firstName.value[0] + lastName.value[0] |
||||
} else if (firstName.value) { |
||||
return firstName.value[0] + (firstName.value.length > 1 ? firstName.value[1] : '') |
||||
} else if (lastName.value) { |
||||
return lastName.value[0] + (lastName.value.length > 1 ? lastName.value[1] : '') |
||||
} else { |
||||
return email.value[0] + email.value[1] |
||||
} |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
class="flex nc-user-avatar" |
||||
:class="{ |
||||
'min-w-4 min-h-4': size === 'small', |
||||
'min-w-6 min-h-6': size === 'medium', |
||||
}" |
||||
:style="{ backgroundColor }" |
||||
> |
||||
<template v-if="!props.hideLabel"> |
||||
{{ usernameInitials }} |
||||
</template> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.nc-user-avatar { |
||||
@apply rounded-full text-xs flex items-center justify-center text-white uppercase; |
||||
} |
||||
</style> |
@ -0,0 +1,37 @@
|
||||
<script lang="ts" setup> |
||||
import type { WorkspaceType } from 'nocodb-sdk' |
||||
|
||||
const props = defineProps<{ |
||||
workspace: WorkspaceType | undefined |
||||
hideLabel?: boolean |
||||
size?: 'small' | 'medium' | 'large' |
||||
}>() |
||||
|
||||
const workspaceColor = computed(() => |
||||
props.workspace ? props.workspace.meta?.color || stringToColour(props.workspace.id!) : undefined, |
||||
) |
||||
|
||||
const size = computed(() => props.size || 'medium') |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
class="flex nc-workspace-avatar" |
||||
:class="{ |
||||
'min-w-4 w-4 h-4 rounded': size === 'small', |
||||
'min-w-6 w-6 h-6 rounded-md': size === 'medium', |
||||
'min-w-10 w-10 h-10 rounded-lg !text-base': size === 'large', |
||||
}" |
||||
:style="{ backgroundColor: workspaceColor }" |
||||
> |
||||
<template v-if="!props.hideLabel"> |
||||
{{ props.workspace?.title?.slice(0, 2) }} |
||||
</template> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.nc-workspace-avatar { |
||||
@apply text-xs flex items-center justify-center text-white uppercase; |
||||
} |
||||
</style> |
@ -0,0 +1,9 @@
|
||||
<template> |
||||
<a-divider class="nc-divider" /> |
||||
</template> |
||||
|
||||
<style lang="scss"> |
||||
.nc-divider.ant-divider { |
||||
@apply !my-1; |
||||
} |
||||
</style> |
@ -0,0 +1,19 @@
|
||||
<script lang="ts" setup> |
||||
const props = defineProps<{ |
||||
selectable?: boolean | undefined |
||||
}>() |
||||
|
||||
const selectable = computed(() => props.selectable ?? false) |
||||
</script> |
||||
|
||||
<template> |
||||
<a-menu class="nc-menu" :selectable="selectable"> |
||||
<slot /> |
||||
</a-menu> |
||||
</template> |
||||
|
||||
<style lang="scss"> |
||||
.nc-menu { |
||||
@apply bg-white !rounded-md !py-1; |
||||
} |
||||
</style> |
@ -0,0 +1,17 @@
|
||||
<template> |
||||
<a-menu-item class="nc-menu-item"> |
||||
<div class="nc-menu-item-inner"> |
||||
<slot /> |
||||
</div> |
||||
</a-menu-item> |
||||
</template> |
||||
|
||||
<style lang="scss"> |
||||
.nc-menu-item { |
||||
@apply !py-2 font-normal text-sm; |
||||
} |
||||
|
||||
.nc-menu-item-inner { |
||||
@apply flex flex-row items-center gap-x-2.25; |
||||
} |
||||
</style> |
@ -0,0 +1,53 @@
|
||||
<script lang="ts" setup> |
||||
const { isRightSidebarOpen: _isRightSidebarOpen } = storeToRefs(useSidebarStore()) |
||||
const isRightSidebarOpen = ref(_isRightSidebarOpen.value) |
||||
|
||||
watch(_isRightSidebarOpen, (val) => { |
||||
if (val) { |
||||
isRightSidebarOpen.value = true |
||||
} else { |
||||
setTimeout(() => { |
||||
isRightSidebarOpen.value = false |
||||
}, 300) |
||||
} |
||||
}) |
||||
|
||||
const onClick = () => { |
||||
if (_isRightSidebarOpen.value) return |
||||
|
||||
_isRightSidebarOpen.value = !_isRightSidebarOpen.value |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<NcTooltip |
||||
placement="bottomLeft" |
||||
hide-on-click |
||||
class="transition-all duration-100" |
||||
:class="{ |
||||
'!w-0 !opacity-0': isRightSidebarOpen, |
||||
'!w-8 !opacity-100 mr-2': !isRightSidebarOpen, |
||||
}" |
||||
> |
||||
<template #title> |
||||
{{ |
||||
isRightSidebarOpen |
||||
? `${$t('general.hide')} ${$t('objects.sidebar').toLowerCase()}` |
||||
: `${$t('general.show')} ${$t('objects.sidebar').toLowerCase()}` |
||||
}} |
||||
</template> |
||||
<NcButton |
||||
type="text" |
||||
size="small" |
||||
class="nc-sidebar-right-toggle-icon !text-gray-600 !hover:text-gray-800" |
||||
:class="{ |
||||
'invisible !w-0': isRightSidebarOpen, |
||||
}" |
||||
@click="onClick" |
||||
> |
||||
<div class="flex items-center text-inherit"> |
||||
<GeneralIcon icon="doubleLeftArrow" class="duration-150 transition-all !text-lg -mt-0.25" /> |
||||
</div> |
||||
</NcButton> |
||||
</NcTooltip> |
||||
</template> |
@ -1,61 +1,42 @@
|
||||
<script setup lang="ts"> |
||||
import { NcProjectType, useRouter } from '#imports' |
||||
import type { NcButtonSize } from '~/lib' |
||||
|
||||
const props = defineProps<{ |
||||
activeWorkspaceId?: string | undefined |
||||
modal?: boolean |
||||
type?: string |
||||
isOpen: boolean |
||||
size?: NcButtonSize |
||||
centered?: boolean |
||||
}>() |
||||
|
||||
const router = useRouter() |
||||
const { isUIAllowed } = useUIPermission() |
||||
|
||||
const { orgRoles, workspaceRoles } = useRoles() |
||||
|
||||
const projectStore = useProject() |
||||
const { isSharedBase } = storeToRefs(projectStore) |
||||
|
||||
const workspaceStore = useWorkspace() |
||||
const { activeWorkspaceId: _activeWorkspaceId } = storeToRefs(workspaceStore) |
||||
|
||||
const projectCreateDlg = ref(false) |
||||
const projectType = ref(NcProjectType.DB) |
||||
|
||||
const navigateToCreateProject = (type: NcProjectType) => { |
||||
if (props.modal) { |
||||
projectType.value = type |
||||
projectCreateDlg.value = true |
||||
} else { |
||||
router.push({ |
||||
path: '/create', |
||||
query: { |
||||
type, |
||||
workspaceId: props.activeWorkspaceId, |
||||
}, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
useEventListener(document, 'keydown', async (e: KeyboardEvent) => { |
||||
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey |
||||
if (e.altKey && !e.shiftKey && !cmdOrCtrl) { |
||||
switch (e.keyCode) { |
||||
// ALT + D |
||||
case 68: { |
||||
e.stopPropagation() |
||||
navigateToCreateProject(NcProjectType.DB) |
||||
break |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
|
||||
const size = computed(() => props.size || 'small') |
||||
const centered = computed(() => props.centered ?? true) |
||||
</script> |
||||
|
||||
<template> |
||||
<div> |
||||
<a-button |
||||
class="!py-0 !px-0 !border-0 !h-full !rounded-md w-full hover:bg-gray-100 text-sm select-none cursor-pointer" |
||||
:type="props.type ?? 'primary'" |
||||
@click="navigateToCreateProject(NcProjectType.DB)" |
||||
> |
||||
<div class="flex w-full items-center gap-2"> |
||||
<slot>{{ $t('title.newProj') }} <MdiMenuDown /></slot> |
||||
</div> |
||||
</a-button> |
||||
<WorkspaceCreateProjectDlg v-model="projectCreateDlg" :type="projectType" /> |
||||
</div> |
||||
<NcButton |
||||
v-if="isUIAllowed('projectCreate', false, workspaceRoles ?? orgRoles) && !isSharedBase" |
||||
type="text" |
||||
:size="size" |
||||
:centered="centered" |
||||
@click="projectCreateDlg = true" |
||||
> |
||||
<slot /> |
||||
<WorkspaceCreateProjectDlg v-model="projectCreateDlg" /> |
||||
</NcButton> |
||||
</template> |
||||
|
||||
<style scoped></style> |
||||
|
@ -1,66 +0,0 @@
|
||||
<script lang="ts" setup> |
||||
const isConfirmed = ref(false) |
||||
const isDeleting = ref(false) |
||||
const { signOut } = useGlobal() |
||||
|
||||
const { deleteWorkspace, navigateToWorkspace } = useWorkspace() |
||||
const { workspacesList, activeWorkspaceId } = storeToRefs(useWorkspace()) |
||||
|
||||
const onDelete = async () => { |
||||
isDeleting.value = true |
||||
try { |
||||
await deleteWorkspace(activeWorkspaceId.value) |
||||
|
||||
isConfirmed.value = false |
||||
isDeleting.value = false |
||||
|
||||
if (workspacesList.value.length > 0) { |
||||
await navigateToWorkspace(workspacesList.value[0].id) |
||||
} else { |
||||
await signOut() |
||||
setTimeout(() => { |
||||
window.location.href = '/' |
||||
}, 100) |
||||
} |
||||
} finally { |
||||
isDeleting.value = false |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="flex flex-col items-center"> |
||||
<div class="item flex flex-col"> |
||||
<div class="font-medium text-base">Delete Workspace</div> |
||||
<div class="text-gray-500 mt-2">Delete this workspace and all it’s contents.</div> |
||||
<div class="flex flex-row p-4 border-1 rounded-lg gap-x-2 mt-6"> |
||||
<div class="flex"> |
||||
<GeneralIcon icon="warning" class="text-xl text-orange-600" /> |
||||
</div> |
||||
<div class="flex flex-col items-start gap-y-1"> |
||||
<div class="flex font-medium">This action is irreversible.</div> |
||||
<div class="flex flex-row text-gray-500"> |
||||
You have 31 days to undo this action by following the steps provided in recover workspace mail sent to your email. |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="flex flex-row mt-8 gap-x-2"> |
||||
<a-checkbox v-model:checked="isConfirmed" /> |
||||
<div class="flex">I understand that this action is irreversible</div> |
||||
</div> |
||||
|
||||
<div class="flex flex-row w-full justify-end mt-8"> |
||||
<NcButton type="danger" :disabled="!isConfirmed" :loading="isDeleting" @click="onDelete"> |
||||
<template #loading> Deleting Workspace </template> |
||||
Delete Workspace |
||||
</NcButton> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.item { |
||||
@apply p-6 rounded-2xl border-1 max-w-180 mt-10; |
||||
} |
||||
</style> |
@ -1,371 +1,29 @@
|
||||
<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 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> |
||||
<script lang="ts" setup></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 class="flex flex-row flex-grow pl-0.5 pr-1 py-0.5 rounded-md w-full" style="max-width: calc(100% - 2.5rem)"> |
||||
<div class="flex-grow min-w-20"> |
||||
<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" |
||||
class="flex items-center nc-workspace-menu overflow-hidden py-1.25 pr-0.25 justify-center w-full" |
||||
> |
||||
<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> |
||||
<a |
||||
class="w-10 min-w-10 transition-all duration-200 p-1 transform" |
||||
href="https://github.com/nocodb/nocodb" |
||||
target="_blank" |
||||
> |
||||
<img width="25" alt="NocoDB" src="~/assets/img/icons/256x256.png" /> |
||||
</a> |
||||
|
||||
<div class="font-semibold text-base">Nocodb</div> |
||||
<div class="flex flex-grow"></div> |
||||
</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> |
||||
</div> |
||||
</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> |
||||
|
@ -0,0 +1,164 @@
|
||||
<script lang="ts" setup> |
||||
const { signOut } = useGlobal() |
||||
|
||||
const { deleteWorkspace, navigateToWorkspace, updateWorkspace } = useWorkspace() |
||||
const { workspacesList, activeWorkspaceId, activeWorkspace, workspaces } = storeToRefs(useWorkspace()) |
||||
|
||||
const formValidator = ref() |
||||
const isConfirmed = ref(false) |
||||
const isDeleting = ref(false) |
||||
const isErrored = ref(false) |
||||
const isTitleUpdating = ref(false) |
||||
const isCancelButtonVisible = ref(false) |
||||
|
||||
const form = ref({ |
||||
title: '', |
||||
}) |
||||
|
||||
const formRules = { |
||||
title: [ |
||||
{ required: true, message: 'Workspace name required' }, |
||||
{ min: 3, message: 'Workspace name must be at least 3 characters long' }, |
||||
{ max: 50, message: 'Workspace name must be at most 50 characters long' }, |
||||
], |
||||
} |
||||
|
||||
const onDelete = async () => { |
||||
isDeleting.value = true |
||||
try { |
||||
await deleteWorkspace(activeWorkspaceId.value, { skipStateUpdate: true }) |
||||
|
||||
isConfirmed.value = false |
||||
isDeleting.value = false |
||||
|
||||
// We only remove the delete workspace from the list after the api call is successful |
||||
workspaces.value.delete(activeWorkspaceId.value) |
||||
|
||||
if (workspacesList.value.length > 1) { |
||||
await navigateToWorkspace(workspacesList.value[0].id) |
||||
} else { |
||||
// As signin page will clear the workspaces, we need to check if there are more than one workspace |
||||
await signOut(false) |
||||
setTimeout(() => { |
||||
window.location.href = '/' |
||||
}, 100) |
||||
} |
||||
} finally { |
||||
isDeleting.value = false |
||||
} |
||||
} |
||||
|
||||
const titleChange = async () => { |
||||
const valid = await formValidator.value.validate() |
||||
|
||||
if (!valid) return |
||||
|
||||
if (isTitleUpdating.value) return |
||||
|
||||
isTitleUpdating.value = true |
||||
isErrored.value = false |
||||
|
||||
try { |
||||
await updateWorkspace(activeWorkspaceId.value, { |
||||
title: form.value.title, |
||||
}) |
||||
} catch (e: any) { |
||||
console.error(e) |
||||
} finally { |
||||
isTitleUpdating.value = false |
||||
isCancelButtonVisible.value = false |
||||
} |
||||
} |
||||
|
||||
watch( |
||||
() => activeWorkspace.value.title, |
||||
() => { |
||||
form.value.title = activeWorkspace.value.title |
||||
}, |
||||
{ |
||||
immediate: true, |
||||
}, |
||||
) |
||||
|
||||
watch( |
||||
() => form.value.title, |
||||
async () => { |
||||
try { |
||||
if (form.value.title !== activeWorkspace.value?.title) { |
||||
isCancelButtonVisible.value = true |
||||
} else { |
||||
isCancelButtonVisible.value = false |
||||
} |
||||
isErrored.value = !(await formValidator.value.validate()) |
||||
} catch (e: any) { |
||||
isErrored.value = true |
||||
} |
||||
}, |
||||
) |
||||
|
||||
const onCancel = () => { |
||||
form.value.title = activeWorkspace.value?.title |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="flex flex-col items-center nc-workspace-settings-settings"> |
||||
<div class="item flex flex-col w-full"> |
||||
<div class="font-medium text-base">Change Workspace Name</div> |
||||
<a-form ref="formValidator" layout="vertical" no-style :model="form" class="w-full" @finish="titleChange"> |
||||
<div class="text-gray-500 mt-6 mb-1.5">Workspace name</div> |
||||
<a-form-item name="title" :rules="formRules.title"> |
||||
<a-input |
||||
v-model:value="form.title" |
||||
class="w-full !rounded-md !py-1.5" |
||||
placeholder="Workspace name" |
||||
data-testid="nc-workspace-settings-settings-rename-input" |
||||
/> |
||||
</a-form-item> |
||||
<div class="flex flex-row w-full justify-end mt-8 gap-4"> |
||||
<NcButton |
||||
v-if="isCancelButtonVisible" |
||||
type="secondary" |
||||
html-type="submit" |
||||
data-testid="nc-workspace-settings-settings-rename-cancel" |
||||
@click="onCancel" |
||||
> |
||||
<template #loading> Renaming Workspace </template> |
||||
Cancel |
||||
</NcButton> |
||||
<NcButton |
||||
type="primary" |
||||
html-type="submit" |
||||
:disabled="isErrored || (form.title && form.title === activeWorkspace.title)" |
||||
:loading="isDeleting" |
||||
data-testid="nc-workspace-settings-settings-rename-submit" |
||||
> |
||||
<template #loading> Renaming Workspace </template> |
||||
Rename Workspace |
||||
</NcButton> |
||||
</div> |
||||
</a-form> |
||||
</div> |
||||
<div class="item flex flex-col"> |
||||
<div class="font-medium text-base">Delete Workspace</div> |
||||
<div class="text-gray-500 mt-2">Delete this workspace and all it’s contents.</div> |
||||
<div class="flex flex-row mt-8 gap-x-2"> |
||||
<a-checkbox v-model:checked="isConfirmed" /> |
||||
<div class="flex">I understand that this action is irreversible</div> |
||||
</div> |
||||
|
||||
<div class="flex flex-row w-full justify-end mt-8"> |
||||
<NcButton type="danger" :disabled="!isConfirmed" :loading="isDeleting" @click="onDelete"> |
||||
<template #loading> Deleting Workspace </template> |
||||
Delete Workspace |
||||
</NcButton> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.item { |
||||
@apply p-6 rounded-2xl border-1 max-w-180 mt-10 min-w-100 w-full; |
||||
} |
||||
</style> |
@ -1,3 +1,11 @@
|
||||
<script lang="ts"> |
||||
export default { |
||||
name: 'EmptyLayout', |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<slot></slot> |
||||
<NuxtLayout class="h-screen"> |
||||
<slot></slot> |
||||
</NuxtLayout> |
||||
</template> |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue