Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 48 additions & 9 deletions website/client/components/Home/PackButton.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
<template>
<button
class="pack-button"
:disabled="!isValid || loading"
aria-label="Pack repository"
:class="{ 'pack-button--loading': loading }"
:disabled="!isValid && !loading"
:aria-label="loading ? 'Cancel processing' : 'Pack repository'"
type="submit"
@click="handleClick"
>
{{ loading ? 'Processing...' : 'Pack' }}
<span class="pack-button__text pack-button__text--normal">
{{ loading ? 'Processing...' : 'Pack' }}
</span>
<span class="pack-button__text pack-button__text--hover">
{{ loading ? 'Cancel' : 'Pack' }}
</span>
<PackIcon v-if="!loading" :size="20" />
</button>
</template>

<script setup>
<script setup lang="ts">
import PackIcon from './PackIcon.vue';

defineProps({
loading: Boolean,
isValid: Boolean,
});
const props = defineProps<{
loading?: boolean;
isValid?: boolean;
}>();

const emit = defineEmits<(e: 'cancel') => void>();

function handleClick(event: MouseEvent) {
if (props.loading) {
event.preventDefault();
event.stopPropagation();
emit('cancel');
}
}
</script>

<style scoped>
Expand All @@ -36,18 +53,40 @@ defineProps({
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;

position: relative;
}

.pack-button:hover:not(:disabled) {
background: var(--vp-c-brand-2);
}

.pack-button--loading:hover {
background: var(--vp-c-danger-1);
}

.pack-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.pack-button__text {
transition: opacity 0.2s ease;
}

.pack-button__text--hover {
position: absolute;
opacity: 0;
pointer-events: none;
}

.pack-button--loading:hover .pack-button__text--normal {
opacity: 0;
}

.pack-button--loading:hover .pack-button__text--hover {
opacity: 1;
}

.pack-button-icon {
font-size: 20px;
line-height: 1;
Expand Down
14 changes: 14 additions & 0 deletions website/client/components/Home/TryIt.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<PackButton
:loading="loading"
:isValid="isSubmitValid"
@cancel="handleCancel"
/>
<div
v-if="shouldShowReset"
Expand Down Expand Up @@ -92,6 +93,7 @@
:result="result"
:loading="loading"
:error="error"
:error-type="errorType"
:repository-url="inputRepositoryUrl"
@repack="handleRepack"
/>
Expand Down Expand Up @@ -129,6 +131,7 @@ const {
// Request states
loading,
error,
errorType,
result,
hasExecuted,

Expand All @@ -141,6 +144,7 @@ const {
submitRequest,
repackWithSelectedFiles,
resetOptions,
cancelRequest,
} = usePackRequest();

// Check if reset button should be shown
Expand Down Expand Up @@ -179,6 +183,12 @@ function updateUrlFromCurrentState() {
}

async function handleSubmit(event?: SubmitEvent) {
// Prevent form submission when already loading
if (loading.value) {
event?.preventDefault();
return;
}

// Prevent accidental form submissions from unintended buttons
if (event?.submitter && !isSubmitValid.value) {
const submitter = event.submitter as HTMLElement;
Expand Down Expand Up @@ -213,6 +223,10 @@ function handleRepack(selectedFiles: FileInfo[]) {
repackWithSelectedFiles(selectedFiles);
}

function handleCancel() {
cancelRequest();
}

// Watch for changes in packOptions and inputUrl to update URL in real-time
watch(
[packOptions, inputUrl],
Expand Down
2 changes: 2 additions & 0 deletions website/client/components/Home/TryItFileUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const props = defineProps<{

const emit = defineEmits<{
upload: [file: File];
cancel: [];
}>();

const { validateZipFile } = useZipProcessor();
Expand Down Expand Up @@ -100,6 +101,7 @@ function clearFile() {
<PackButton
:loading="loading"
:isValid="isValid"
@cancel="$emit('cancel')"
/>
</div>
</template>
Expand Down
2 changes: 2 additions & 0 deletions website/client/components/Home/TryItFolderUpload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const props = defineProps<{

const emit = defineEmits<{
upload: [file: File];
cancel: [];
}>();

const { createZipFromFiles } = useZipProcessor();
Expand Down Expand Up @@ -110,6 +111,7 @@ function clearFolder() {
<PackButton
:loading="loading"
:isValid="isValid"
@cancel="$emit('cancel')"
/>
</div>
</template>
Expand Down
2 changes: 2 additions & 0 deletions website/client/components/Home/TryItResult.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface Props {
result?: PackResult | null;
loading?: boolean;
error?: string | null;
errorType?: 'error' | 'warning';
repositoryUrl?: string;
}

Expand Down Expand Up @@ -50,6 +51,7 @@ const handleRepack = (selectedFiles: FileInfo[]) => {
<TryItResultErrorContent
v-else-if="error"
:message="error"
:error-type="errorType"
:repository-url="repositoryUrl"
/>
<div v-else-if="result" class="result-content">
Expand Down
30 changes: 23 additions & 7 deletions website/client/components/Home/TryItResultErrorContent.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<script setup lang="ts">
import { AlertTriangle, Copy } from 'lucide-vue-next';
import { AlertCircle, AlertTriangle, Copy } from 'lucide-vue-next';
import { computed, ref } from 'vue';

const props = defineProps<{
message: string;
repositoryUrl?: string;
errorType?: 'error' | 'warning';
}>();

const copied = ref(false);
Expand All @@ -25,10 +26,11 @@ const copyCommand = async (event: Event) => {
</script>

<template>
<div class="error">
<div class="error-content">
<AlertTriangle :size="32" class="error-icon" />
<p class="error-message">{{ message }}</p>
<div :class="errorType === 'warning' ? 'warning' : 'error'">
<div class="content">
<AlertCircle v-if="errorType === 'warning'" :size="32" class="warning-icon" />
<AlertTriangle v-else :size="32" class="error-icon" />
<p :class="errorType === 'warning' ? 'warning-message' : 'error-message'">{{ message }}</p>
<div class="suggestion">
<p>Try using the command line tool instead:</p>
<div class="command-block">
Expand All @@ -47,14 +49,15 @@ const copyCommand = async (event: Event) => {
</template>

<style scoped>
.error {
.error,
.warning {
padding: 32px;
display: flex;
justify-content: center;
align-items: center;
}

.error-content {
.content {
max-width: 700px;
width: 100%;
display: flex;
Expand All @@ -68,10 +71,23 @@ const copyCommand = async (event: Event) => {
margin-bottom: 16px;
}

.warning-icon {
color: var(--vp-c-warning-1);
margin-bottom: 16px;
}

.error-message {
color: var(--vp-c-danger-1);
font-size: 1.1em;
margin: 0 0 24px;
white-space: pre-wrap;
}

.warning-message {
color: var(--vp-c-warning-1);
font-size: 1.1em;
margin: 0 0 24px;
white-space: pre-wrap;
}

.suggestion {
Expand Down
3 changes: 2 additions & 1 deletion website/client/components/Home/TryItUrlInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const emit = defineEmits<{
'update:url': [value: string];
submit: [];
keydown: [event: KeyboardEvent];
cancel: [];
}>();

const isValidUrl = computed(() => {
Expand Down Expand Up @@ -114,7 +115,7 @@ function handleKeydown(event: KeyboardEvent) {
<span>Please enter a valid GitHub repository URL (e.g., yamadashy/repomix)</span>
</div>
<div v-if="showButton" class="pack-button-container">
<PackButton :isValid="isValidUrl" :loading="loading" @click="handleSubmit"/>
<PackButton :isValid="isValidUrl" :loading="loading" @click="handleSubmit" @cancel="$emit('cancel')"/>
</div>
</div>
</template>
Expand Down
29 changes: 25 additions & 4 deletions website/client/components/utils/requestHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { type AnalyticsActionType, analyticsUtils } from './analytics';
interface RequestHandlerOptions {
onSuccess?: (result: PackResult) => void;
onError?: (error: string) => void;
onAbort?: (message: string) => void;
signal?: AbortSignal;
file?: File;
}
Expand All @@ -18,7 +19,7 @@ export async function handlePackRequest(
options: PackOptions,
handlerOptions: RequestHandlerOptions = {},
): Promise<void> {
const { onSuccess, onError, signal, file } = handlerOptions;
const { onSuccess, onError, onAbort, signal, file } = handlerOptions;
const processedUrl = url.trim();

// Track pack start
Expand Down Expand Up @@ -46,14 +47,34 @@ export async function handlePackRequest(

onSuccess?.(response);
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'An unexpected error occurred';
// Check for abort/timeout first, regardless of error type
if (signal?.aborted) {
const isTimeout = signal?.reason === 'timeout';
if (isTimeout) {
onAbort?.('Request timed out.\nPlease consider using Include Patterns or Ignore Patterns to reduce the scope.');
return;
}

if (errorMessage === 'AbortError') {
onError?.('Request was cancelled');
const isCancelled = signal?.reason === 'cancel';
if (isCancelled) {
onAbort?.('Request was cancelled.');
return;
}

onAbort?.('Request was cancelled with an unknown reason.');
return;
}

let errorMessage: string;

if (err instanceof Error) {
errorMessage = err.message;
} else {
errorMessage = 'An unexpected error occurred';
}

analyticsUtils.trackPackError(processedUrl, errorMessage);

console.error('Error processing repository:', err);
onError?.(errorMessage);
}
Expand Down
13 changes: 11 additions & 2 deletions website/client/composables/usePackRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function usePackRequest() {
// Request states
const loading = ref(false);
const error = ref<string | null>(null);
const errorType = ref<'error' | 'warning'>('error');
const result = ref<PackResult | null>(null);
const hasExecuted = ref(false);

Expand Down Expand Up @@ -51,6 +52,7 @@ export function usePackRequest() {

function resetRequest() {
error.value = null;
errorType.value = 'error';
result.value = null;
hasExecuted.value = false;
}
Expand All @@ -66,13 +68,15 @@ export function usePackRequest() {

loading.value = true;
error.value = null;
errorType.value = 'error';
result.value = null;
hasExecuted.value = true;
inputRepositoryUrl.value = inputUrl.value;

// Set up automatic timeout
const timeoutId = setTimeout(() => {
if (requestController) {
requestController.abort('Request timed out');
requestController.abort('timeout');
}
}, TIMEOUT_MS);

Expand All @@ -88,6 +92,10 @@ export function usePackRequest() {
onError: (errorMessage) => {
error.value = errorMessage;
},
onAbort: (message) => {
error.value = message;
errorType.value = 'warning';
},
signal: requestController.signal,
file: mode.value === 'file' || mode.value === 'folder' ? uploadedFile.value || undefined : undefined,
},
Expand Down Expand Up @@ -132,7 +140,7 @@ export function usePackRequest() {

function cancelRequest() {
if (requestController) {
requestController.abort();
requestController.abort('cancel');
requestController = null;
}
loading.value = false;
Expand Down Expand Up @@ -167,6 +175,7 @@ export function usePackRequest() {
// Request states
loading,
error,
errorType,
result,
hasExecuted,

Expand Down
Loading
Loading