mirror of
https://github.com/yamadashy/repomix.git
synced 2026-05-30 11:18:53 +02:00
d9e141ec65
Implement cancel button for pack requests with improved user experience. The button now shows "Cancel" on hover during processing and allows users to abort ongoing requests. Also improved error messages for better clarity. - Add cancel functionality to PackButton with hover state - Implement request cancellation using AbortController - Improve error messages for remote repository processing failures - Update timeout handling and server configuration - Add proper event handling to prevent form submission conflicts
221 lines
4.4 KiB
Vue
221 lines
4.4 KiB
Vue
<script setup lang="ts">
|
||
import { AlertTriangle, FolderArchive } from 'lucide-vue-next';
|
||
import { useFileUpload } from '../../composables/useFileUpload';
|
||
import { useZipProcessor } from '../../composables/useZipProcessor';
|
||
import PackButton from './PackButton.vue';
|
||
|
||
const props = defineProps<{
|
||
loading: boolean;
|
||
showButton?: boolean;
|
||
}>();
|
||
|
||
const emit = defineEmits<{
|
||
upload: [file: File];
|
||
cancel: [];
|
||
}>();
|
||
|
||
const { validateZipFile } = useZipProcessor();
|
||
|
||
const {
|
||
fileInput,
|
||
dragActive,
|
||
selectedItem: selectedFile,
|
||
errorMessage,
|
||
hasError,
|
||
isValid,
|
||
inputAttributes,
|
||
handleFileSelect,
|
||
handleDragOver,
|
||
handleDragLeave,
|
||
handleDrop,
|
||
triggerFileInput,
|
||
clearSelection,
|
||
} = useFileUpload({
|
||
mode: 'file',
|
||
placeholder: 'Drop your ZIP file here or click to browse (max 10MB)',
|
||
icon: 'file',
|
||
options: {
|
||
maxFileSize: 10 * 1024 * 1024, // 10MB
|
||
acceptedTypes: ['.zip'],
|
||
accept: '.zip',
|
||
validateFile: validateZipFile,
|
||
},
|
||
});
|
||
|
||
async function onFileSelect(files: FileList | null) {
|
||
const result = await handleFileSelect(files);
|
||
if (result.success && result.result) {
|
||
emit('upload', result.result);
|
||
}
|
||
}
|
||
|
||
async function onDrop(event: DragEvent) {
|
||
const result = await handleDrop(event);
|
||
if (result.success && result.result) {
|
||
emit('upload', result.result);
|
||
}
|
||
}
|
||
|
||
function clearFile() {
|
||
clearSelection();
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="upload-wrapper">
|
||
<div
|
||
class="upload-container"
|
||
:class="{ 'drag-active': dragActive, 'has-error': hasError }"
|
||
@dragover.prevent="handleDragOver"
|
||
@dragleave="handleDragLeave"
|
||
@drop.prevent="onDrop"
|
||
@click="triggerFileInput"
|
||
>
|
||
<input
|
||
ref="fileInput"
|
||
v-bind="inputAttributes"
|
||
class="hidden-input"
|
||
@change="(e) => onFileSelect((e.target as HTMLInputElement).files)"
|
||
/>
|
||
<div class="upload-content">
|
||
<div class="upload-icon">
|
||
<AlertTriangle v-if="hasError" class="icon-error" size="20" />
|
||
<FolderArchive v-else class="icon-folder" size="20" />
|
||
</div>
|
||
<div class="upload-text">
|
||
<p v-if="errorMessage" class="error-message">
|
||
{{ errorMessage }}
|
||
</p>
|
||
<p v-else-if="selectedFile" class="selected-file">
|
||
Selected: {{ selectedFile }}
|
||
<button class="clear-button" @click.stop="clearFile">×</button>
|
||
</p>
|
||
<template v-else>
|
||
<p>Drop your ZIP file here or click to browse (max 10MB)</p>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-if="showButton" class="pack-button-container">
|
||
<PackButton
|
||
:loading="loading"
|
||
:isValid="isValid"
|
||
@cancel="$emit('cancel')"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.upload-wrapper {
|
||
width: 100%;
|
||
}
|
||
|
||
.upload-container {
|
||
border: 2px dashed var(--vp-c-border);
|
||
border-radius: 8px;
|
||
padding: 0 16px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
height: 50px;
|
||
display: flex;
|
||
align-items: center;
|
||
background: var(--vp-c-bg);
|
||
user-select: none;
|
||
}
|
||
|
||
.upload-container:hover {
|
||
border-color: var(--vp-c-brand-1);
|
||
background-color: var(--vp-c-bg-soft);
|
||
}
|
||
|
||
.drag-active {
|
||
border-color: var(--vp-c-brand-1);
|
||
background-color: var(--vp-c-bg-soft);
|
||
}
|
||
|
||
.has-error {
|
||
border-color: var(--vp-c-danger-1);
|
||
}
|
||
|
||
.hidden-input {
|
||
display: none;
|
||
}
|
||
|
||
.upload-content {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 12px;
|
||
width: 100%;
|
||
pointer-events: none; /* Allow clicks to pass through to container */
|
||
}
|
||
|
||
.upload-icon {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.icon-folder {
|
||
color: var(--vp-c-text-1);
|
||
}
|
||
|
||
.icon-error {
|
||
color: var(--vp-c-danger-1);
|
||
}
|
||
|
||
.upload-text {
|
||
flex: 1;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.upload-text p {
|
||
margin: 0;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.error-message {
|
||
color: var(--vp-c-danger-1);
|
||
}
|
||
|
||
.selected-file {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
width: 100%;
|
||
}
|
||
|
||
.clear-button {
|
||
background: none;
|
||
border: none;
|
||
color: var(--vp-c-text-2);
|
||
cursor: pointer;
|
||
font-size: 1.2em;
|
||
padding: 0 4px;
|
||
line-height: 1;
|
||
flex-shrink: 0;
|
||
pointer-events: auto; /* Re-enable pointer events for button */
|
||
}
|
||
|
||
.clear-button:hover {
|
||
color: var(--vp-c-text-1);
|
||
}
|
||
|
||
.pack-button-container {
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.upload-text p {
|
||
font-size: 13px;
|
||
}
|
||
}
|
||
</style>
|