feat: add export to topics dropdown
This commit is contained in:
@@ -2,12 +2,17 @@
|
||||
import RowVirtualizerFixed from '~/components/RowVirtualizerFixed.vue';
|
||||
import { useFloating, offset, flip, shift, autoUpdate, hide } from '@floating-ui/vue';
|
||||
import { DialogType } from '~/composables/useDialog';
|
||||
import { exportTopicToJson, exportTopicToMarkdown } from '~~/utils/export';
|
||||
|
||||
const { openDialog } = useDialog();
|
||||
const dropdownOpen = ref(false);
|
||||
const dropdownTrigger = ref<HTMLElement | null>(null);
|
||||
const dropdownContent = ref(null);
|
||||
const activeMenuTopicId = ref<string | null>(null);
|
||||
const exportSubmenuOpen = ref(false);
|
||||
const exportTrigger = ref<HTMLElement | null>(null);
|
||||
const exportContent = ref(null);
|
||||
const exportInProgress = ref(false);
|
||||
|
||||
const route = useRoute();
|
||||
const { getAgent, patchTopicLocally, deleteTopic: deleteAgentTopic } = await useAgents();
|
||||
@@ -34,16 +39,51 @@ const { floatingStyles, placement, middlewareData } = useFloating(dropdownTrigge
|
||||
transform: false,
|
||||
});
|
||||
|
||||
const {
|
||||
floatingStyles: exportFloatingStyles,
|
||||
placement: exportPlacement,
|
||||
middlewareData: exportMiddlewareData,
|
||||
} = useFloating(exportTrigger, exportContent, {
|
||||
placement: 'right-start',
|
||||
whileElementsMounted: autoUpdate,
|
||||
middleware: [offset(6), flip({ fallbackPlacements: ['left-start', 'right-end', 'left-end'] }), shift({ padding: 10 }), hide()],
|
||||
transform: false,
|
||||
});
|
||||
|
||||
const transformOrigin = computed(() =>
|
||||
placement.value.startsWith('top')
|
||||
? 'transform-origin-bottom-center'
|
||||
: 'transform-origin-top-center'
|
||||
);
|
||||
|
||||
const exportTransformOrigin = computed(() => {
|
||||
switch (exportPlacement.value.split('-')[0]) {
|
||||
case 'left':
|
||||
return 'transform-origin-right-center';
|
||||
case 'right':
|
||||
return 'transform-origin-left-center';
|
||||
case 'top':
|
||||
return 'transform-origin-bottom-center';
|
||||
case 'bottom':
|
||||
return 'transform-origin-top-center';
|
||||
default:
|
||||
return 'transform-origin-left-center';
|
||||
}
|
||||
});
|
||||
|
||||
const closeExportSubmenu = () => {
|
||||
exportSubmenuOpen.value = false;
|
||||
};
|
||||
|
||||
const closeDropdown = () => {
|
||||
dropdownOpen.value = false;
|
||||
activeMenuTopicId.value = null;
|
||||
dropdownTrigger.value = null;
|
||||
closeExportSubmenu();
|
||||
};
|
||||
|
||||
const openExportSubmenu = () => {
|
||||
exportSubmenuOpen.value = true;
|
||||
};
|
||||
|
||||
// Computed to get the topic data for the currently open menu
|
||||
@@ -51,6 +91,35 @@ const menuTopic = computed(() =>
|
||||
topics.value.find(t => t.id === activeMenuTopicId.value)
|
||||
);
|
||||
|
||||
const handleExport = async (format: 'json' | 'markdown') => {
|
||||
if (!menuTopic.value || exportInProgress.value) return;
|
||||
|
||||
exportInProgress.value = true;
|
||||
try {
|
||||
if (format === 'json') {
|
||||
await exportTopicToJson(menuTopic.value.id);
|
||||
} else {
|
||||
await exportTopicToMarkdown(menuTopic.value.id);
|
||||
}
|
||||
} finally {
|
||||
exportInProgress.value = false;
|
||||
closeDropdown();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDropdownClickOutside = (event?: Event) => {
|
||||
const target = event?.target as Node | null;
|
||||
if (target) {
|
||||
if (exportContent.value && (exportContent.value as HTMLElement).contains(target)) {
|
||||
return;
|
||||
}
|
||||
if (exportTrigger.value && exportTrigger.value.contains(target)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
closeDropdown();
|
||||
};
|
||||
|
||||
const topicsOpen = ref(true);
|
||||
|
||||
const autoRenameTopic = async (topicId: string) => {
|
||||
@@ -135,6 +204,7 @@ const handleNavClick = (e: MouseEvent) => {
|
||||
activeMenuTopicId.value = topicId;
|
||||
dropdownTrigger.value = trigger;
|
||||
dropdownOpen.value = true;
|
||||
closeExportSubmenu();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -211,36 +281,49 @@ onMounted(() => {
|
||||
leave-active-class="transition-[opacity,transform] duration-100 ease-[cubic-bezier(0.5,_1,_0.89,_1)]"
|
||||
leave-from-class="opacity-100 scale-100 translate-y-0"
|
||||
leave-to-class="opacity-0 scale-95 translate-y-1">
|
||||
<div v-if="dropdownOpen" ref="dropdownContent" :style="{
|
||||
<div v-if="dropdownOpen" ref="dropdownContent" v-click-outside="handleDropdownClickOutside" :style="{
|
||||
...floatingStyles,
|
||||
visibility: middlewareData.hide?.referenceHidden
|
||||
? 'hidden'
|
||||
: 'visible',
|
||||
}" class="fixed z-20" :class="transformOrigin">
|
||||
<div v-click-outside="closeDropdown"
|
||||
<div
|
||||
class="bg-[var(--bg-surface)] border border-[var(--color-border)] rounded-xl p-1.5 shadow-xl flex flex-col gap-1 min-w-40">
|
||||
|
||||
|
||||
<!-- Dynamic content based on menuTopic -->
|
||||
<template v-if="menuTopic">
|
||||
<button v-if="menuTopic.renaming" @click="cancelAutoRename(menuTopic.id); closeDropdown()"
|
||||
@mouseenter="closeExportSubmenu"
|
||||
class="text-left px-3 py-1.5 text-sm rounded-lg @hover:bg-[var(--color-hover)] transition-colors disabled:opacity-50">
|
||||
Cancel Auto Rename
|
||||
</button>
|
||||
<button v-else @click="autoRenameTopic(menuTopic.id); closeDropdown()"
|
||||
@mouseenter="closeExportSubmenu"
|
||||
class="text-left px-3 py-1.5 text-sm rounded-lg @hover:bg-[var(--color-hover)] transition-colors disabled:opacity-50">
|
||||
Auto Rename
|
||||
</button>
|
||||
|
||||
<button :disabled="menuTopic.renaming ?? false"
|
||||
@click="startRename(menuTopic.id, menuTopic.name); closeDropdown()"
|
||||
@mouseenter="closeExportSubmenu"
|
||||
class="text-left px-3 py-1.5 text-sm rounded-lg @hover:bg-[var(--color-hover)] transition-colors disabled:opacity-50">
|
||||
Rename
|
||||
</button>
|
||||
|
||||
<button ref="exportTrigger" @click.stop="exportSubmenuOpen = !exportSubmenuOpen"
|
||||
@mouseenter="openExportSubmenu"
|
||||
class="text-left px-3 py-1.5 text-sm rounded-lg @hover:bg-[var(--color-hover)] transition-colors disabled:opacity-50 flex items-center justify-between gap-3"
|
||||
:class="{ 'bg-[var(--color-hover)]': exportSubmenuOpen }"
|
||||
:disabled="exportInProgress">
|
||||
<span>Export</span>
|
||||
<span class="i-mynaui-chevron-right text-4 shrink-0"
|
||||
:class="exportPlacement.startsWith('left') ? 'rotate-180' : ''"></span>
|
||||
</button>
|
||||
|
||||
<div class="h-px bg-[var(--color-border)] my-1" />
|
||||
|
||||
<button @click="deleteTopic(menuTopic.id); closeDropdown()"
|
||||
@mouseenter="closeExportSubmenu"
|
||||
class="text-left px-3 py-1.5 text-sm rounded-lg @hover:bg-[var(--color-hover)] transition-colors disabled:opacity-50 text-red-500">
|
||||
Delete
|
||||
</button>
|
||||
@@ -248,6 +331,32 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<Transition
|
||||
enter-active-class="transition-[opacity,transform] duration-150 ease-[cubic-bezier(0.5,_1,_0.89,_1)]"
|
||||
enter-from-class="opacity-0 scale-95" enter-to-class="opacity-100 scale-100"
|
||||
leave-active-class="transition-[opacity,transform] duration-100 ease-[cubic-bezier(0.5,_1,_0.89,_1)]"
|
||||
leave-from-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-95">
|
||||
<div v-if="dropdownOpen && exportSubmenuOpen" ref="exportContent" :style="{
|
||||
...exportFloatingStyles,
|
||||
visibility: exportMiddlewareData.hide?.referenceHidden
|
||||
? 'hidden'
|
||||
: 'visible',
|
||||
}" class="fixed z-30" :class="exportTransformOrigin"
|
||||
@mouseenter="openExportSubmenu">
|
||||
<div
|
||||
class="bg-[var(--bg-surface)] border border-[var(--color-border)] rounded-xl p-1.5 shadow-xl flex flex-col gap-1 min-w-44">
|
||||
<button :disabled="exportInProgress" @click="handleExport('json')"
|
||||
class="text-left px-3 py-1.5 text-sm rounded-lg @hover:bg-[var(--color-hover)] transition-colors disabled:opacity-50 whitespace-nowrap">
|
||||
Export to JSON
|
||||
</button>
|
||||
<button :disabled="exportInProgress" @click="handleExport('markdown')"
|
||||
class="text-left px-3 py-1.5 text-sm rounded-lg @hover:bg-[var(--color-hover)] transition-colors disabled:opacity-50 whitespace-nowrap">
|
||||
Export to Markdown
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</nav>
|
||||
</template>
|
||||
Reference in New Issue
Block a user