ExportDialog.svelte

  1<script lang="ts">
  2	import { showExportDialog } from '$lib/stores/ui';
  3	import { sendReviewToClaude } from '$lib/stores/terminal';
  4
  5	let exporting = $state(false);
  6	let result = $state<{ success: boolean; path?: string; message?: string; text?: string } | null>(null);
  7	let clipboardState = $state<'idle' | 'loading' | 'ready' | 'copied'>('idle');
  8	let pendingClipboardText = $state<string | null>(null);
  9
 10	async function exportAs(format: string) {
 11		if (format === 'clipboard') {
 12			clipboardState = 'loading';
 13			pendingClipboardText = null;
 14			try {
 15				const res = await fetch('/api/export', {
 16					method: 'POST',
 17					headers: { 'Content-Type': 'application/json' },
 18					body: JSON.stringify({ format })
 19				});
 20				const data = await res.json();
 21				if (data?.text) {
 22					pendingClipboardText = data.text;
 23					// Ensure "Preparing..." is visible long enough for user to notice
 24					await new Promise((r) => setTimeout(r, 800));
 25					clipboardState = 'ready';
 26				} else {
 27					result = { success: false, message: 'No content to copy.' };
 28					clipboardState = 'idle';
 29				}
 30			} catch (err) {
 31				result = { success: false, message: String(err) };
 32				clipboardState = 'idle';
 33			}
 34			return;
 35		}
 36
 37		exporting = true;
 38		result = null;
 39		try {
 40			if (format === 'claude') {
 41				const res = await sendReviewToClaude();
 42				if (res.success) {
 43					$showExportDialog = false;
 44					return;
 45				}
 46				result = res;
 47				return;
 48			}
 49
 50			const res = await fetch('/api/export', {
 51				method: 'POST',
 52				headers: { 'Content-Type': 'application/json' },
 53				body: JSON.stringify({ format })
 54			});
 55			result = await res.json();
 56		} catch (err) {
 57			result = { success: false, message: String(err) };
 58		} finally {
 59			exporting = false;
 60		}
 61	}
 62
 63	function copyNow() {
 64		if (!pendingClipboardText) return;
 65		navigator.clipboard.writeText(pendingClipboardText).then(() => {
 66			clipboardState = 'copied';
 67			pendingClipboardText = null;
 68			setTimeout(() => { clipboardState = 'idle'; }, 2000);
 69		}).catch(() => {
 70			const textarea = document.createElement('textarea');
 71			textarea.value = pendingClipboardText!;
 72			textarea.style.position = 'fixed';
 73			textarea.style.left = '-9999px';
 74			document.body.appendChild(textarea);
 75			textarea.focus();
 76			textarea.select();
 77			document.execCommand('copy');
 78			document.body.removeChild(textarea);
 79			clipboardState = 'copied';
 80			pendingClipboardText = null;
 81			setTimeout(() => { clipboardState = 'idle'; }, 2000);
 82		});
 83	}
 84
 85	function close() {
 86		$showExportDialog = false;
 87		result = null;
 88	}
 89</script>
 90
 91{#if $showExportDialog}
 92	<!-- svelte-ignore a11y_click_events_have_key_events -->
 93	<!-- svelte-ignore a11y_no_static_element_interactions -->
 94	<div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center" onclick={close}>
 95		<!-- svelte-ignore a11y_click_events_have_key_events -->
 96		<!-- svelte-ignore a11y_no_static_element_interactions -->
 97		<div class="bg-panel border border-border-strong rounded-xl shadow-2xl w-full max-w-md mx-4" onclick={(e) => e.stopPropagation()}>
 98			<div class="px-5 py-4 border-b border-border">
 99				<h2 class="text-lg font-semibold text-primary">Export Review</h2>
100				<p class="text-sm text-tertiary mt-1">Send your review comments to AI or save for reference</p>
101			</div>
102
103			<div class="p-5 space-y-2">
104				<button
105					class="w-full text-left px-4 py-3 rounded-lg border border-border-strong hover:bg-hover/50 transition-colors group"
106					onclick={() => exportAs('claude')}
107					disabled={exporting}
108				>
109					<div class="text-sm font-medium text-primary group-hover:opacity-80">Send to Claude Code</div>
110					<div class="text-xs text-tertiary mt-0.5">Stream review to Claude with live output in terminal</div>
111				</button>
112
113				<button
114					class="w-full text-left px-4 py-3 rounded-lg border transition-all {clipboardState === 'ready' ? 'border-accent-blue bg-accent-blue/10 ring-1 ring-accent-blue/30' : clipboardState === 'copied' ? 'border-accent-green bg-accent-green/10' : 'border-border-strong hover:bg-hover/50'} group"
115					onclick={() => clipboardState === 'ready' ? copyNow() : exportAs('clipboard')}
116					disabled={exporting || clipboardState === 'loading'}
117				>
118					{#if clipboardState === 'loading'}
119						<div class="text-sm font-medium text-primary flex items-center gap-2">
120							<span class="animate-spin">⏳</span> Preparing content...
121						</div>
122						<div class="text-xs text-tertiary mt-0.5">Generating review text</div>
123					{:else if clipboardState === 'ready'}
124						<div class="text-sm font-medium text-accent-blue flex items-center gap-2">
125							📋 Content ready — click to copy
126						</div>
127						<div class="text-xs text-accent-blue/70 mt-0.5">Click now to copy to clipboard</div>
128					{:else if clipboardState === 'copied'}
129						<div class="text-sm font-medium text-accent-green flex items-center gap-2">
130							✓ Copied to clipboard!
131						</div>
132					{:else}
133						<div class="text-sm font-medium text-primary group-hover:opacity-80">Copy to Clipboard</div>
134						<div class="text-xs text-tertiary mt-0.5">Copy structured review text for any AI tool</div>
135					{/if}
136				</button>
137
138				<button
139					class="w-full text-left px-4 py-3 rounded-lg border border-border-strong hover:bg-hover/50 transition-colors group"
140					onclick={() => exportAs('json')}
141					disabled={exporting}
142				>
143					<div class="text-sm font-medium text-primary group-hover:opacity-80">Export as JSON</div>
144					<div class="text-xs text-tertiary mt-0.5">Save .lcr-review.json in project root</div>
145				</button>
146
147				<button
148					class="w-full text-left px-4 py-3 rounded-lg border border-border-strong hover:bg-hover/50 transition-colors group"
149					onclick={() => exportAs('markdown')}
150					disabled={exporting}
151				>
152					<div class="text-sm font-medium text-primary group-hover:opacity-80">Export as Markdown</div>
153					<div class="text-xs text-tertiary mt-0.5">Save .lcr-review.md in project root</div>
154				</button>
155			</div>
156
157			{#if result}
158				<div class="px-5 pb-3">
159					<div class="text-sm rounded-lg px-3 py-2 {result.success ? 'bg-accent-green/10 text-accent-green' : 'bg-accent-red/10 text-accent-red'}">
160						{result.message || result.path || 'Done!'}
161					</div>
162				</div>
163			{/if}
164
165			<div class="px-5 py-3 border-t border-border flex justify-end">
166				<button
167					class="text-sm px-4 py-2 rounded text-tertiary hover:text-primary hover:bg-hover transition-colors"
168					onclick={close}
169				>
170					Close
171				</button>
172			</div>
173		</div>
174	</div>
175{/if}