802 lines
22 KiB
Vue
802 lines
22 KiB
Vue
<template>
|
|
<v-container
|
|
fluid
|
|
class="pa-0 fill-height"
|
|
>
|
|
<v-row class="fill-height">
|
|
<!-- Left Panel - Template Info -->
|
|
<v-col
|
|
cols="12"
|
|
md="4"
|
|
class="pa-4 border-e"
|
|
>
|
|
<div class="d-flex align-center mb-4">
|
|
<h2 class="text-h5 mr-2">
|
|
{{ isEditMode ? '编辑模板' : '创建模板' }}
|
|
</h2>
|
|
<v-spacer />
|
|
<v-btn
|
|
icon
|
|
variant="text"
|
|
@click="goBack"
|
|
>
|
|
<v-icon>mdi-close</v-icon>
|
|
<v-tooltip
|
|
activator="parent"
|
|
location="bottom"
|
|
>
|
|
关闭
|
|
</v-tooltip>
|
|
</v-btn>
|
|
</div>
|
|
|
|
<v-form
|
|
v-model="formValid"
|
|
class="mb-4"
|
|
>
|
|
<v-text-field
|
|
v-model="templateName"
|
|
label="模板名称 *"
|
|
placeholder="输入模板名称"
|
|
:rules="[requiredRule]"
|
|
required
|
|
clearable
|
|
density="comfortable"
|
|
/>
|
|
|
|
<v-textarea
|
|
v-model="templateDescription"
|
|
label="模板描述"
|
|
placeholder="描述这个模板的用途..."
|
|
rows="2"
|
|
auto-grow
|
|
clearable
|
|
density="comfortable"
|
|
/>
|
|
</v-form>
|
|
|
|
<v-divider class="my-4" />
|
|
|
|
<!-- Template Actions -->
|
|
<div class="d-flex ga-2">
|
|
<v-btn
|
|
color="primary"
|
|
@click="saveTemplate"
|
|
:disabled="!formValid || !hasChanges"
|
|
prepend-icon="mdi-content-save"
|
|
block
|
|
>
|
|
保存模板
|
|
</v-btn>
|
|
</div>
|
|
</v-col>
|
|
|
|
<!-- Right Panel - Tree Structure -->
|
|
<v-col
|
|
cols="12"
|
|
md="8"
|
|
class="pa-4"
|
|
>
|
|
<div class="d-flex align-center mb-4">
|
|
<h3 class="text-h6 mr-2">模板结构</h3>
|
|
<v-spacer />
|
|
<v-btn
|
|
icon
|
|
variant="text"
|
|
@click="addRootDirectory"
|
|
size="small"
|
|
>
|
|
<v-icon>mdi-folder-plus</v-icon>
|
|
<v-tooltip
|
|
activator="parent"
|
|
location="bottom"
|
|
>
|
|
添加根目录
|
|
</v-tooltip>
|
|
</v-btn>
|
|
<v-btn
|
|
icon
|
|
variant="text"
|
|
@click="addRootFile"
|
|
size="small"
|
|
>
|
|
<v-icon>mdi-file-plus</v-icon>
|
|
<v-tooltip
|
|
activator="parent"
|
|
location="bottom"
|
|
>
|
|
添加根文件
|
|
</v-tooltip>
|
|
</v-btn>
|
|
<v-btn
|
|
icon
|
|
variant="text"
|
|
@click="expandAll"
|
|
size="small"
|
|
>
|
|
<v-icon>mdi-arrow-expand-down</v-icon>
|
|
<v-tooltip
|
|
activator="parent"
|
|
location="bottom"
|
|
>
|
|
展开全部
|
|
</v-tooltip>
|
|
</v-btn>
|
|
<v-btn
|
|
icon
|
|
variant="text"
|
|
@click="collapseAll"
|
|
size="small"
|
|
>
|
|
<v-icon>mdi-arrow-collapse-up</v-icon>
|
|
<v-tooltip
|
|
activator="parent"
|
|
location="bottom"
|
|
>
|
|
收起全部
|
|
</v-tooltip>
|
|
</v-btn>
|
|
</div>
|
|
|
|
<TemplateTree
|
|
:items="treeItems"
|
|
:height="treeHeight"
|
|
v-model:expanded="expandedNodes"
|
|
v-model:showContextMenuVar="showContextMenuVar"
|
|
:context-menu-x="contextMenuX"
|
|
:context-menu-y="contextMenuY"
|
|
:current-context-item="currentContextItem"
|
|
:empty-text="'暂无文件或目录'"
|
|
:read-only="false"
|
|
@showContextMenu="showContextMenu"
|
|
@add-new-item-to-current-directory="addNewItemToCurrentDirectory"
|
|
@edit-file="editFile"
|
|
@delete-item="deleteItem"
|
|
@add-directory="addDirectory"
|
|
@add-file="addFile"
|
|
/>
|
|
</v-col>
|
|
</v-row>
|
|
|
|
<!-- Add Directory Dialog -->
|
|
<v-dialog
|
|
v-model="showDirectoryDialog"
|
|
max-width="500"
|
|
>
|
|
<v-card>
|
|
<v-card-title>添加目录</v-card-title>
|
|
<v-card-text>
|
|
<v-form v-model="directoryFormValid">
|
|
<v-text-field
|
|
v-model="newDirectoryPath"
|
|
label="目录路径 *"
|
|
placeholder="例如: src/components"
|
|
:rules="[requiredRule]"
|
|
required
|
|
density="comfortable"
|
|
ref="directoryInputRef"
|
|
@keydown.enter="confirmAddDirectory"
|
|
/>
|
|
</v-form>
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-spacer />
|
|
<v-btn @click="showDirectoryDialog = false"> 取消 </v-btn>
|
|
<v-btn
|
|
color="primary"
|
|
@click="confirmAddDirectory"
|
|
:disabled="!directoryFormValid"
|
|
>
|
|
添加
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
|
|
<!-- Add File Dialog -->
|
|
<v-dialog
|
|
v-model="showFileDialog"
|
|
max-width="600"
|
|
>
|
|
<v-card>
|
|
<v-card-title>添加文件</v-card-title>
|
|
<v-card-text>
|
|
<v-form v-model="fileFormValid">
|
|
<v-text-field
|
|
v-model="newFilePath"
|
|
label="目标文件路径 *"
|
|
placeholder="例如: src/index.js"
|
|
:rules="[requiredRule]"
|
|
required
|
|
density="comfortable"
|
|
ref="fileInputRef"
|
|
@keydown.enter="selectAndAddFile"
|
|
/>
|
|
|
|
<div class="d-flex align-center my-4">
|
|
<v-btn
|
|
@click="selectSourceFile"
|
|
variant="outlined"
|
|
class="mr-2"
|
|
>
|
|
选择文件
|
|
</v-btn>
|
|
<v-btn
|
|
@click="selectSourceFolder"
|
|
variant="outlined"
|
|
class="mr-2"
|
|
>
|
|
选择文件夹
|
|
</v-btn>
|
|
</div>
|
|
|
|
<!-- Display selected source -->
|
|
<div v-if="selectedSourcePath" class="d-flex align-center">
|
|
<v-icon :color="isAutoFindMode ? 'warning' : 'info'" class="mr-2">
|
|
{{ isAutoFindMode ? 'mdi-folder' : 'mdi-file' }}
|
|
</v-icon>
|
|
<span class="text-truncate">{{ selectedSourcePath }}</span>
|
|
</div>
|
|
|
|
<!-- Auto-find indicator -->
|
|
<v-alert
|
|
v-if="isAutoFindMode"
|
|
type="info"
|
|
variant="tonal"
|
|
class="mt-2"
|
|
>
|
|
<div class="d-flex align-center">
|
|
<v-icon class="mr-2">mdi-auto-fix</v-icon>
|
|
<span>自动寻找文件夹内的文件</span>
|
|
</div>
|
|
</v-alert>
|
|
</v-form>
|
|
</v-card-text>
|
|
<v-card-actions>
|
|
<v-spacer />
|
|
<v-btn @click="showFileDialog = false"> 取消 </v-btn>
|
|
<v-btn
|
|
color="primary"
|
|
@click="selectAndAddFile"
|
|
:disabled="!fileFormValid || !selectedSourcePath"
|
|
>
|
|
添加
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card>
|
|
</v-dialog>
|
|
|
|
<!-- File Content Editor Dialog -->
|
|
<v-dialog
|
|
v-model="showEditorDialog"
|
|
fullscreen
|
|
>
|
|
<v-card>
|
|
<v-toolbar
|
|
dark
|
|
color="primary"
|
|
>
|
|
<v-btn
|
|
icon
|
|
@click="showEditorDialog = false"
|
|
>
|
|
<v-icon>mdi-close</v-icon>
|
|
</v-btn>
|
|
<v-toolbar-title> 编辑文件: {{ editingFile?.path }} </v-toolbar-title>
|
|
<v-spacer />
|
|
<v-btn
|
|
variant="text"
|
|
@click="showEditorDialog = false"
|
|
>
|
|
关闭
|
|
</v-btn>
|
|
</v-toolbar>
|
|
|
|
<v-card-text class="pa-0 fill-height">
|
|
<v-textarea
|
|
v-model="editingFileContent"
|
|
class="fill-height"
|
|
style="height: calc(100vh - 120px)"
|
|
no-resize
|
|
spellcheck="false"
|
|
placeholder="在此输入文件内容..."
|
|
variant="solo-filled"
|
|
density="comfortable"
|
|
/>
|
|
</v-card-text>
|
|
</v-card>
|
|
</v-dialog>
|
|
|
|
<!-- Snackbar for error messages -->
|
|
<v-snackbar
|
|
v-model="snackbarVisible"
|
|
:color="snackbarColor"
|
|
:timeout="2000"
|
|
location="top"
|
|
>
|
|
{{ snackbarMessage }}
|
|
<template #actions>
|
|
<v-btn
|
|
icon
|
|
@click="snackbarVisible = false"
|
|
>
|
|
<v-icon>mdi-close</v-icon>
|
|
</v-btn>
|
|
</template>
|
|
</v-snackbar>
|
|
</v-container>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { useTemplateStore } from '@/renderer/store/template'
|
|
import { Template } from '@/renderer/store/template'
|
|
import TemplateTree from '@/renderer/components/TemplateTree.vue'
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const templateStore = useTemplateStore()
|
|
|
|
// Extract methods from store
|
|
const { getTemplateById } = templateStore
|
|
|
|
// Form state
|
|
const formValid = ref(false)
|
|
const templateName = ref('')
|
|
const templateDescription = ref('')
|
|
const hasChanges = ref(false)
|
|
|
|
// Dialogs
|
|
const showDirectoryDialog = ref(false)
|
|
const showFileDialog = ref(false)
|
|
const showEditorDialog = ref(false)
|
|
const directoryFormValid = ref(false)
|
|
const fileFormValid = ref(false)
|
|
|
|
// Input refs for auto-focus
|
|
const directoryInputRef = ref<HTMLInputElement | null>(null)
|
|
const fileInputRef = ref<HTMLInputElement | null>(null)
|
|
|
|
// Context menu
|
|
const showContextMenuVar = ref(false)
|
|
const contextMenuX = ref(0)
|
|
const contextMenuY = ref(0)
|
|
const currentContextItem = ref<TreeItem | null>(null)
|
|
|
|
// Snackbar variables
|
|
const snackbarVisible = ref(false)
|
|
const snackbarMessage = ref('')
|
|
const snackbarColor = ref('error') // Can be 'error', 'success', 'warning', etc.
|
|
|
|
// New item state
|
|
const newDirectoryPath = ref('')
|
|
const newFilePath = ref('')
|
|
const editingFile = ref<{ path: string; content: string } | null>(null)
|
|
const editingFileContent = ref('')
|
|
const parentDirectoryForNewItems = ref<string | null>(null) // Track parent context
|
|
const selectedSourcePath = ref<string>('') // Track the selected source file or folder
|
|
const isAutoFindMode = ref(false) // Track if auto-find mode is enabled
|
|
|
|
// Tree state
|
|
const expandedNodes = ref<string[]>([])
|
|
const treeHeight = ref(600)
|
|
|
|
// Define type for tree items
|
|
interface TreeItem {
|
|
id: string
|
|
name: string
|
|
type: 'directory' | 'file'
|
|
children?: TreeItem[]
|
|
}
|
|
|
|
// Item state
|
|
const directories = ref<{ path: string }[]>([])
|
|
const files = ref<{ path: string; content?: string; sourceFilePath?: string }[]>([])
|
|
|
|
// Computed
|
|
const isEditMode = computed(() => !!route.params.id)
|
|
const treeItems = computed<TreeItem[]>(() => {
|
|
// Create root structure
|
|
const root: TreeItem = {
|
|
id: 'root',
|
|
name: 'root',
|
|
type: 'directory',
|
|
children: []
|
|
}
|
|
|
|
// Process each directory
|
|
directories.value.forEach((dir) => {
|
|
const pathParts = dir.path.split('/').filter(part => part !== '') // Split path and remove empty parts
|
|
let currentLevel = root.children || []
|
|
|
|
// Navigate/create the directory structure
|
|
for (let i = 0; i < pathParts.length; i++) {
|
|
const part = pathParts[i]
|
|
const isLastPart = i === pathParts.length - 1
|
|
|
|
// Check if this part already exists in the current level
|
|
let existingItem = currentLevel.find(item => item.name === part)
|
|
|
|
if (!existingItem) {
|
|
// Create new directory item
|
|
const newDir: TreeItem = {
|
|
id: `dir-${part}-${Date.now()}-${Math.random()}`, // Unique ID
|
|
name: part,
|
|
type: 'directory',
|
|
children: []
|
|
}
|
|
currentLevel.push(newDir)
|
|
existingItem = newDir
|
|
}
|
|
|
|
// Move to the next level if not the last part
|
|
if (!isLastPart && existingItem.children) {
|
|
currentLevel = existingItem.children
|
|
}
|
|
}
|
|
})
|
|
|
|
// Process each file and add to the appropriate directory
|
|
files.value.forEach((file) => {
|
|
const pathParts = file.path.split('/').filter(part => part !== '')
|
|
const fileName = pathParts.pop() // Get the last part as filename
|
|
|
|
if (!fileName) return // Skip if no filename
|
|
|
|
let currentLevel = root.children || []
|
|
|
|
// Navigate to the appropriate directory for the file
|
|
for (let i = 0; i < pathParts.length; i++) {
|
|
const part = pathParts[i]
|
|
const dirItem = currentLevel.find(item => item.name === part && item.type === 'directory')
|
|
|
|
if (dirItem && dirItem.children) {
|
|
currentLevel = dirItem.children
|
|
} else {
|
|
// If the directory doesn't exist, create it
|
|
const newDir: TreeItem = {
|
|
id: `dir-${part}-${Date.now()}-${Math.random()}`,
|
|
name: part,
|
|
type: 'directory',
|
|
children: []
|
|
}
|
|
currentLevel.push(newDir)
|
|
currentLevel = newDir.children || []
|
|
}
|
|
}
|
|
|
|
// Add the file to the current level
|
|
currentLevel.push({
|
|
id: `file-${fileName}-${Date.now()}-${Math.random()}`,
|
|
name: fileName,
|
|
type: 'file'
|
|
})
|
|
})
|
|
|
|
return root.children || []
|
|
})
|
|
|
|
// Validation rules
|
|
const requiredRule = (v: string | null | undefined) => !!v || '此字段为必填项'
|
|
|
|
// Watch for changes to detect unsaved changes
|
|
watch(
|
|
[templateName, templateDescription, directories, files],
|
|
() => {
|
|
hasChanges.value = true
|
|
},
|
|
{ deep: true }
|
|
)
|
|
|
|
// Lifecycle
|
|
onMounted(async () => {
|
|
await templateStore.loadTemplates()
|
|
|
|
// Set tree height based on window height
|
|
updateTreeHeight()
|
|
|
|
// Add resize listener
|
|
window.addEventListener('resize', updateTreeHeight)
|
|
|
|
// Handle edit mode
|
|
if (isEditMode.value) {
|
|
const templateId = route.params.id as string
|
|
const template = getTemplateById(templateId)
|
|
|
|
if (template) {
|
|
templateName.value = template.name
|
|
templateDescription.value = template.description
|
|
directories.value = [...template.directories]
|
|
files.value = [...template.files]
|
|
hasChanges.value = false
|
|
} else {
|
|
router.push('/templates')
|
|
}
|
|
}
|
|
|
|
// Expand all nodes by default
|
|
expandAll()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('resize', updateTreeHeight)
|
|
})
|
|
|
|
// Methods
|
|
const updateTreeHeight = () => {
|
|
treeHeight.value = window.innerHeight - 200
|
|
}
|
|
|
|
const showContextMenu = (event: MouseEvent, item: TreeItem | null) => {
|
|
event.preventDefault()
|
|
contextMenuX.value = event.clientX
|
|
contextMenuY.value = event.clientY
|
|
currentContextItem.value = item
|
|
showContextMenuVar.value = false // Close first to reset position
|
|
nextTick(() => {
|
|
contextMenuX.value = event.clientX
|
|
contextMenuY.value = event.clientY
|
|
showContextMenuVar.value = true
|
|
})
|
|
}
|
|
|
|
const addRootDirectory = () => {
|
|
newDirectoryPath.value = ''
|
|
showDirectoryDialog.value = true
|
|
autoFocusDirectoryInput()
|
|
}
|
|
|
|
const addRootFile = () => {
|
|
newFilePath.value = ''
|
|
showFileDialog.value = true
|
|
autoFocusFileInput()
|
|
}
|
|
|
|
const addDirectory = (parentItem: TreeItem | null = null) => {
|
|
showContextMenuVar.value = false
|
|
currentContextItem.value = parentItem
|
|
parentDirectoryForNewItems.value = parentItem ? parentItem.name : null // Store parent context
|
|
newDirectoryPath.value = '' // Don't pre-fill the path
|
|
showDirectoryDialog.value = true
|
|
autoFocusDirectoryInput()
|
|
}
|
|
|
|
const addFile = (parentItem: TreeItem | null = null) => {
|
|
showContextMenuVar.value = false
|
|
parentDirectoryForNewItems.value = parentItem ? parentItem.name : null // Store parent context
|
|
newFilePath.value = '' // Don't pre-fill the path
|
|
showFileDialog.value = true
|
|
autoFocusFileInput()
|
|
}
|
|
|
|
const addNewItemToCurrentDirectory = (directoryItem: TreeItem) => {
|
|
currentContextItem.value = directoryItem
|
|
parentDirectoryForNewItems.value = directoryItem.name // Store parent context
|
|
newDirectoryPath.value = '' // Don't pre-fill the path
|
|
showDirectoryDialog.value = true
|
|
autoFocusDirectoryInput()
|
|
}
|
|
|
|
const confirmAddDirectory = () => {
|
|
if (!newDirectoryPath.value.trim()) return
|
|
|
|
// Construct the full path if there's a parent directory
|
|
let fullPath = newDirectoryPath.value
|
|
if (parentDirectoryForNewItems.value) {
|
|
// Make sure parent directory ends with a slash before appending
|
|
const parentPath = parentDirectoryForNewItems.value.endsWith('/')
|
|
? parentDirectoryForNewItems.value
|
|
: parentDirectoryForNewItems.value + '/'
|
|
fullPath = parentPath + newDirectoryPath.value
|
|
}
|
|
|
|
// Check for duplicates
|
|
const exists = directories.value.some(
|
|
(d) => d.path === fullPath
|
|
)
|
|
if (exists) {
|
|
showSnackbar('该目录路径已存在!')
|
|
return
|
|
}
|
|
|
|
directories.value.push({
|
|
path: fullPath
|
|
})
|
|
|
|
showDirectoryDialog.value = false
|
|
newDirectoryPath.value = ''
|
|
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
|
currentContextItem.value = null
|
|
}
|
|
|
|
const selectAndAddFile = async () => {
|
|
if (!newFilePath.value.trim()) return
|
|
|
|
// Construct the full path if there's a parent directory
|
|
let fullPath = newFilePath.value
|
|
if (parentDirectoryForNewItems.value) {
|
|
// Make sure parent directory ends with a slash before appending
|
|
const parentPath = parentDirectoryForNewItems.value.endsWith('/')
|
|
? parentDirectoryForNewItems.value
|
|
: parentDirectoryForNewItems.value + '/'
|
|
fullPath = parentPath + newFilePath.value
|
|
}
|
|
|
|
// Check for duplicates
|
|
const exists = files.value.some((f) => f.path === fullPath)
|
|
if (exists) {
|
|
showSnackbar('该文件路径已存在!')
|
|
return
|
|
}
|
|
|
|
// Check if we're in auto-find mode and have a selected folder
|
|
if (isAutoFindMode.value && selectedSourcePath.value) {
|
|
// Auto-find files in the selected folder
|
|
const foundFiles = await window.mainApi.invoke('msgFindFilesInFolder', selectedSourcePath.value)
|
|
|
|
// Add all found files as template files
|
|
for (const foundFile of foundFiles) {
|
|
// Calculate relative path from selected folder to the found file
|
|
const relativePath = foundFile.replace(selectedSourcePath.value, '').substring(1) // Remove leading slash
|
|
const fullTargetPath = fullPath ? `${fullPath}/${relativePath}` : relativePath
|
|
|
|
// Check for duplicates
|
|
const duplicate = files.value.some(f => f.path === fullTargetPath)
|
|
if (!duplicate) {
|
|
const newFile = {
|
|
path: fullTargetPath,
|
|
sourceFilePath: foundFile
|
|
}
|
|
files.value.push(newFile)
|
|
}
|
|
}
|
|
|
|
// Reset selection state
|
|
selectedSourcePath.value = ''
|
|
isAutoFindMode.value = false
|
|
|
|
showFileDialog.value = false
|
|
newFilePath.value = ''
|
|
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
|
currentContextItem.value = null
|
|
return
|
|
}
|
|
|
|
// If we have a selected file, just add it
|
|
if (selectedSourcePath.value) {
|
|
const newFile = {
|
|
path: fullPath,
|
|
sourceFilePath: selectedSourcePath.value
|
|
}
|
|
|
|
files.value.push(newFile)
|
|
|
|
// Reset selection state
|
|
selectedSourcePath.value = ''
|
|
isAutoFindMode.value = false
|
|
|
|
showFileDialog.value = false
|
|
newFilePath.value = ''
|
|
parentDirectoryForNewItems.value = null // Clear parent context after creation
|
|
currentContextItem.value = null
|
|
return
|
|
}
|
|
|
|
// Otherwise, show file selection dialog
|
|
showSnackbar('请先选择一个文件或文件夹', 'warning')
|
|
}
|
|
|
|
const goBack = () => {
|
|
if (hasChanges.value) {
|
|
if (confirm('您有未保存的更改,确定要离开吗?')) {
|
|
router.push('/templates')
|
|
}
|
|
} else {
|
|
router.push('/templates')
|
|
}
|
|
}
|
|
|
|
const expandAll = () => {
|
|
expandedNodes.value = treeItems.value.map((item) => item.id)
|
|
}
|
|
|
|
const collapseAll = () => {
|
|
expandedNodes.value = []
|
|
}
|
|
|
|
const editFile = (item: TreeItem) => {
|
|
// For now, just show a message that files are selected, not edited
|
|
showSnackbar('该文件是从源文件复制的,无法直接编辑', 'info')
|
|
}
|
|
|
|
const deleteItem = (item: TreeItem) => {
|
|
if (!confirm('确定要删除此项吗?')) return
|
|
|
|
if (item.type === 'directory') {
|
|
directories.value = directories.value.filter(
|
|
(d) => `dir-${d.path}` !== item.id
|
|
)
|
|
} else {
|
|
files.value = files.value.filter((f) => `file-${f.path}` !== item.id)
|
|
}
|
|
}
|
|
|
|
// Show snackbar message
|
|
const showSnackbar = (message: string, color: string = 'error') => {
|
|
snackbarMessage.value = message
|
|
snackbarColor.value = color
|
|
snackbarVisible.value = true
|
|
}
|
|
|
|
// Methods for file/folder selection
|
|
const selectSourceFile = async () => {
|
|
const result = await window.mainApi.invoke('msgOpenFile', 'any')
|
|
if (!result.canceled && result.filePaths && result.filePaths.length > 0) {
|
|
selectedSourcePath.value = result.filePaths[0]
|
|
isAutoFindMode.value = false // Not in auto-find mode when selecting a specific file
|
|
}
|
|
}
|
|
|
|
const selectSourceFolder = async () => {
|
|
const result = await window.mainApi.invoke('msgSelectDirectory')
|
|
if (!result.canceled && result.filePath) {
|
|
selectedSourcePath.value = result.filePath
|
|
isAutoFindMode.value = true // In auto-find mode when selecting a folder
|
|
}
|
|
}
|
|
|
|
// Auto focus methods
|
|
const autoFocusDirectoryInput = async () => {
|
|
await nextTick()
|
|
if (directoryInputRef.value) {
|
|
;(directoryInputRef.value as HTMLInputElement).focus()
|
|
}
|
|
}
|
|
|
|
const autoFocusFileInput = async () => {
|
|
await nextTick()
|
|
if (fileInputRef.value) {
|
|
;(fileInputRef.value as HTMLInputElement).focus()
|
|
}
|
|
}
|
|
|
|
const saveTemplate = () => {
|
|
if (!formValid.value) return
|
|
|
|
const templateData = {
|
|
name: templateName.value,
|
|
description: templateDescription.value,
|
|
files: files.value,
|
|
directories: directories.value
|
|
}
|
|
|
|
if (isEditMode.value) {
|
|
const templateId = route.params.id as string
|
|
templateStore.updateTemplate({
|
|
id: templateId,
|
|
...templateData
|
|
})
|
|
} else {
|
|
templateStore.addTemplate(templateData)
|
|
}
|
|
|
|
hasChanges.value = false
|
|
router.push('/templates')
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.border-e {
|
|
border-right: 1px solid rgba(0, 0, 0, 0.12);
|
|
}
|
|
|
|
.fill-height {
|
|
height: 100%;
|
|
}
|
|
|
|
.cursor-pointer {
|
|
cursor: pointer;
|
|
}
|
|
</style>
|