|
|
|
@ -17,8 +17,6 @@ const {
|
|
|
|
|
|
|
|
|
|
const { $e } = useNuxtApp() |
|
|
|
|
|
|
|
|
|
const isReady = ref(false) |
|
|
|
|
|
|
|
|
|
const searchExtensionRef = ref<HTMLInputElement>() |
|
|
|
|
|
|
|
|
|
const extensionHeaderRef = ref<HTMLDivElement>() |
|
|
|
@ -27,6 +25,13 @@ const searchQuery = ref<string>('')
|
|
|
|
|
|
|
|
|
|
const showSearchBox = ref(false) |
|
|
|
|
|
|
|
|
|
const panelSize = computed(() => { |
|
|
|
|
if (isPanelExpanded.value) { |
|
|
|
|
return extensionPanelSize.value |
|
|
|
|
} |
|
|
|
|
return 0 |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const { width } = useElementSize(extensionHeaderRef) |
|
|
|
|
|
|
|
|
|
const isOpenSearchBox = computed(() => { |
|
|
|
@ -54,14 +59,6 @@ const toggleMarket = () => {
|
|
|
|
|
isMarketVisible.value = !isMarketVisible.value |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const normalizePaneMaxWidth = computed(() => { |
|
|
|
|
if (isReady.value) { |
|
|
|
|
return 60 |
|
|
|
|
} else { |
|
|
|
|
return extensionPanelSize.value |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
const onMove = async (_event: { moved: { newIndex: number; oldIndex: number; element: ExtensionType } }) => { |
|
|
|
|
let { |
|
|
|
|
moved: { newIndex = 0, oldIndex = 0, element }, |
|
|
|
@ -100,20 +97,6 @@ const onMove = async (_event: { moved: { newIndex: number; oldIndex: number; ele
|
|
|
|
|
$e('a:extension:reorder') |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
defineExpose({ |
|
|
|
|
onReady: () => { |
|
|
|
|
isReady.value = true |
|
|
|
|
}, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
watch(isPanelExpanded, (newValue) => { |
|
|
|
|
if (newValue && !isReady.value) { |
|
|
|
|
setTimeout(() => { |
|
|
|
|
isReady.value = true |
|
|
|
|
}, 300) |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
onClickOutside(searchExtensionRef, () => { |
|
|
|
|
if (searchQuery.value) { |
|
|
|
|
return |
|
|
|
@ -130,125 +113,119 @@ onMounted(() => {
|
|
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
<template> |
|
|
|
|
<Pane |
|
|
|
|
v-if="isPanelExpanded" |
|
|
|
|
:size="extensionPanelSize" |
|
|
|
|
min-size="10%" |
|
|
|
|
max-size="60%" |
|
|
|
|
class="nc-extension-pane" |
|
|
|
|
:style="{ |
|
|
|
|
minWidth: isReady ? '300px' : `${normalizePaneMaxWidth}%`, |
|
|
|
|
maxWidth: `${normalizePaneMaxWidth}%`, |
|
|
|
|
}" |
|
|
|
|
> |
|
|
|
|
<div |
|
|
|
|
ref="extensionHeaderRef" |
|
|
|
|
class="h-[var(--toolbar-height)] flex items-center gap-3 px-4 py-2 border-b-1 border-gray-200 bg-white" |
|
|
|
|
> |
|
|
|
|
<div |
|
|
|
|
class="flex items-center gap-3 font-weight-700 text-gray-700 text-base" |
|
|
|
|
:class="{ |
|
|
|
|
'flex-1': !isOpenSearchBox, |
|
|
|
|
}" |
|
|
|
|
> |
|
|
|
|
<GeneralIcon icon="ncPuzzleSolid" class="h-5 w-5 text-gray-700 opacity-85" /> |
|
|
|
|
<span v-if="!isOpenSearchBox || width >= 507">Extensions</span> |
|
|
|
|
</div> |
|
|
|
|
<div |
|
|
|
|
class="flex justify-end" |
|
|
|
|
:class="{ |
|
|
|
|
'flex-1': isOpenSearchBox, |
|
|
|
|
}" |
|
|
|
|
> |
|
|
|
|
<NcButton v-if="!isOpenSearchBox" size="xs" type="text" class="!px-1" @click="handleShowSearchInput"> |
|
|
|
|
<GeneralIcon icon="search" class="flex-none !text-gray-500" /> |
|
|
|
|
</NcButton> |
|
|
|
|
<div v-else class="flex flex-grow items-center justify-end !max-w-[300px]"> |
|
|
|
|
<a-input |
|
|
|
|
ref="searchExtensionRef" |
|
|
|
|
v-model:value="searchQuery" |
|
|
|
|
type="text" |
|
|
|
|
class="nc-input-border-on-value !h-7 !px-3 !py-1 !rounded-lg" |
|
|
|
|
placeholder="Search Extension" |
|
|
|
|
allow-clear |
|
|
|
|
@keydown.esc="handleCloseSearchbox" |
|
|
|
|
<Pane :size="panelSize" max-size="60%" class="nc-extension-pane"> |
|
|
|
|
<Transition name="layout" :duration="150"> |
|
|
|
|
<div v-if="isPanelExpanded" class="flex flex-col"> |
|
|
|
|
<div |
|
|
|
|
ref="extensionHeaderRef" |
|
|
|
|
class="h-[var(--toolbar-height)] flex items-center gap-3 px-4 py-2 border-b-1 border-gray-200 bg-white" |
|
|
|
|
> |
|
|
|
|
<div |
|
|
|
|
class="flex items-center gap-3 font-weight-700 text-gray-700 text-base" |
|
|
|
|
:class="{ |
|
|
|
|
'flex-1': !isOpenSearchBox, |
|
|
|
|
}" |
|
|
|
|
> |
|
|
|
|
<template #prefix> |
|
|
|
|
<GeneralIcon icon="search" class="mr-2 h-4 w-4 text-gray-500 group-hover:text-black" /> |
|
|
|
|
</template> |
|
|
|
|
</a-input> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
<NcButton type="secondary" size="xs" @click="toggleMarket"> |
|
|
|
|
<div class="flex items-center gap-1 text-xs max-w-full -ml-3px"> |
|
|
|
|
<GeneralIcon icon="plus" /> |
|
|
|
|
{{ $t('general.install') }} |
|
|
|
|
</div> |
|
|
|
|
</NcButton> |
|
|
|
|
</div> |
|
|
|
|
<template v-if="extensionList.length === 0"> |
|
|
|
|
<div class="flex items-center flex-col gap-4 w-full nc-scrollbar-md text-center p-4"> |
|
|
|
|
<GeneralIcon icon="ncPuzzleSolid" class="h-12 w-12 flex-none mt-[120px] text-gray-500 !stroke-transparent" /> |
|
|
|
|
|
|
|
|
|
<div class="font-weight-700 text-base">No extensions added</div> |
|
|
|
|
<div class="text-sm text-gray-700">Add Extensions from the community extensions marketplace</div> |
|
|
|
|
<NcButton size="small" @click="toggleMarket"> |
|
|
|
|
<div class="flex items-center gap-1 -ml-3px"> |
|
|
|
|
<GeneralIcon icon="plus" /> |
|
|
|
|
{{ $t('general.install') }} |
|
|
|
|
<GeneralIcon icon="ncPuzzleSolid" class="h-5 w-5 text-gray-700 opacity-85" /> |
|
|
|
|
<span v-if="!isOpenSearchBox || width >= 507">Extensions</span> |
|
|
|
|
</div> |
|
|
|
|
</NcButton> |
|
|
|
|
<!-- Todo: add docs link --> |
|
|
|
|
<NcButton size="small" type="secondary"> |
|
|
|
|
<div class="flex items-center gap-1.5"> |
|
|
|
|
<GeneralIcon icon="externalLink" /> |
|
|
|
|
{{ $t('activity.goToDocs') }} |
|
|
|
|
</div> |
|
|
|
|
</NcButton> |
|
|
|
|
</div> |
|
|
|
|
</template> |
|
|
|
|
<template v-else> |
|
|
|
|
<Draggable |
|
|
|
|
:model-value="filteredExtensionList" |
|
|
|
|
draggable=".nc-extension-item" |
|
|
|
|
item-key="id" |
|
|
|
|
handle=".nc-extension-drag-handler" |
|
|
|
|
ghost-class="ghost" |
|
|
|
|
class="nc-extension-list-wrapper flex items-center flex-col gap-3 w-full nc-scrollbar-md py-4" |
|
|
|
|
:class="{ |
|
|
|
|
'h-full': searchQuery && !filteredExtensionList.length && extensionList.length, |
|
|
|
|
}" |
|
|
|
|
@start="(e) => e.target.classList.add('grabbing')" |
|
|
|
|
@end="(e) => e.target.classList.remove('grabbing')" |
|
|
|
|
@change="onMove($event)" |
|
|
|
|
> |
|
|
|
|
<template #item="{ element: ext }"> |
|
|
|
|
<div class="nc-extension-item w-full"> |
|
|
|
|
<ExtensionsWrapper :extension-id="ext.id" /> |
|
|
|
|
<div |
|
|
|
|
class="flex justify-end" |
|
|
|
|
:class="{ |
|
|
|
|
'flex-1': isOpenSearchBox, |
|
|
|
|
}" |
|
|
|
|
> |
|
|
|
|
<NcButton v-if="!isOpenSearchBox" size="xs" type="text" class="!px-1" @click="handleShowSearchInput"> |
|
|
|
|
<GeneralIcon icon="search" class="flex-none !text-gray-500" /> |
|
|
|
|
</NcButton> |
|
|
|
|
<div v-else class="flex flex-grow items-center justify-end !max-w-[300px]"> |
|
|
|
|
<a-input |
|
|
|
|
ref="searchExtensionRef" |
|
|
|
|
v-model:value="searchQuery" |
|
|
|
|
type="text" |
|
|
|
|
class="nc-input-border-on-value !h-7 !px-3 !py-1 !rounded-lg" |
|
|
|
|
placeholder="Search Extension" |
|
|
|
|
allow-clear |
|
|
|
|
@keydown.esc="handleCloseSearchbox" |
|
|
|
|
> |
|
|
|
|
<template #prefix> |
|
|
|
|
<GeneralIcon icon="search" class="mr-2 h-4 w-4 text-gray-500 group-hover:text-black" /> |
|
|
|
|
</template> |
|
|
|
|
</a-input> |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</template> |
|
|
|
|
<template v-if="searchQuery && !filteredExtensionList.length && extensionList.length" #header> |
|
|
|
|
<div class="w-full h-full flex-1 flex items-center justify-center"> |
|
|
|
|
<div class="pb-6 text-gray-500 flex flex-col items-center gap-6 text-center"> |
|
|
|
|
<img |
|
|
|
|
src="~assets/img/placeholder/no-search-result-found.png" |
|
|
|
|
class="!w-[164px] flex-none" |
|
|
|
|
alt="No search results found" |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
{{ $t('title.noResultsMatchedYourSearch') }} |
|
|
|
|
<NcButton type="secondary" size="xs" @click="toggleMarket"> |
|
|
|
|
<div class="flex items-center gap-1 text-xs max-w-full -ml-3px"> |
|
|
|
|
<GeneralIcon icon="plus" /> |
|
|
|
|
{{ $t('general.install') }} |
|
|
|
|
</div> |
|
|
|
|
</NcButton> |
|
|
|
|
</div> |
|
|
|
|
<template v-if="extensionList.length === 0"> |
|
|
|
|
<div class="flex items-center flex-col gap-4 w-full nc-scrollbar-md text-center p-4"> |
|
|
|
|
<GeneralIcon icon="ncPuzzleSolid" class="h-12 w-12 flex-none mt-[120px] text-gray-500 !stroke-transparent" /> |
|
|
|
|
|
|
|
|
|
<div class="font-weight-700 text-base">No extensions added</div> |
|
|
|
|
<div class="text-sm text-gray-700">Add Extensions from the community extensions marketplace</div> |
|
|
|
|
<NcButton size="small" @click="toggleMarket"> |
|
|
|
|
<div class="flex items-center gap-1 -ml-3px"> |
|
|
|
|
<GeneralIcon icon="plus" /> |
|
|
|
|
{{ $t('general.install') }} |
|
|
|
|
</div> |
|
|
|
|
</NcButton> |
|
|
|
|
<!-- Todo: add docs link --> |
|
|
|
|
<NcButton size="small" type="secondary"> |
|
|
|
|
<div class="flex items-center gap-1.5"> |
|
|
|
|
<GeneralIcon icon="externalLink" /> |
|
|
|
|
{{ $t('activity.goToDocs') }} |
|
|
|
|
</div> |
|
|
|
|
</NcButton> |
|
|
|
|
</div> |
|
|
|
|
</template> |
|
|
|
|
</Draggable> |
|
|
|
|
</template> |
|
|
|
|
<ExtensionsMarket v-if="isMarketVisible" v-model="isMarketVisible" /> |
|
|
|
|
<ExtensionsDetails |
|
|
|
|
v-if="isDetailsVisible && detailsExtensionId" |
|
|
|
|
v-model="isDetailsVisible" |
|
|
|
|
:extension-id="detailsExtensionId" |
|
|
|
|
:from="detailsFrom" |
|
|
|
|
/> |
|
|
|
|
<template v-else> |
|
|
|
|
<Draggable |
|
|
|
|
:model-value="filteredExtensionList" |
|
|
|
|
draggable=".nc-extension-item" |
|
|
|
|
item-key="id" |
|
|
|
|
handle=".nc-extension-drag-handler" |
|
|
|
|
ghost-class="ghost" |
|
|
|
|
class="nc-extension-list-wrapper flex items-center flex-col gap-3 w-full nc-scrollbar-md py-4" |
|
|
|
|
:class="{ |
|
|
|
|
'h-full': searchQuery && !filteredExtensionList.length && extensionList.length, |
|
|
|
|
}" |
|
|
|
|
@start="(e) => e.target.classList.add('grabbing')" |
|
|
|
|
@end="(e) => e.target.classList.remove('grabbing')" |
|
|
|
|
@change="onMove($event)" |
|
|
|
|
> |
|
|
|
|
<template #item="{ element: ext }"> |
|
|
|
|
<div class="nc-extension-item w-full"> |
|
|
|
|
<ExtensionsWrapper :extension-id="ext.id" /> |
|
|
|
|
</div> |
|
|
|
|
</template> |
|
|
|
|
<template v-if="searchQuery && !filteredExtensionList.length && extensionList.length" #header> |
|
|
|
|
<div class="w-full h-full flex-1 flex items-center justify-center"> |
|
|
|
|
<div class="pb-6 text-gray-500 flex flex-col items-center gap-6 text-center"> |
|
|
|
|
<img |
|
|
|
|
src="~assets/img/placeholder/no-search-result-found.png" |
|
|
|
|
class="!w-[164px] flex-none" |
|
|
|
|
alt="No search results found" |
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
{{ $t('title.noResultsMatchedYourSearch') }} |
|
|
|
|
</div> |
|
|
|
|
</div> |
|
|
|
|
</template> |
|
|
|
|
</Draggable> |
|
|
|
|
</template> |
|
|
|
|
<ExtensionsMarket v-if="isMarketVisible" v-model="isMarketVisible" /> |
|
|
|
|
<ExtensionsDetails |
|
|
|
|
v-if="isDetailsVisible && detailsExtensionId" |
|
|
|
|
v-model="isDetailsVisible" |
|
|
|
|
:extension-id="detailsExtensionId" |
|
|
|
|
:from="detailsFrom" |
|
|
|
|
/> |
|
|
|
|
</div> |
|
|
|
|
</Transition> |
|
|
|
|
</Pane> |
|
|
|
|
</template> |
|
|
|
|
|
|
|
|
|