Browse Source

feat(nc-gui): customizable extension header

pull/9351/head
Ramesh Mane 3 months ago
parent
commit
d09e2785eb
  1. 50
      packages/nc-gui/components/extensions/Extension.vue
  2. 69
      packages/nc-gui/components/extensions/Extension/Header.vue
  3. 12
      packages/nc-gui/components/extensions/Extension/HeaderMenu.vue
  4. 30
      packages/nc-gui/components/extensions/Extension/Wrapper.vue
  5. 2
      packages/nc-gui/extensions/data-exporter/index.vue
  6. 2
      packages/nc-gui/extensions/json-exporter/index.vue

50
packages/nc-gui/components/extensions/Extension.vue

@ -222,56 +222,10 @@ eventBus.on((event, payload) => {
: {} : {}
" "
> >
<div
v-if="fullscreen"
class="flex items-center gap-3 cursor-default px-4 pt-4 pb-[15px] border-b-1 border-nc-gray-medium"
>
<img
v-if="extensionManifest"
:src="getExtensionAssetsUrl(extensionManifest.iconUrl)"
alt="icon"
class="flex-none w-8 h-8"
/>
<div v-if="titleEditMode" class="flex-1 flex">
<a-input
ref="titleInput"
v-model:value="tempTitle"
type="text"
class="flex-1 flex-grow !h-8 !px-1 !py-1 !-ml-1 !rounded-lg !text-lg font-semibold extension-title max-w-[420px]"
@click.stop
@keyup.enter.stop="updateExtensionTitle"
@keyup.esc.stop="updateExtensionTitle"
@blur="updateExtensionTitle"
>
</a-input>
</div>
<NcTooltip v-else show-on-truncate-only class="extension-title truncate text-lg flex-1">
<template #title>
{{ extension.title }}
</template>
<span class="cursor-pointer text-gray-800 font-semibold" @dblclick="enableEditMode">
{{ extension.title }}
</span>
</NcTooltip>
<ExtensionsExtensionHeaderMenu
:active-error="activeError"
:fullscreen="fullscreen"
@rename="enableEditMode"
@duplicate="handleDuplicateExtension(extension.id, true)"
@show-details="showExtensionDetails(extension.extensionId, 'extension')"
@clear-data="extension.clear()"
@delete="extension.delete()"
/>
<NcButton size="xs" type="text" class="flex-none !px-1" @click="fullscreen = false">
<GeneralIcon icon="close" />
</NcButton>
</div>
<div <div
v-show="fullscreen || !collapsed" v-show="fullscreen || !collapsed"
class="extension-content" class="extension-content h-full"
:class="{ 'fullscreen h-[calc(100%-40px)] p-6': fullscreen, 'h-full': !fullscreen }" :class="{ 'fullscreen': fullscreen, 'h-full': !fullscreen }"
> >
<component :is="component" :key="extension.uiKey" class="h-full" /> <component :is="component" :key="extension.uiKey" class="h-full" />
</div> </div>

69
packages/nc-gui/components/extensions/Extension/Header.vue

@ -1,4 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
/**
* ExtensionHeader component.
*
* @slot prefix - Slot for custom content to be displayed at the start of the header when in fullscreen mode.
* @slot extra - Slot for additional custom content to be displayed before the options and close button in fullscreen mode.
*/
interface Props { interface Props {
isFullscreen?: boolean isFullscreen?: boolean
} }
@ -36,6 +43,12 @@ const expandExtension = () => {
collapsed.value = false collapsed.value = false
} }
/**
* Handles the duplication of an extension.
*
* @param id - The ID of the extension to duplicate.
* @param open - Optional. If true, the duplicated extension will be opened.
*/
const handleDuplicateExtension = async (id: string, open: boolean = false) => { const handleDuplicateExtension = async (id: string, open: boolean = false) => {
const duplicatedExt = await duplicateExtension(id) const duplicatedExt = await duplicateExtension(id)
@ -49,17 +62,18 @@ const handleDuplicateExtension = async (id: string, open: boolean = false) => {
<template> <template>
<div <div
class="extension-header px-3 py-2" v-if="(isFullscreen && fullscreen) || !isFullscreen"
class="extension-header flex items-center"
:class="{ :class="{
'border-b-1 border-gray-200 h-[49px]': !collapsed, 'border-b-1 border-nc-gray-medium h-[49px]': !collapsed && !isFullscreen,
'collapsed border-transparent h-[48px]': collapsed, 'collapsed border-transparent h-[48px]': collapsed && !isFullscreen,
'px-3 py-2 gap-1': !isFullscreen,
'gap-3 px-4 pt-4 pb-[15px] border-b-1 border-nc-gray-medium': isFullscreen,
}" }"
@click="expandExtension" @click="expandExtension"
> >
<div class="extension-header-left max-w-[calc(100%_-_100px)]"> <slot v-if="isFullscreen" name="prefix"></slot>
<!-- Todo: enable later when we support extension reordering --> <NcButton v-if="!isFullscreen" size="xs" type="text" class="nc-extension-drag-handler !px-1" @click.stop>
<!-- eslint-disable vue/no-constant-condition -->
<NcButton size="xs" type="text" class="nc-extension-drag-handler !px-1" @click.stop>
<GeneralIcon icon="ncDrag" class="flex-none text-gray-500" /> <GeneralIcon icon="ncDrag" class="flex-none text-gray-500" />
</NcButton> </NcButton>
@ -67,33 +81,44 @@ const handleDuplicateExtension = async (id: string, open: boolean = false) => {
v-if="extensionManifest" v-if="extensionManifest"
:src="getExtensionAssetsUrl(extensionManifest.iconUrl)" :src="getExtensionAssetsUrl(extensionManifest.iconUrl)"
alt="icon" alt="icon"
class="h-8 w-8 object-contain" class="h-8 w-8 object-contain flex-none"
/> />
<div v-if="titleEditMode" class="flex-1">
<a-input <a-input
v-if="titleEditMode && !fullscreen"
ref="titleInput" ref="titleInput"
v-model:value="tempTitle" v-model:value="tempTitle"
type="text" type="text"
class="flex-grow !h-8 !px-1 !py-1 !-ml-1 !rounded-lg w-4/5 extension-title" class="flex-1 !h-8 !px-1 !py-1 !-ml-1 !rounded-lg extension-title"
:class="{
'w-4/5': !isFullscreen,
'!text-lg !font-semibold max-w-[420px]': isFullscreen,
}"
@click.stop @click.stop
@keyup.enter="updateExtensionTitle" @keyup.enter="updateExtensionTitle"
@keyup.esc="updateExtensionTitle" @keyup.esc="updateExtensionTitle"
@blur="updateExtensionTitle" @blur="updateExtensionTitle"
> >
</a-input> </a-input>
</div>
<NcTooltip v-else show-on-truncate-only class="truncate"> <NcTooltip v-else show-on-truncate-only class="truncate flex-1">
<template #title> <template #title>
{{ extension.title }} {{ extension.title }}
</template> </template>
<span class="extension-title cursor-pointer" @dblclick.stop="enableEditMode" @click.stop> <span
class="extension-title cursor-pointer"
:class="{
'text-lg font-semibold ': isFullscreen,
}"
@dblclick.stop="enableEditMode"
@click.stop
>
{{ extension.title }} {{ extension.title }}
</span> </span>
</NcTooltip> </NcTooltip>
</div> <slot v-if="isFullscreen" name="extra"></slot>
<div class="extension-header-right" @click.stop>
<ExtensionsExtensionHeaderMenu <ExtensionsExtensionHeaderMenu
:active-error="activeError" :is-fullscreen="isFullscreen"
class="nc-extension-menu" class="nc-extension-menu"
@rename="enableEditMode" @rename="enableEditMode"
@duplicate="handleDuplicateExtension(extension.id, true)" @duplicate="handleDuplicateExtension(extension.id, true)"
@ -101,20 +126,24 @@ const handleDuplicateExtension = async (id: string, open: boolean = false) => {
@clear-data="extension.clear()" @clear-data="extension.clear()"
@delete="extension.delete()" @delete="extension.delete()"
/> />
<NcButton v-if="!activeError" type="text" size="xs" class="nc-extension-expand-btn !px-1" @click="fullscreen = true"> <template v-if="!isFullscreen">
<NcButton v-if="!activeError" size="xs" type="text" class="nc-extension-expand-btn !px-1" @click.stop="fullscreen = true">
<GeneralIcon icon="ncMaximize2" class="h-3.5 w-3.5" /> <GeneralIcon icon="ncMaximize2" class="h-3.5 w-3.5" />
</NcButton> </NcButton>
<NcButton size="xs" type="text" class="!px-1" @click="collapsed = !collapsed"> <NcButton size="xs" type="text" class="!px-1" @click.stop="collapsed = !collapsed">
<GeneralIcon :icon="collapsed ? 'arrowDown' : 'arrowUp'" class="flex-none" /> <GeneralIcon :icon="collapsed ? 'arrowDown' : 'arrowUp'" class="flex-none" />
</NcButton> </NcButton>
</div> </template>
<template v-else>
<NcButton :size="isFullscreen ? 'small' : 'xs'" type="text" class="flex-none !px-1" @click="fullscreen = false">
<GeneralIcon icon="close" />
</NcButton>
</template>
</div> </div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.extension-header { .extension-header {
@apply flex justify-between;
&.collapsed:not(:hover) { &.collapsed:not(:hover) {
.nc-extension-expand-btn, .nc-extension-expand-btn,
.nc-extension-menu { .nc-extension-menu {

12
packages/nc-gui/components/extensions/Extension/HeaderMenu.vue

@ -1,17 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
interface Props { interface Props {
fullscreen?: boolean isFullscreen?: boolean
activeError?: boolean
} }
const { fullscreen, activeError } = defineProps<Props>() defineProps<Props>()
const { activeError } = useExtensionHelperOrThrow()
const emits = defineEmits(['rename', 'duplicate', 'showDetails', 'clearData', 'delete']) const emits = defineEmits(['rename', 'duplicate', 'showDetails', 'clearData', 'delete'])
</script> </script>
<template> <template>
<div class="flex items-center"> <div class="flex items-center" @click.stop>
<NcDropdown :trigger="['click']" placement="bottomRight"> <NcDropdown :trigger="['click']" placement="bottomRight">
<NcButton type="text" size="xs" class="!px-1"> <NcButton type="text" :size="isFullscreen ? 'small' : 'xs'" class="!px-1">
<GeneralIcon icon="threeDotVertical" /> <GeneralIcon icon="threeDotVertical" />
</NcButton> </NcButton>

30
packages/nc-gui/components/extensions/Extension/Wrapper.vue

@ -0,0 +1,30 @@
<script lang="ts" setup>
const { fullscreen } = useExtensionHelperOrThrow()
const headerRef = ref<HTMLDivElement>()
const { height } = useElementSize(headerRef)
</script>
<template>
<div>
<div ref="headerRef">
<ExtensionsExtensionHeader>
<template #extra>
<slot name="headerExtra"></slot>
</template>
</ExtensionsExtensionHeader>
</div>
<div
:class="{
'p-6 max': fullscreen,
'h-full': !fullscreen,
}"
:style="fullscreen ? { height: height ? `calc(100% - ${height}px)` : 'calc(100% - 64px)' } : {}"
>
<slot />
</div>
</div>
</template>
<style lang="scss" scoped></style>

2
packages/nc-gui/extensions/data-exporter/index.vue

@ -203,6 +203,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<ExtensionsExtensionWrapper>
<div ref="dataExporterRef" class="data-exporter"> <div ref="dataExporterRef" class="data-exporter">
<div class="pb-3 flex items-center justify-between gap-2.5 flex-wrap"> <div class="pb-3 flex items-center justify-between gap-2.5 flex-wrap">
<div <div
@ -383,6 +384,7 @@ onMounted(() => {
<div v-else class="px-3 py-2 flex-1 flex items-center justify-center text-gray-600">No exports</div> <div v-else class="px-3 py-2 flex-1 flex items-center justify-center text-gray-600">No exports</div>
</div> </div>
</div> </div>
</ExtensionsExtensionWrapper>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>

2
packages/nc-gui/extensions/json-exporter/index.vue

@ -92,11 +92,13 @@ onMounted(() => {
</script> </script>
<template> <template>
<ExtensionsExtensionWrapper>
<div class="flex flex-col gap-2 p-2 border-1 rounded-lg"> <div class="flex flex-col gap-2 p-2 border-1 rounded-lg">
<NcSelect v-model:value="exportPayload.tableId" :options="tableList" placeholder="-select table-" @change="onTableSelect" /> <NcSelect v-model:value="exportPayload.tableId" :options="tableList" placeholder="-select table-" @change="onTableSelect" />
<NcSelect v-model:value="exportPayload.viewId" :options="viewList" placeholder="-select view-" @change="onViewSelect" /> <NcSelect v-model:value="exportPayload.viewId" :options="viewList" placeholder="-select view-" @change="onViewSelect" />
<NcButton @click="exportJson">Export</NcButton> <NcButton @click="exportJson">Export</NcButton>
</div> </div>
</ExtensionsExtensionWrapper>
</template> </template>
<style lang="scss"></style> <style lang="scss"></style>

Loading…
Cancel
Save