Update SendFilesModal.vue

This commit is contained in:
Baobhan Sith
2025-05-16 21:59:42 +08:00
parent 15cbf53a42
commit f44bbe3ed5
@@ -69,21 +69,33 @@
<p>{{ t('sendFilesModal.noConnectionsFound') }}</p> <p>{{ t('sendFilesModal.noConnectionsFound') }}</p>
</div> </div>
<div v-else class="space-y-3"> <div v-else class="space-y-3">
<div v-for="group in filteredGroupedConnections" :key="group.tag ? group.tag.id : 'untagged'" class="tag-group"> <div v-for="group in filteredGroupedConnections" :key="getGroupId(group)" class="tag-group">
<div class="flex items-center py-1.5"> <div
class="flex items-center py-1.5 cursor-pointer group"
@click="toggleTagGroupExpansion(group)"
>
<input <input
type="checkbox" type="checkbox"
:id="'tag-' + (group.tag ? group.tag.id : 'untagged')" :id="'tag-cb-' + getGroupId(group)"
:checked="isTagGroupSelected(group)" :checked="isTagGroupSelected(group)"
:indeterminate="isTagGroupIndeterminate(group)" :indeterminate="isTagGroupIndeterminate(group)"
@change="toggleTagGroupSelection(group)" @change="toggleTagGroupSelection(group)"
class="mr-2 h-4 w-4 rounded border-border text-primary focus:ring-primary focus:ring-offset-0 cursor-pointer" @click.stop
class="mr-1.5 h-4 w-4 rounded border-border text-primary focus:ring-primary focus:ring-offset-0 cursor-pointer"
/> />
<label :for="'tag-' + (group.tag ? group.tag.id : 'untagged')" class="font-semibold text-foreground select-none cursor-pointer text-sm"> <i
:class="['fas', expandedTagGroups[getGroupId(group)] ? 'fa-chevron-down' : 'fa-chevron-right', 'mr-2 w-3 text-center text-text-secondary/80 group-hover:text-text-secondary transition-transform duration-150 ease-in-out']"
style="font-size: 0.75rem;"
></i>
<label
:for="'tag-cb-' + getGroupId(group)"
class="font-semibold text-foreground select-none cursor-pointer text-sm"
@click.stop
>
{{ group.tag ? group.tag.name : t('sendFilesModal.untaggedConnections') }} ({{ group.connections.length }}) {{ group.tag ? group.tag.name : t('sendFilesModal.untaggedConnections') }} ({{ group.connections.length }})
</label> </label>
</div> </div>
<ul class="pl-7 space-y-0.5"> <ul v-show="expandedTagGroups[getGroupId(group)] ?? true" class="pl-7 space-y-0.5">
<li <li
v-for="connection in group.connections" v-for="connection in group.connections"
:key="connection.id" :key="connection.id"
@@ -190,6 +202,22 @@ const selectedConnectionIds = ref<number[]>([]);
const isLoadingConnections = ref(false); const isLoadingConnections = ref(false);
const isLoadingTags = ref(false); const isLoadingTags = ref(false);
const expandedTagGroups = ref<Record<string, boolean>>({});
const getGroupId = (group: GroupedConnection): string => {
return group.tag ? String(group.tag.id) : 'untagged';
};
const toggleTagGroupExpansion = (group: GroupedConnection) => {
const groupId = getGroupId(group);
// If state is undefined, default to true (expanded), then toggle it.
// So, if undefined, it becomes !(true) = false. If defined, it's just toggled.
// To make it default to expanded and then collapse on first click:
// expandedTagGroups.value[groupId] = !(expandedTagGroups.value[groupId] ?? true);
// To make it default to collapsed and then expand on first click (if true means expanded):
expandedTagGroups.value[groupId] = !(expandedTagGroups.value[groupId] ?? true);
};
// Simulate data for itemsToSend for development if not provided // Simulate data for itemsToSend for development if not provided
const itemsToSendInternal = computed<ItemToSend[]>(() => { const itemsToSendInternal = computed<ItemToSend[]>(() => {
if (props.itemsToSend && props.itemsToSend.length > 0) { if (props.itemsToSend && props.itemsToSend.length > 0) {
@@ -269,19 +297,38 @@ const groupedConnections = computed<GroupedConnection[]>(() => {
}); });
const filteredGroupedConnections = computed<GroupedConnection[]>(() => { const filteredGroupedConnections = computed<GroupedConnection[]>(() => {
const baseGroups = groupedConnections.value;
if (!searchTerm.value.trim()) { if (!searchTerm.value.trim()) {
return groupedConnections.value; // If no search term, filter out groups that initially have no connections.
return baseGroups.filter(group => group.connections.length > 0);
} }
const lowerSearchTerm = searchTerm.value.toLowerCase(); const lowerSearchTerm = searchTerm.value.toLowerCase();
return groupedConnections.value
const result = baseGroups
.map(group => { .map(group => {
const filteredConns = group.connections.filter(conn => const groupDisplayName = group.tag ? group.tag.name : t('sendFilesModal.untaggedConnections');
conn.type?.toLowerCase() === 'ssh' && // 只包括 SSH 连接 const isTagMatch = groupDisplayName.toLowerCase().includes(lowerSearchTerm);
const connsMatchingSearchByName = group.connections.filter(conn =>
conn.name.toLowerCase().includes(lowerSearchTerm) conn.name.toLowerCase().includes(lowerSearchTerm)
// conn.type filtering is already handled in groupedConnections
); );
return { ...group, connections: filteredConns };
if (isTagMatch) {
// Tag name matches. Show all connections of this group.
return { ...group, connections: group.connections };
} else if (connsMatchingSearchByName.length > 0) {
// Tag name doesn't match, but some connection names do. Show only those connections.
return { ...group, connections: connsMatchingSearchByName };
}
return null; // Group doesn't match by tag name and has no connections matching by name
}) })
.filter(group => group.connections.length > 0); // 只包括仍有连接的分组 .filter(group => group !== null && group.connections.length > 0) as GroupedConnection[];
return result;
}); });
const isTagGroupSelected = (group: GroupedConnection): boolean => { const isTagGroupSelected = (group: GroupedConnection): boolean => {