Files
protocol-bicorder/bicorder-app/src/components/ExportControls.svelte
2025-12-18 10:24:27 -07:00

217 lines
5.6 KiB
Svelte

<script lang="ts">
import { createEventDispatcher } from 'svelte';
import type { BicorderState } from '../types';
export let data: BicorderState;
export let focusedMode: boolean = false;
const dispatch = createEventDispatcher<{
reset: void;
}>();
let showUploadDialog = false;
let isUploading = false;
// Gitea configuration
const GITEA_TOKEN = 'd495e72e955c00be2de0f1e18183f6a385b6e52c';
const GITEA_API_URL = 'https://git.medlab.host/api/v1';
const REPO_OWNER = 'ntnsndr';
const REPO_NAME = 'protocol-bicorder-data';
const REPO_URL = `https://git.medlab.host/${REPO_OWNER}/${REPO_NAME}`;
const APP_VERSION = '1.0.0';
function exportToJSON() {
const json = JSON.stringify(data, null, 2);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `bicorder-${data.metadata.protocol || 'diagnostic'}-${new Date().toISOString().slice(0, 10)}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
async function uploadReadings() {
isUploading = true;
try {
// Generate filename with timestamp
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5) + 'Z';
const filename = `bicorder-${timestamp}.json`;
const filepath = `readings/${filename}`;
// Create commit message
const protocolName = data.metadata.protocol || 'Unknown Protocol';
const analystName = data.metadata.analyst || 'Anonymous';
const commitMessage = `Bicorder reading: ${protocolName} by ${analystName} | Source: Protocol Bicorder v${data.version}`;
// Prepare the content (base64 encoded)
const jsonContent = JSON.stringify(data, null, 2);
const base64Content = btoa(unescape(encodeURIComponent(jsonContent)));
// Get current timestamp for commit
const now = new Date();
const commitDate = now.toISOString();
// Upload to Gitea
const response = await fetch(
`${GITEA_API_URL}/repos/${REPO_OWNER}/${REPO_NAME}/contents/${filepath}`,
{
method: 'POST',
mode: 'cors',
headers: {
'Authorization': `token ${GITEA_TOKEN}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
content: base64Content,
message: commitMessage,
branch: 'main',
dates: {
author: commitDate,
committer: commitDate,
},
}),
}
);
if (response.ok) {
alert('Successfully uploaded! Your reading is now public.');
showUploadDialog = false;
} else {
const errorData = await response.json();
throw new Error(errorData.message || `Upload failed: ${response.statusContents}`);
}
} catch (err) {
console.error('Upload error:', err);
alert(`Upload error: ${(err as Error).message}`);
} finally {
isUploading = false;
}
}
function handleReset() {
dispatch('reset');
}
</script>
<section class="export-controls" class:focused={focusedMode}>
<div class="button-group">
<button on:click={exportToJSON}>
💾 Export JSON
</button>
<button on:click={() => showUploadDialog = !showUploadDialog}>
📤 Upload
</button>
<button class="reset-btn" on:click={handleReset}>
🗑️ Reset All
</button>
</div>
{#if showUploadDialog}
<div class="webhook-config">
<p class="upload-confirmation">
Are you sure you are ready to share your readings publicly?
</p>
<p class="upload-terms">
Submitted readings are posted publicly and licensed to the public domain. By proceeding, you agree to these terms.
</p>
<p class="upload-repo-link">
Readings are posted to <a href={REPO_URL} target="_blank" rel="noopener noreferrer">this repository</a>.
</p>
<div class="webhook-buttons">
<button on:click={uploadReadings} disabled={isUploading}>
{isUploading ? 'Uploading...' : 'Proceed'}
</button>
<button on:click={() => showUploadDialog = false} disabled={isUploading}>Cancel</button>
</div>
</div>
{/if}
</section>
<style>
.export-controls {
margin: 2rem 0;
padding: 1rem;
border-top: 2px solid var(--border-color);
}
.export-controls.focused {
border-top: none;
}
.button-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 0.5rem;
margin-bottom: 1rem;
}
.reset-btn {
border-color: #ff0000;
color: #ff0000;
}
.reset-btn:hover {
background-color: #ff0000;
color: #ffffff;
}
.webhook-config {
margin-top: 1rem;
padding: 1rem;
border: 2px solid var(--border-color);
}
.upload-confirmation {
font-size: 1rem;
font-weight: bold;
margin-bottom: 1rem;
text-align: center;
}
.upload-terms {
font-size: 0.85rem;
opacity: 0.7;
line-height: 1.4;
margin-bottom: 1rem;
text-align: center;
}
.upload-repo-link {
font-size: 0.9rem;
margin-bottom: 1rem;
text-align: center;
}
.upload-repo-link a {
color: var(--fg-color);
text-decoration: underline;
}
.upload-repo-link a:hover {
opacity: 0.7;
}
.webhook-buttons {
display: flex;
gap: 0.5rem;
margin-top: 0.5rem;
}
.webhook-buttons button {
flex: 1;
}
@media (max-width: 768px) {
.button-group {
grid-template-columns: 1fr;
}
}
</style>