refactor: restructure project layout
- Move main process to electron/main/ - Move preload to electron/preload/ - Add shared types in electron/shared/ - Split redis-service into modular redis/ directory - Flatten renderer to src/ (remove src/renderer/ nesting) - Update electron.vite.config.ts paths - Update tsconfig paths - Output to electron-dist/
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
<script setup lang="ts">
|
||||
// src/renderer/src/components/ConnectionList.vue
|
||||
import { ref, computed } from 'vue'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import ConnectionCard from './ConnectionCard.vue'
|
||||
import { useConnectionStore } from '@renderer/stores/connection'
|
||||
import type { ConnectionConfig } from '@renderer/stores/connection'
|
||||
|
||||
const connStore = useConnectionStore()
|
||||
const searchQuery = ref('')
|
||||
const emit = defineEmits<{ edit: [conn: ConnectionConfig | null] }>()
|
||||
|
||||
const filteredConnections = computed(() => {
|
||||
const q = searchQuery.value.toLowerCase()
|
||||
return connStore.connections
|
||||
.filter(c => c.name.toLowerCase().includes(q) || c.host.includes(q))
|
||||
.sort((a, b) => a.order - b.order)
|
||||
})
|
||||
|
||||
async function handleDelete(id: string) {
|
||||
try {
|
||||
await ElMessageBox.confirm('Delete this connection?', 'Confirm', {
|
||||
type: 'warning',
|
||||
confirmButtonText: 'Delete',
|
||||
cancelButtonText: 'Cancel',
|
||||
})
|
||||
await connStore.deleteConnection(id)
|
||||
} catch { /* cancelled */ }
|
||||
}
|
||||
|
||||
function handleEdit(conn: ConnectionConfig) {
|
||||
emit('edit', conn)
|
||||
}
|
||||
|
||||
function handleNew() {
|
||||
emit('edit', null)
|
||||
}
|
||||
|
||||
async function handleExport() {
|
||||
try {
|
||||
const data = await connStore.exportConnections()
|
||||
const blob = new Blob([data], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `jredis-connections-${new Date().toISOString().slice(0, 10)}.json`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
ElMessage.success('Connections exported')
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleImport() {
|
||||
try {
|
||||
const result = await window.electronAPI.dialog.openFile({
|
||||
title: 'Import Connections',
|
||||
filters: [{ name: 'JSON', extensions: ['json'] }],
|
||||
})
|
||||
if (result) {
|
||||
const count = await connStore.importConnections(result.content)
|
||||
ElMessage.success(`Imported ${count} connection(s)`)
|
||||
await connStore.loadConnections()
|
||||
}
|
||||
} catch (err: any) {
|
||||
ElMessage.error(err.message)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="connection-list">
|
||||
<div class="list-search">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
class="search-input"
|
||||
type="text"
|
||||
placeholder="Search connections..."
|
||||
/>
|
||||
</div>
|
||||
<div class="list-items">
|
||||
<ConnectionCard
|
||||
v-for="conn in filteredConnections"
|
||||
:key="conn.id"
|
||||
:connection="conn"
|
||||
@edit="handleEdit"
|
||||
@delete="handleDelete"
|
||||
/>
|
||||
<div v-if="filteredConnections.length === 0" class="list-empty">
|
||||
<p v-if="searchQuery">No connections match</p>
|
||||
<p v-else>No connections yet</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-footer">
|
||||
<button class="list-add-btn" @click="handleNew">
|
||||
+ New Connection
|
||||
</button>
|
||||
<div class="list-actions">
|
||||
<el-button size="small" text @click="handleImport">
|
||||
<el-icon><Upload /></el-icon> Import
|
||||
</el-button>
|
||||
<el-button size="small" text @click="handleExport">
|
||||
<el-icon><Download /></el-icon> Export
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.connection-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list-search {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-primary);
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
border-color: var(--border-accent);
|
||||
}
|
||||
|
||||
.list-items {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 14px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.list-empty {
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.list-footer {
|
||||
padding: 14px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.list-add-btn {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: var(--bg-card);
|
||||
border: 1px dashed var(--border-color);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.list-add-btn:hover {
|
||||
border-color: var(--border-accent);
|
||||
color: var(--accent-light);
|
||||
background: var(--bg-card-hover);
|
||||
}
|
||||
|
||||
.list-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user