feat: Add Upload demo view

pull/2838/head
Matthew Richardson 6 days ago
parent 80d4d8e128
commit 426627a591
No known key found for this signature in database
GPG Key ID: 94F48D0A011CC828

1
Cargo.lock generated

@ -233,6 +233,7 @@ dependencies = [
"tauri-plugin-shell", "tauri-plugin-shell",
"tauri-plugin-store", "tauri-plugin-store",
"tauri-plugin-updater", "tauri-plugin-updater",
"tauri-plugin-upload",
"tauri-plugin-window-state", "tauri-plugin-window-state",
"time", "time",
"tiny_http", "tiny_http",

@ -29,6 +29,7 @@
"@tauri-apps/plugin-shell": "^2.3.0", "@tauri-apps/plugin-shell": "^2.3.0",
"@tauri-apps/plugin-store": "^2.3.0", "@tauri-apps/plugin-store": "^2.3.0",
"@tauri-apps/plugin-updater": "^2.9.0", "@tauri-apps/plugin-updater": "^2.9.0",
"@tauri-apps/plugin-upload": "^2.3.0",
"@zerodevx/svelte-json-view": "1.0.11" "@zerodevx/svelte-json-view": "1.0.11"
}, },
"devDependencies": { "devDependencies": {

@ -38,6 +38,7 @@ tauri-plugin-process = { path = "../../../plugins/process", version = "2.3.0" }
tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.4.0" } tauri-plugin-opener = { path = "../../../plugins/opener", version = "2.4.0" }
tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.0" } tauri-plugin-shell = { path = "../../../plugins/shell", version = "2.3.0" }
tauri-plugin-store = { path = "../../../plugins/store", version = "2.3.0" } tauri-plugin-store = { path = "../../../plugins/store", version = "2.3.0" }
tauri-plugin-upload = { path = "../../../plugins/upload", version = "2.3.0" }
[dependencies.tauri] [dependencies.tauri]
workspace = true workspace = true

@ -95,6 +95,7 @@
{ {
"identifier": "opener:allow-open-path", "identifier": "opener:allow-open-path",
"allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }] "allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }]
} },
"upload:default"
] ]
} }

@ -39,6 +39,7 @@ pub fn run() {
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_store::Builder::default().build()) .plugin(tauri_plugin_store::Builder::default().build())
.plugin(tauri_plugin_upload::init())
.setup(move |app| { .setup(move |app| {
#[cfg(desktop)] #[cfg(desktop)]
{ {

@ -16,6 +16,7 @@
import Opener from './views/Opener.svelte' import Opener from './views/Opener.svelte'
import Store from './views/Store.svelte' import Store from './views/Store.svelte'
import Updater from './views/Updater.svelte' import Updater from './views/Updater.svelte'
import Upload from './views/Upload.svelte'
import Clipboard from './views/Clipboard.svelte' import Clipboard from './views/Clipboard.svelte'
import WebRTC from './views/WebRTC.svelte' import WebRTC from './views/WebRTC.svelte'
import Scanner from './views/Scanner.svelte' import Scanner from './views/Scanner.svelte'
@ -107,6 +108,11 @@
component: Updater, component: Updater,
icon: 'i-codicon-cloud-download' icon: 'i-codicon-cloud-download'
}, },
{
label: 'Upload',
component: Upload,
icon: 'i-codicon-cloud-upload'
},
{ {
label: 'Clipboard', label: 'Clipboard',
component: Clipboard, component: Clipboard,

@ -0,0 +1,376 @@
<script>
import { download, upload } from '@tauri-apps/plugin-upload'
import { open } from '@tauri-apps/plugin-dialog'
import { JsonView } from '@zerodevx/svelte-json-view'
import { appDataDir } from '@tauri-apps/api/path'
import { onMount } from 'svelte'
export let onMessage
let downloadUrl = 'https://httpbin.org/json'
let downloadFolder = ''
let downloadPath = ''
let downloadProgress = null
let downloadResult = null
let isDownloading = false
let uploadUrl = 'https://httpbin.org/post'
let uploadFilePath = ''
let uploadProgress = null
let uploadResult = null
let isUploading = false
onMount(async () => {
try {
const defaultDir = await appDataDir()
if (!downloadFolder) {
downloadFolder = defaultDir
updateDownloadPath()
}
} catch (error) {
onMessage({ error: `Failed to get default directory: ${error.toString()}` })
}
})
async function selectDownloadFolder() {
try {
const selected = await open({
directory: true,
multiple: false,
defaultPath: downloadFolder || undefined
})
if (selected) {
downloadFolder = selected
updateDownloadPath()
}
} catch (error) {
onMessage({ error: error.toString() })
}
}
function getFilenameFromUrl(url) {
try {
const urlObj = new URL(url)
let pathname = urlObj.pathname
// Remove leading slash
if (pathname.startsWith('/')) {
pathname = pathname.substring(1)
}
// If pathname is empty or ends with slash, use a default name
if (!pathname || pathname.endsWith('/')) {
return 'downloaded-file.json'
}
// Extract filename from pathname
const segments = pathname.split('/')
let filename = segments[segments.length - 1]
// If no extension, try to infer from URL or use default
if (!filename.includes('.')) {
// Check if URL suggests a file type
if (url.includes('json') || urlObj.searchParams.has('format') && urlObj.searchParams.get('format') === 'json') {
filename += '.json'
} else if (url.includes('xml')) {
filename += '.xml'
} else if (url.includes('csv')) {
filename += '.csv'
} else {
filename += '.txt'
}
}
return filename
} catch (error) {
return 'downloaded-file.json'
}
}
function updateDownloadPath() {
if (downloadFolder && downloadUrl) {
const filename = getFilenameFromUrl(downloadUrl)
downloadPath = `${downloadFolder}/${filename}`
} else {
downloadPath = ''
}
}
// Update download path when URL changes
$: if (downloadUrl) {
updateDownloadPath()
}
async function selectUploadFile() {
try {
const selected = await open({
directory: false,
multiple: false
})
if (selected) {
uploadFilePath = selected
}
} catch (error) {
onMessage({ error: error.toString() })
}
}
async function startDownload() {
if (!downloadUrl || !downloadFolder) {
onMessage({ error: 'Please provide both URL and download folder' })
return
}
// Ensure download path is updated
updateDownloadPath()
if (!downloadPath) {
onMessage({ error: 'Could not generate download path' })
return
}
isDownloading = true
downloadProgress = null
downloadResult = null
try {
await download(
downloadUrl,
downloadPath,
(progress) => {
downloadProgress = {
progress: progress.progress,
progressTotal: progress.progressTotal,
total: progress.total,
transferSpeed: progress.transferSpeed,
percentage: progress.total > 0 ? Math.round((progress.progressTotal / progress.total) * 100) : 0
}
},
new Map([
['User-Agent', 'Tauri Upload Plugin Demo']
])
)
downloadResult = {
success: true,
message: `File downloaded successfully to: ${downloadPath}`,
finalProgress: downloadProgress
}
onMessage({
type: 'download',
result: downloadResult
})
} catch (error) {
downloadResult = {
success: false,
error: error.toString()
}
onMessage({ error: error.toString() })
} finally {
isDownloading = false
}
}
async function startUpload() {
if (!uploadUrl || !uploadFilePath) {
onMessage({ error: 'Please provide both URL and file path' })
return
}
isUploading = true
uploadProgress = null
uploadResult = null
try {
const response = await upload(
uploadUrl,
uploadFilePath,
(progress) => {
uploadProgress = {
progress: progress.progress,
progressTotal: progress.progressTotal,
total: progress.total,
transferSpeed: progress.transferSpeed,
percentage: progress.total > 0 ? Math.round((progress.progressTotal / progress.total) * 100) : 0
}
},
new Map([
['User-Agent', 'Tauri Upload Plugin Demo']
])
)
uploadResult = {
success: true,
response: response,
finalProgress: uploadProgress
}
onMessage({
type: 'upload',
result: uploadResult
})
} catch (error) {
uploadResult = {
success: false,
error: error.toString()
}
onMessage({ error: error.toString() })
} finally {
isUploading = false
}
}
</script>
<div class="space-y-6">
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4 text-gray-800">File Download</h3>
<div class="space-y-3">
<div>
<label for="download-url" class="block text-sm font-medium text-gray-700 mb-1">Download URL:</label>
<input
id="download-url"
bind:value={downloadUrl}
type="url"
placeholder="https://example.com/file.json"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isDownloading}
/>
</div>
<div>
<label for="download-folder" class="block text-sm font-medium text-gray-700 mb-1">Download folder:</label>
<div class="flex gap-2">
<input
id="download-folder"
bind:value={downloadFolder}
type="text"
placeholder="Select download folder..."
class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isDownloading}
/>
<button
on:click={selectDownloadFolder}
class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 disabled:opacity-50"
disabled={isDownloading}
>
Browse
</button>
</div>
</div>
{#if downloadPath}
<div class="bg-blue-50 border border-blue-200 p-3 rounded-md">
<div class="text-sm text-blue-800">
<strong>File will be saved as:</strong>
<div class="font-mono text-xs mt-1 break-all">{downloadPath}</div>
</div>
</div>
{/if}
<button
on:click={startDownload}
class="w-full px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={isDownloading || !downloadUrl || !downloadFolder}
>
{isDownloading ? 'Downloading...' : 'Download File'}
</button>
{#if downloadProgress}
<div class="bg-white p-3 rounded border">
<div class="flex justify-between text-sm text-gray-600 mb-1">
<span>Progress: {downloadProgress.percentage}%</span>
<span>Speed: {Math.round(downloadProgress.transferSpeed / 1024)} KB/s</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div
class="bg-blue-500 h-2 rounded-full transition-all duration-300"
style="width: {downloadProgress.percentage}%"
></div>
</div>
<div class="text-xs text-gray-500 mt-1">
{Math.round(downloadProgress.progressTotal / 1024)} KB / {Math.round(downloadProgress.total / 1024)} KB
</div>
</div>
{/if}
{#if downloadResult}
<div class="bg-white p-3 rounded border">
<JsonView json={downloadResult} />
</div>
{/if}
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-lg font-semibold mb-4 text-gray-800">File Upload</h3>
<div class="space-y-3">
<div>
<label for="upload-url" class="block text-sm font-medium text-gray-700 mb-1">Upload URL:</label>
<input
id="upload-url"
bind:value={uploadUrl}
type="url"
placeholder="https://httpbin.org/post"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
disabled={isUploading}
/>
</div>
<div>
<label for="upload-file" class="block text-sm font-medium text-gray-700 mb-1">File to upload:</label>
<div class="flex gap-2">
<input
id="upload-file"
bind:value={uploadFilePath}
type="text"
placeholder="Select file to upload..."
class="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
disabled={isUploading}
/>
<button
on:click={selectUploadFile}
class="px-4 py-2 bg-gray-500 text-white rounded-md hover:bg-gray-600 disabled:opacity-50"
disabled={isUploading}
>
Browse
</button>
</div>
</div>
<button
on:click={startUpload}
class="w-full px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed"
disabled={isUploading || !uploadUrl || !uploadFilePath}
>
{isUploading ? 'Uploading...' : 'Upload File'}
</button>
{#if uploadProgress}
<div class="bg-white p-3 rounded border">
<div class="flex justify-between text-sm text-gray-600 mb-1">
<span>Progress: {uploadProgress.percentage}%</span>
<span>Speed: {Math.round(uploadProgress.transferSpeed / 1024)} KB/s</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div
class="bg-green-500 h-2 rounded-full transition-all duration-300"
style="width: {uploadProgress.percentage}%"
></div>
</div>
<div class="text-xs text-gray-500 mt-1">
{Math.round(uploadProgress.progressTotal / 1024)} KB / {Math.round(uploadProgress.total / 1024)} KB
</div>
</div>
{/if}
{#if uploadResult}
<div class="bg-white p-3 rounded border">
<JsonView json={uploadResult} />
</div>
{/if}
</div>
</div>
</div>

@ -110,6 +110,9 @@ importers:
'@tauri-apps/plugin-updater': '@tauri-apps/plugin-updater':
specifier: ^2.9.0 specifier: ^2.9.0
version: link:../../plugins/updater version: link:../../plugins/updater
'@tauri-apps/plugin-upload':
specifier: ^2.3.0
version: link:../../plugins/upload
'@zerodevx/svelte-json-view': '@zerodevx/svelte-json-view':
specifier: 1.0.11 specifier: 1.0.11
version: 1.0.11(svelte@5.28.2) version: 1.0.11(svelte@5.28.2)
@ -2339,9 +2342,9 @@ snapshots:
- encoding - encoding
- mocha - mocha
'@covector/assemble@0.12.0(mocha@10.8.2)': '@covector/assemble@0.12.0':
dependencies: dependencies:
'@covector/command': 0.8.0(mocha@10.8.2) '@covector/command': 0.8.0
'@covector/files': 0.8.0 '@covector/files': 0.8.0
effection: 2.0.8(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2)
js-yaml: 4.1.0 js-yaml: 4.1.0
@ -2352,10 +2355,9 @@ snapshots:
unified: 9.2.2 unified: 9.2.2
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- mocha
- supports-color - supports-color
'@covector/changelog@0.12.0(mocha@10.8.2)': '@covector/changelog@0.12.0':
dependencies: dependencies:
'@covector/files': 0.8.0 '@covector/files': 0.8.0
effection: 2.0.8(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2)
@ -2365,16 +2367,14 @@ snapshots:
unified: 9.2.2 unified: 9.2.2
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- mocha
- supports-color - supports-color
'@covector/command@0.8.0(mocha@10.8.2)': '@covector/command@0.8.0':
dependencies: dependencies:
'@effection/process': 2.1.4 '@effection/process': 2.1.4
effection: 2.0.8(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2)
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
- mocha
'@covector/files@0.8.0': '@covector/files@0.8.0':
dependencies: dependencies:
@ -2421,6 +2421,8 @@ snapshots:
dependencies: dependencies:
effection: 2.0.8(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2)
mocha: 10.8.2 mocha: 10.8.2
transitivePeerDependencies:
- encoding
'@effection/process@2.1.4': '@effection/process@2.1.4':
dependencies: dependencies:
@ -3258,9 +3260,9 @@ snapshots:
dependencies: dependencies:
'@clack/prompts': 0.7.0 '@clack/prompts': 0.7.0
'@covector/apply': 0.10.0(mocha@10.8.2) '@covector/apply': 0.10.0(mocha@10.8.2)
'@covector/assemble': 0.12.0(mocha@10.8.2) '@covector/assemble': 0.12.0
'@covector/changelog': 0.12.0(mocha@10.8.2) '@covector/changelog': 0.12.0
'@covector/command': 0.8.0(mocha@10.8.2) '@covector/command': 0.8.0
'@covector/files': 0.8.0 '@covector/files': 0.8.0
effection: 2.0.8(mocha@10.8.2) effection: 2.0.8(mocha@10.8.2)
globby: 11.1.0 globby: 11.1.0

Loading…
Cancel
Save