Files
OSIT-AE-App-Svelte/src/routes/journals/ae_comp__modal_journal_export.svelte
Scott Idem 2f3125c64b style(journals): apply expanded 80-width formatting and snake_case
- Batch formatted all Journals module files using Prettier with printWidth: 80.
- Refactored preventDefault to prevent_default across all Svelte components.
- Standardized line breaks for imports and long attribute lists for better readability.
- Ensured consistent snake_case naming for internal identifiers.
2026-02-06 14:15:43 -05:00

220 lines
7.2 KiB
Svelte

<script lang="ts">
/**
* @file ae_comp__modal_journal_export.svelte
* @description Modal component for bulk exporting journal entries.
* Allows exporting entries using various templates (Markdown, HTML, JSON).
* @author One Sky IT
*/
import { Modal } from 'flowbite-svelte';
import {
Download,
Copy,
FileJson,
FileType,
Code,
Settings2
} from 'lucide-svelte';
import { ae_util } from '$lib/ae_utils/ae_utils';
import type { ae_JournalEntry, ae_Journal } from '$lib/types/ae_types';
import {
EXPORT_TEMPLATES,
type ExportTemplate
} from '$lib/ae_journals/ae_journals_export_templates';
interface Props {
open: boolean;
entries: ae_JournalEntry[];
journal?: ae_Journal;
on_close: () => void;
}
let {
open = $bindable(false),
entries = [],
journal,
on_close
}: Props = $props();
// State
let selected_template_id: string = $state('standard_markdown');
let export_preview: string = $state('');
let export_count: number = $derived(entries.length);
const templates = Object.values(EXPORT_TEMPLATES);
const selected_template = $derived(
EXPORT_TEMPLATES[
selected_template_id as keyof typeof EXPORT_TEMPLATES
] || EXPORT_TEMPLATES.standard_markdown
);
// Auto-select template based on journal type when modal opens
$effect(() => {
if (open && journal?.type_code) {
if (
journal.type_code === 'personal_log' &&
EXPORT_TEMPLATES.personal_log
) {
selected_template_id = 'personal_log';
} else if (
journal.type_code === 'amazon_vine' &&
EXPORT_TEMPLATES.amazon_vine
) {
selected_template_id = 'amazon_vine';
}
}
});
// Re-generate preview when entries or template changes
$effect(() => {
if (open && entries.length > 0) {
generate_preview();
}
});
function generate_preview() {
if (!selected_template) return;
export_preview = selected_template.formatter(entries);
}
/**
* Downloads the generated content as a file.
*/
function handle_download() {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `journal_export_${timestamp}.${selected_template.extension}`;
const blob = new Blob([export_preview], {
type: 'text/plain;charset=utf-8'
});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
/**
* Copies the generated content to the clipboard.
*/
async function handle_copy() {
try {
await navigator.clipboard.writeText(export_preview);
alert('Content copied to clipboard!');
} catch (err) {
console.error('Failed to copy:', err);
alert('Failed to copy to clipboard.');
}
}
</script>
<Modal
title="Export Journal Entries"
bind:open
autoclose={false}
size="xl"
class="w-full"
>
<div class="space-y-4">
<div
class="flex items-center justify-between p-4 bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-200 rounded-lg"
>
<div>
<strong>Ready to Export:</strong>
{export_count} entries.
</div>
{#if journal}
<div class="text-xs opacity-70">
Journal Type: <span class="font-mono"
>{journal.type_code || 'standard'}</span
>
</div>
{/if}
</div>
<!-- Template Selection -->
<div class="space-y-2">
<label class="label flex items-center gap-2">
<Settings2 size="1em" />
<span class="label-text font-bold">Select Export Template</span>
</label>
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-2">
{#each templates as template}
<button
type="button"
class="btn variant-ringed-surface flex flex-col gap-1 p-2 h-24 items-center justify-center border-2 text-center {selected_template_id ===
template.id
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
: 'opacity-60'}"
onclick={() => {
selected_template_id = template.id;
generate_preview();
}}
>
{#if template.extension === 'json'}
<FileJson size="1.5em" />
{:else if template.extension === 'html'}
<Code size="1.5em" />
{:else}
<FileType size="1.5em" />
{/if}
<span class="text-xs font-bold leading-tight"
>{template.name}</span
>
<span class="text-[10px] opacity-70"
>.{template.extension}</span
>
</button>
{/each}
</div>
</div>
<!-- Preview Area -->
<div class="form-control">
<div class="label flex justify-between items-center">
<span class="label-text">Preview</span>
<span class="text-[10px] opacity-50 uppercase tracking-widest"
>{selected_template.name}</span
>
</div>
<textarea
class="textarea h-64 font-mono text-xs bg-gray-50 dark:bg-gray-900"
readonly
value={export_preview.substring(0, 5000) +
(export_preview.length > 5000
? '\n... (truncated for preview)'
: '')}
></textarea>
</div>
<!-- Actions -->
<div class="modal-action flex justify-between items-center">
<button
type="button"
class="btn preset-tonal-secondary"
onclick={on_close}>Close</button
>
<div class="flex gap-2">
<button
type="button"
class="btn variant-soft-primary"
onclick={handle_copy}
>
<Copy class="mr-2" size="1.2em" /> Copy to Clipboard
</button>
<button
type="button"
class="btn preset-filled-primary"
onclick={handle_download}
>
<Download class="mr-2" size="1.2em" /> Download File
</button>
</div>
</div>
</div>
</Modal>