Skip to content
Open
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
8 changes: 8 additions & 0 deletions servers/demo/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,11 @@ export const createExpenseReportClientUrl = async ({ endpointId }: { endpointId:
export const createDeExpenseReportClientUrl = async ({ endpointId }: { endpointId: string }) => {
return `${window.location.origin}${base}/belegeinreichung-client/${endpointId}`;
};

export const createFlottformFileSharingClientUrl = async ({
endpointId
}: {
endpointId: string;
}) => {
return `${window.location.origin}${base}/flottform-file-sharing-client/${endpointId}`;
};
27 changes: 27 additions & 0 deletions servers/demo/src/lib/components/FileExchangeProgress.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
export let exchangeFilesMetaData: {
fileName: string;
fileIndex: number;
currentFileProgress: number;
totalFileCount: number;
} = { fileName: '', fileIndex: 0, currentFileProgress: 0, totalFileCount: 0 };
export let fileExchangeType: 'sending' | 'receiving' = 'sending';
</script>

<div class="m-4">
<h3 class="text-sm font-medium mb-2">
{fileExchangeType === 'sending' ? 'sending' : 'receiving'} ({exchangeFilesMetaData.currentFileProgress}%):
{exchangeFilesMetaData.fileName}
</h3>

<div class="w-full overflow-hidden rounded bg-[#f1f5f9] h-2">
<div
class="transition-[width] duration-300 ease-[ease] bg-[#3b82f6] h-full"
style="width: {(exchangeFilesMetaData.fileIndex / exchangeFilesMetaData.totalFileCount) *
100}%"
></div>
</div>
<p class="text-right text-[#64748b] text-sm mt-4">
{exchangeFilesMetaData.fileIndex} of {exchangeFilesMetaData.totalFileCount} files
</p>
</div>
38 changes: 38 additions & 0 deletions servers/demo/src/lib/components/ReceivedFilesList.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
let { receivedFiles, removeFile } = $props<{
receivedFiles: { url: string; name: string }[];
removeFile: (index: number) => void;
}>();
let filesContainer: HTMLDivElement | null = null;
function scrollToBottomOfMessages() {
if (filesContainer) {
filesContainer.scrollTop = filesContainer.scrollHeight;
}
}
$effect(() => {
if (receivedFiles.length > 0) {
scrollToBottomOfMessages();
}
});
</script>

<div class="h-[50vh] overflow-y-auto scroll-smooth" bind:this={filesContainer}>
{#each receivedFiles as file, index}
<li class="flex items-center justify-between p-3 m-2 bg-gray-100 rounded-lg">
<span class="text-sm">{file.name}</span>
<div class="flex flex-col">
<a href={file.url} download={file.name} class="text-green-500 hover:underline text-sm">
Download
</a>
<button
class="text-red-500 hover:underline text-sm"
onclick={() => {
removeFile(index);
}}
>
Delete
</button>
</div>
</li>
{/each}
</div>
5 changes: 5 additions & 0 deletions servers/demo/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,10 @@
title="Customized default UI"
description="See how you can tailor Flottform's default UI to better match your design, while still retaining all the powerful features of the original interface. This demo lets you explore how easy it is to adapt the default elements to fit seamlessly with your brand's style."
/>
<DemoLink
href="{base}/flottform-file-sharing"
title="Flottform File Sharing"
description="See how you can use Flottform to exchange files between two peers!"
/>
</div>
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<script lang="ts">
import { FlottformFileInputClient } from '@flottform/forms';
import { onMount, tick } from 'svelte';
import { page } from '$app/stores';
import { sdpExchangeServerBase } from '../../../api';
import FileExchangeProgress from '$lib/components/FileExchangeProgress.svelte';
import ReceivedFilesList from '$lib/components/ReceivedFilesList.svelte';
let connectionStatus = 'init';
let sendFiles: () => void;
let stopFileTransfer: () => void;
let error: string = '';
let outgoingInputField: HTMLInputElement;
const addReceivedFile = (receivedFile: File) => {
const fileDownloadUrl = URL.createObjectURL(receivedFile);
receivedFiles = [...receivedFiles, { name: receivedFile.name, url: fileDownloadUrl }];
};
const removeFile = (fileIndex: number) => {
URL.revokeObjectURL(receivedFiles[fileIndex].url);
receivedFiles = receivedFiles.filter((_, i) => i !== fileIndex);
};
let receivedFiles: { url: string; name: string }[] = [];
let sendingFilesMetaData = {
sending: false,
fileName: '',
fileIndex: 0,
currentFileSendingProgress: 0,
totalFileCount: 0
};
let receivingFilesMetaData = {
receiving: false,
fileName: '',
fileIndex: 0,
currentFileReceivingProgress: 0,
totalFileCount: 0,
overallProgress: 0
};
onMount(() => {
const flottformFileInputClient = new FlottformFileInputClient({
flottformApi: sdpExchangeServerBase,
endpointId: $page.params.endpointId,
outgoingInputField
});
flottformFileInputClient.start();
flottformFileInputClient.on('init', () => {
sendFiles = flottformFileInputClient.sendFiles;
stopFileTransfer = flottformFileInputClient.close;
connectionStatus = 'init';
});
flottformFileInputClient.on('connected', async () => {
connectionStatus = 'connected';
await tick();
if (outgoingInputField) {
flottformFileInputClient.setOutgoingInputField(outgoingInputField);
}
});
flottformFileInputClient.on(
'file-sending-progress',
({ fileIndex, fileName, currentFileProgress, totalFileCount }) => {
sendingFilesMetaData = {
sending: true,
currentFileSendingProgress: Number((currentFileProgress * 100).toFixed(2)),
fileName,
fileIndex,
totalFileCount
};
}
);
flottformFileInputClient.on('single-file-transfered', ({ name, type, size }) => {
sendingFilesMetaData = {
sending: false,
fileName: '',
fileIndex: 0,
currentFileSendingProgress: 0,
totalFileCount: 0
};
});
flottformFileInputClient.on(
'file-receiving-progress',
({ fileIndex, totalFileCount, fileName, currentFileProgress, overallProgress }) => {
receivingFilesMetaData = {
receiving: true,
currentFileReceivingProgress: Number((currentFileProgress * 100).toFixed(2)),
fileName,
fileIndex,
totalFileCount,
overallProgress
};
}
);
flottformFileInputClient.on('single-file-received', (fileReceived) => {
addReceivedFile(fileReceived);
receivingFilesMetaData = {
receiving: false,
fileName: '',
fileIndex: 0,
currentFileReceivingProgress: 0,
totalFileCount: 0,
overallProgress: 0
};
});
flottformFileInputClient.on('disconnected', () => {
connectionStatus = 'disconnected';
});
flottformFileInputClient.on('error', (e) => {
connectionStatus = 'error';
error = e;
});
});
</script>

<div class="min-h-screen flex items-center justify-center overflow-hidden bg-gray-50">
<div class="max-w-screen-xl w-full p-4 box-border flex flex-col items-center">
<h1 class="text-2xl font-bold text-gray-800 mb-8">Flottform File Sharing - Client</h1>
<div class="w-full max-w-md bg-white shadow-lg rounded-lg">
{#if connectionStatus === 'init'}
<p class="text-gray-700 text-center mb-6">Connecting to the host...</p>
{:else if connectionStatus === 'connected'}
<div class="p-6 bg-white rounded-lg shadow-md">
<h2 class="text-xl font-semibold mb-4">File Sharing</h2>
<!-- Send Files -->
<div class="mb-6">
<h3 class="text-lg font-medium mb-2">Send one or multiple File</h3>
<form action="" onsubmit={sendFiles} class="flex flex-wrap gap-2">
<input
type="file"
class="flex-1 py-2 px-3 border rounded-lg"
bind:this={outgoingInputField}
multiple
/>
<button
type="submit"
disabled={sendingFilesMetaData.sending}
class="py-2 px-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-400 disabled:hover:bg-gray-400 disabled:cursor-not-allowed"
>
{sendingFilesMetaData.sending ? 'Sending...' : 'Send'}
</button>
</form>
{#if sendingFilesMetaData.sending}
<FileExchangeProgress
fileExchangeType="sending"
exchangeFilesMetaData={{
...sendingFilesMetaData,
currentFileProgress: sendingFilesMetaData.currentFileSendingProgress
}}
/>
{/if}
</div>

<!-- Received Files -->
<div>
<h3 class="text-lg font-medium mb-2">Received Files</h3>
<ul class="space-y-2">
{#if receivedFiles.length === 0}
<p class="italic text-sm">---No Files Received Yet---</p>
{:else}
<ReceivedFilesList {receivedFiles} {removeFile} />
{/if}
</ul>
{#if receivingFilesMetaData.receiving}
<FileExchangeProgress
fileExchangeType="receiving"
exchangeFilesMetaData={{
...receivingFilesMetaData,
currentFileProgress: receivingFilesMetaData.currentFileReceivingProgress
}}
/>
{/if}
</div>
<button
onclick={stopFileTransfer}
class="bg-[#660000] hover:bg-[#b30000] text-white px-4 py-2 rounded-lg text-base mt-4"
>Stop File Transfer</button
>
</div>
{:else if connectionStatus === 'disconnected'}
<div class="flex flex-col items-center w-full gap-4 p-8">
<p class="text-center">Channel is disconnected!</p>
<p class="text-center">
Do want to connect one more time? Scan the QR code or copy the link from the other
device!
</p>
</div>
{:else if connectionStatus === 'error'}
<div class="flex flex-col items-center w-full gap-4 p-8">
<p class="text-center text-red-500">
Connection Channel Failed with the following error: {error}
</p>
</div>
{/if}
</div>
</div>
</div>
Loading