多维表格
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.
 
 
 
 
 
 

241 lines
7.8 KiB

<script setup lang="ts">
interface Prop {
extensionId: string
error?: any
}
const { extensionId, error } = defineProps<Prop>()
const { extensionList, extensionsLoaded, availableExtensions, getExtensionIcon, duplicateExtension, showExtensionDetails } =
useExtensions()
const activeError = ref(error)
const extensionModalRef = ref<HTMLElement>()
const extension = computed(() => {
const ext = extensionList.value.find((ext) => ext.id === extensionId)
if (!ext) {
throw new Error('Extension not found')
}
return ext
})
const titleInput = ref<HTMLInputElement | null>(null)
const titleEditMode = ref<boolean>(false)
const tempTitle = ref<string>(extension.value.title)
const enableEditMode = () => {
titleEditMode.value = true
tempTitle.value = extension.value.title
nextTick(() => {
titleInput.value?.focus()
titleInput.value?.select()
titleInput.value?.scrollIntoView()
})
}
const updateExtensionTitle = async () => {
await extension.value.setTitle(tempTitle.value)
titleEditMode.value = false
}
const { fullscreen, collapsed } = useProvideExtensionHelper(extension)
const component = ref<any>(null)
const extensionManifest = ref<any>(null)
onMounted(() => {
until(extensionsLoaded)
.toMatch((v) => v)
.then(() => {
extensionManifest.value = availableExtensions.value.find((ext) => ext.id === extension.value.extensionId)
if (!extensionManifest) {
return
}
import(`../../extensions/${extensionManifest.value.entry}/index.vue`).then((mod) => {
component.value = markRaw(mod.default)
})
})
.catch((err) => {
if (!extensionManifest.value) {
activeError.value = 'There was an error loading the extension'
return
}
activeError.value = err
})
})
// close fullscreen on escape key press
useEventListener('keydown', (e) => {
if (e.key === 'Escape') {
fullscreen.value = false
}
})
// close fullscreen on clicking extensionModalRef directly
const closeFullscreen = (e: MouseEvent) => {
if (e.target === extensionModalRef.value) {
fullscreen.value = false
}
}
</script>
<template>
<div class="w-full px-4">
<div class="extension-wrapper">
<div class="extension-header" :class="{ 'mb-2': !collapsed }">
<div class="extension-header-left">
<GeneralIcon icon="ncDrag" class="flex-none" />
<img v-if="extensionManifest" :src="getExtensionIcon(extensionManifest.iconUrl)" alt="icon" class="h-6 w-6 object-contain" />
<input
v-if="titleEditMode"
ref="titleInput"
v-model="tempTitle"
class="flex-grow leading-1 outline-0 ring-none capitalize !text-inherit !bg-transparent w-4/5"
@click.stop
@keyup.enter="updateExtensionTitle"
@keyup.esc="updateExtensionTitle"
@blur="updateExtensionTitle"
/>
<NcTooltip v-else show-on-truncate-only class="truncate">
<template #title>
{{ extension.title }}
</template>
<div class="extension-title truncate" @dblclick="enableEditMode">{{ extension.title }}</div>
</NcTooltip>
</div>
<div class="extension-header-right">
<NcButton v-if="!activeError && !collapsed" type="text" size="xxsmall" @click="fullscreen = true">
<GeneralIcon icon="expand" />
</NcButton>
<NcDropdown v-if="!collapsed" :trigger="['click']">
<NcButton type="text" size="xxsmall">
<GeneralIcon icon="threeDotVertical" />
</NcButton>
<template #overlay>
<NcMenu>
<template v-if="!activeError">
<NcMenuItem data-rec="true" class="!hover:text-primary" @click="enableEditMode">
<GeneralIcon icon="edit" />
Rename
</NcMenuItem>
<NcMenuItem data-rec="true" class="!hover:text-primary" @click="duplicateExtension(extension.id)">
<GeneralIcon icon="duplicate" />
Duplicate
</NcMenuItem>
<NcMenuItem
data-rec="true"
class="!hover:text-primary"
@click="showExtensionDetails(extension.extensionId, 'extension')"
>
<GeneralIcon icon="info" />
Details
</NcMenuItem>
<NcDivider />
</template>
<NcMenuItem data-rec="true" class="!text-red-500 !hover:bg-red-50" @click="extension.clear()">
<GeneralIcon icon="reload" />
Clear Data
</NcMenuItem>
<NcMenuItem data-rec="true" class="!text-red-500 !hover:bg-red-50" @click="extension.delete()">
<GeneralIcon icon="delete" />
Delete
</NcMenuItem>
</NcMenu>
</template>
</NcDropdown>
<GeneralIcon v-if="collapsed" icon="arrowUp" @click="collapsed = !collapsed" class="cursor-pointer" />
<GeneralIcon v-else icon="arrowDown" @click="collapsed = !collapsed" class="cursor-pointer" />
</div>
</div>
<template v-if="activeError">
<div v-show="!collapsed" class="extension-content">
<a-result status="error" title="Extension Error">
<template #subTitle>{{ activeError }}</template>
<template #extra>
<NcButton @click="extension.clear()">
<div class="flex items-center gap-2">
<GeneralIcon icon="reload" />
Clear Data
</div>
</NcButton>
<NcButton type="danger" @click="extension.delete()">
<div class="flex items-center gap-2">
<GeneralIcon icon="delete" />
Delete
</div>
</NcButton>
</template>
</a-result>
</div>
</template>
<template v-else>
<Teleport to="body" :disabled="!fullscreen">
<div ref="extensionModalRef" :class="{ 'extension-modal': fullscreen }" @click="closeFullscreen">
<div :class="{ 'extension-modal-content': fullscreen }">
<div
v-if="fullscreen"
class="flex items-center justify-between p-2 bg-gray-100 rounded-t-lg cursor-default h-[40px]"
>
<div class="flex items-center gap-2 text-gray-500 font-weight-600">
<img v-if="extensionManifest" :src="getExtensionIcon(extensionManifest.iconUrl)" alt="icon" class="w-6 h-6" />
<div class="text-sm">{{ extension.title }}</div>
</div>
<GeneralIcon class="cursor-pointer" icon="close" @click="fullscreen = false" />
</div>
<div
v-show="fullscreen || !collapsed"
class="extension-content"
:class="{ 'border-1': !fullscreen, 'h-[calc(100%-40px)]': fullscreen }"
>
<component :is="component" :key="extension.uiKey" />
</div>
</div>
</div>
</Teleport>
</template>
</div>
</div>
</template>
<style scoped lang="scss">
.extension-wrapper {
@apply bg-white rounded-xl px-3 py-[11px] w-full border-1;
}
.extension-header {
@apply flex justify-between;
.extension-header-left {
@apply flex items-center gap-2;
}
.extension-header-right {
@apply flex items-center gap-3;
}
.extension-title {
@apply font-weight-600;
}
}
.extension-content {
@apply rounded-lg;
}
.extension-modal {
@apply absolute top-0 left-0 z-50 w-full h-full bg-black bg-opacity-50;
.extension-modal-content {
@apply bg-white rounded-lg w-[90%] h-[90vh] mt-[5vh] mx-auto;
}
}
</style>