After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 544 B |
After Width: | Height: | Size: 386 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,185 @@
|
||||
<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 { |
||||
isFullscreen?: boolean |
||||
} |
||||
|
||||
withDefaults(defineProps<Props>(), { |
||||
isFullscreen: true, |
||||
}) |
||||
|
||||
const { eventBus, getExtensionAssetsUrl, duplicateExtension, showExtensionDetails } = useExtensions() |
||||
|
||||
const { fullscreen, collapsed, extension, extensionManifest, activeError, showExpandBtn } = useExtensionHelperOrThrow() |
||||
|
||||
const titleInput = ref<HTMLInputElement | null>(null) |
||||
|
||||
const titleEditMode = ref<boolean>(false) |
||||
|
||||
const tempTitle = ref<string>(extension.value.title) |
||||
|
||||
const showExpandButton = computed(() => { |
||||
return showExpandBtn.value && !activeError.value |
||||
}) |
||||
|
||||
const enableEditMode = () => { |
||||
titleEditMode.value = true |
||||
tempTitle.value = extension.value.title |
||||
nextTick(() => { |
||||
titleInput.value?.focus() |
||||
titleInput.value?.select() |
||||
}) |
||||
} |
||||
|
||||
const updateExtensionTitle = async () => { |
||||
await extension.value.setTitle(tempTitle.value) |
||||
titleEditMode.value = false |
||||
} |
||||
|
||||
const expandExtension = () => { |
||||
if (!collapsed.value) return |
||||
|
||||
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 duplicatedExt = await duplicateExtension(id) |
||||
|
||||
if (duplicatedExt?.id && open) { |
||||
fullscreen.value = false |
||||
eventBus.emit(ExtensionsEvents.DUPLICATE, duplicatedExt.id) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
v-if="(isFullscreen && fullscreen) || !isFullscreen" |
||||
class="extension-header flex items-center" |
||||
:class="{ |
||||
'border-b-1 border-nc-border-gray-medium h-[49px]': !collapsed && !isFullscreen, |
||||
'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-border-gray-medium': isFullscreen, |
||||
}" |
||||
@click="expandExtension" |
||||
> |
||||
<slot v-if="isFullscreen" name="prefix"></slot> |
||||
<NcButton v-if="!isFullscreen" size="xs" type="text" class="nc-extension-drag-handler !px-1" @click.stop> |
||||
<GeneralIcon icon="ncDrag" class="flex-none text-gray-500" /> |
||||
</NcButton> |
||||
|
||||
<img |
||||
v-if="extensionManifest" |
||||
:src="getExtensionAssetsUrl(extensionManifest.iconUrl)" |
||||
alt="icon" |
||||
class="h-8 w-8 object-contain flex-none" |
||||
:class="{ |
||||
'mx-1': !isFullscreen, |
||||
}" |
||||
/> |
||||
<div |
||||
v-if="titleEditMode" |
||||
class="flex-1" |
||||
:class="{ |
||||
'mr-1': !isFullscreen, |
||||
}" |
||||
> |
||||
<a-input |
||||
ref="titleInput" |
||||
v-model:value="tempTitle" |
||||
type="text" |
||||
class="!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 |
||||
@keyup.enter="updateExtensionTitle" |
||||
@keyup.esc="updateExtensionTitle" |
||||
@blur="updateExtensionTitle" |
||||
> |
||||
</a-input> |
||||
</div> |
||||
|
||||
<NcTooltip v-else show-on-truncate-only class="truncate flex-1"> |
||||
<template #title> |
||||
{{ extension.title }} |
||||
</template> |
||||
<span |
||||
class="extension-title cursor-pointer" |
||||
:class="{ |
||||
'text-lg font-semibold ': isFullscreen, |
||||
'mr-1': !isFullscreen, |
||||
}" |
||||
@dblclick.stop="enableEditMode" |
||||
@click.stop |
||||
> |
||||
{{ extension.title }} |
||||
</span> |
||||
</NcTooltip> |
||||
<slot v-if="isFullscreen" name="extra"></slot> |
||||
<ExtensionsExtensionHeaderMenu |
||||
:is-fullscreen="isFullscreen" |
||||
class="nc-extension-menu" |
||||
@rename="enableEditMode" |
||||
@duplicate="handleDuplicateExtension(extension.id, true)" |
||||
@show-details="showExtensionDetails(extension.extensionId, 'extension')" |
||||
@clear-data="extension.clear()" |
||||
@delete="extension.delete()" |
||||
/> |
||||
|
||||
<template v-if="!isFullscreen"> |
||||
<NcButton |
||||
v-if="showExpandButton" |
||||
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" /> |
||||
</NcButton> |
||||
<NcButton size="xs" type="text" class="!px-1" @click.stop="collapsed = !collapsed"> |
||||
<GeneralIcon :icon="collapsed ? 'arrowDown' : 'arrowUp'" class="flex-none" /> |
||||
</NcButton> |
||||
</template> |
||||
<NcButton v-else :size="isFullscreen ? 'small' : 'xs'" type="text" class="flex-none !px-1" @click="fullscreen = false"> |
||||
<GeneralIcon icon="close" /> |
||||
</NcButton> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
.extension-header { |
||||
&.collapsed:not(:hover) { |
||||
.nc-extension-expand-btn, |
||||
.nc-extension-menu { |
||||
@apply hidden; |
||||
} |
||||
} |
||||
|
||||
.extension-header-left { |
||||
@apply flex-1 flex items-center gap-2; |
||||
} |
||||
|
||||
.extension-header-right { |
||||
@apply flex items-center gap-1; |
||||
} |
||||
|
||||
.extension-title { |
||||
@apply font-weight-600; |
||||
} |
||||
} |
||||
</style> |
@ -1,17 +1,19 @@
|
||||
<script setup lang="ts"> |
||||
interface Props { |
||||
fullscreen?: boolean |
||||
activeError?: boolean |
||||
isFullscreen?: boolean |
||||
} |
||||
|
||||
const { fullscreen, activeError } = defineProps<Props>() |
||||
defineProps<Props>() |
||||
|
||||
const emits = defineEmits(['rename', 'duplicate', 'showDetails', 'clearData', 'delete']) |
||||
|
||||
const { activeError } = useExtensionHelperOrThrow() |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="flex items-center"> |
||||
<div class="flex items-center" @click.stop> |
||||
<NcDropdown :trigger="['click']" placement="bottomRight"> |
||||
<NcButton type="text" :size="fullscreen ? 'small' : 'xs'" class="!px-1"> |
||||
<NcButton type="text" :size="isFullscreen ? 'small' : 'xs'" class="!px-1"> |
||||
<GeneralIcon icon="threeDotVertical" /> |
||||
</NcButton> |
||||
|
@ -0,0 +1,40 @@
|
||||
<script lang="ts" setup> |
||||
/** |
||||
* ExtensionHeaderWrapper component. |
||||
* |
||||
* @slot headerPrefix - Slot for custom content to be displayed at the start of the header when in fullscreen mode. |
||||
* @slot headerExtra - Slot for additional custom content to be displayed in the header when in fullscreen mode. |
||||
*/ |
||||
const { fullscreen } = useExtensionHelperOrThrow() |
||||
|
||||
const headerRef = ref<HTMLDivElement>() |
||||
|
||||
const { height } = useElementSize(headerRef) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="h-full"> |
||||
<div ref="headerRef" class="extension-header-wrapper"> |
||||
<ExtensionsExtensionHeader> |
||||
<template #prefix> |
||||
<slot name="headerPrefix"></slot> |
||||
</template> |
||||
<template #extra> |
||||
<slot name="headerExtra"></slot> |
||||
</template> |
||||
</ExtensionsExtensionHeader> |
||||
</div> |
||||
<div |
||||
class="extension-content-container" |
||||
:class="{ |
||||
'fullscreen p-6 nc-scrollbar-thin': fullscreen, |
||||
'h-full': !fullscreen, |
||||
}" |
||||
:style="fullscreen ? { height: height ? `calc(100% - ${height}px)` : 'calc(100% - 64px)' } : {}" |
||||
> |
||||
<slot /> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped></style> |
Before Width: | Height: | Size: 298 KiB |
Before Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 348 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 1.4 KiB |
@ -1,5 +1,16 @@
|
||||
This is a sample NocoDB extension that exports data in JSON format. |
||||
This is a sample NocoDB extension that exports data in JSON format. |
||||
It is used to demonstrate how to create a NocoDB extension. |
||||
</br> </br> |
||||
|
||||
This extension is disabled by default. To access it you need to first change the `disabled` property in the manifest file to `false`. |
||||
This extension is disabled by default. To access it you need to first change the `disabled` property in the manifest file to `false`. |
||||
</br></br> |
||||
|
||||
<!-- Todo: Add docs link --> |
||||
<a href="" target="_blank" rel="noopener noreferrer" class="!no-underline !hover:underline inline-flex items-center gap-2 "> |
||||
Learn more |
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none"> |
||||
<path d="M12 8.66667V12.6667C12 13.0203 11.8595 13.3594 11.6095 13.6095C11.3594 13.8595 11.0203 14 10.6667 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V5.33333C2 4.97971 2.14048 4.64057 2.39052 4.39052C2.64057 4.14048 2.97971 4 3.33333 4H7.33333" stroke="#374151" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> |
||||
<path d="M10 2H14V6" stroke="#374151" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> |
||||
<path d="M6.66669 9.33333L14 2" stroke="#374151" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> |
||||
</svg> |
||||
</a> |
||||
|
@ -0,0 +1,18 @@
|
||||
export const modalSizes = { |
||||
xs: { |
||||
width: 'min(calc(100vw - 32px), 448px)', |
||||
height: 'min(90vh, 448px)', |
||||
}, |
||||
sm: { |
||||
width: 'min(calc(100vw - 32px), 640px)', |
||||
height: 'min(90vh, 424px)', |
||||
}, |
||||
md: { |
||||
width: 'min(80vw, 900px)', |
||||
height: 'min(90vh, 540px)', |
||||
}, |
||||
lg: { |
||||
width: 'min(80vw, 1280px)', |
||||
height: 'min(90vh, 864px)', |
||||
}, |
||||
} |