diff --git a/src-tauri/plugins/tauri-plugin-native-utils/permissions/schemas/schema.json b/src-tauri/plugins/tauri-plugin-native-utils/permissions/schemas/schema.json index d377e90..7d967d4 100644 --- a/src-tauri/plugins/tauri-plugin-native-utils/permissions/schemas/schema.json +++ b/src-tauri/plugins/tauri-plugin-native-utils/permissions/schemas/schema.json @@ -1,342 +1,303 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "PermissionFile", - "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", - "type": "object", - "properties": { - "default": { - "description": "The default permission set for the plugin", - "anyOf": [ - { - "$ref": "#/definitions/DefaultPermission" - }, - { - "type": "null" - } - ] - }, - "set": { - "description": "A list of permissions sets defined", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionSet" - } - }, - "permission": { - "description": "A list of inlined permissions", - "default": [], - "type": "array", - "items": { - "$ref": "#/definitions/Permission" - } - } - }, - "definitions": { - "DefaultPermission": { - "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", - "type": "object", - "required": [ - "permissions" - ], - "properties": { - "version": { - "description": "The version of the permission.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 1.0 - }, - "description": { - "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", - "type": [ - "string", - "null" - ] - }, - "permissions": { - "description": "All permissions this set contains.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PermissionSet": { - "description": "A set of direct permissions grouped together under a new name.", - "type": "object", - "required": [ - "description", - "identifier", - "permissions" - ], - "properties": { - "identifier": { - "description": "A unique identifier for the permission.", - "type": "string" - }, - "description": { - "description": "Human-readable description of what the permission does.", - "type": "string" - }, - "permissions": { - "description": "All permissions this set contains.", - "type": "array", - "items": { - "$ref": "#/definitions/PermissionKind" - } - } - } - }, - "Permission": { - "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", - "type": "object", - "required": [ - "identifier" - ], - "properties": { - "version": { - "description": "The version of the permission.", - "type": [ - "integer", - "null" - ], - "format": "uint64", - "minimum": 1.0 - }, - "identifier": { - "description": "A unique identifier for the permission.", - "type": "string" - }, - "description": { - "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", - "type": [ - "string", - "null" - ] - }, - "commands": { - "description": "Allowed or denied commands when using this permission.", - "default": { - "allow": [], - "deny": [] - }, - "allOf": [ - { - "$ref": "#/definitions/Commands" - } - ] - }, - "scope": { - "description": "Allowed or denied scoped when using this permission.", - "allOf": [ - { - "$ref": "#/definitions/Scopes" - } - ] - }, - "platforms": { - "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Target" - } - } - } - }, - "Commands": { - "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", - "type": "object", - "properties": { - "allow": { - "description": "Allowed command.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - }, - "deny": { - "description": "Denied command, which takes priority.", - "default": [], - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "Scopes": { - "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", - "type": "object", - "properties": { - "allow": { - "description": "Data that defines what is allowed by the scope.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - }, - "deny": { - "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", - "type": [ - "array", - "null" - ], - "items": { - "$ref": "#/definitions/Value" - } - } - } - }, - "Value": { - "description": "All supported ACL values.", - "anyOf": [ - { - "description": "Represents a null JSON value.", - "type": "null" - }, - { - "description": "Represents a [`bool`].", - "type": "boolean" - }, - { - "description": "Represents a valid ACL [`Number`].", - "allOf": [ - { - "$ref": "#/definitions/Number" - } - ] - }, - { - "description": "Represents a [`String`].", - "type": "string" - }, - { - "description": "Represents a list of other [`Value`]s.", - "type": "array", - "items": { - "$ref": "#/definitions/Value" - } - }, - { - "description": "Represents a map of [`String`] keys to [`Value`]s.", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Value" - } - } - ] - }, - "Number": { - "description": "A valid ACL number.", - "anyOf": [ - { - "description": "Represents an [`i64`].", - "type": "integer", - "format": "int64" - }, - { - "description": "Represents a [`f64`].", - "type": "number", - "format": "double" - } - ] - }, - "Target": { - "description": "Platform target.", - "oneOf": [ - { - "description": "MacOS.", - "type": "string", - "enum": [ - "macOS" - ] - }, - { - "description": "Windows.", - "type": "string", - "enum": [ - "windows" - ] - }, - { - "description": "Linux.", - "type": "string", - "enum": [ - "linux" - ] - }, - { - "description": "Android.", - "type": "string", - "enum": [ - "android" - ] - }, - { - "description": "iOS.", - "type": "string", - "enum": [ - "iOS" - ] - } - ] - }, - "PermissionKind": { - "type": "string", - "oneOf": [ - { - "description": "Enables the select_download_folder command without any pre-configured scope.", - "type": "string", - "const": "allow-select-download-folder", - "markdownDescription": "Enables the select_download_folder command without any pre-configured scope." - }, - { - "description": "Denies the select_download_folder command without any pre-configured scope.", - "type": "string", - "const": "deny-select-download-folder", - "markdownDescription": "Denies the select_download_folder command without any pre-configured scope." - }, - { - "description": "Enables the select_send_document command without any pre-configured scope.", - "type": "string", - "const": "allow-select-send-document", - "markdownDescription": "Enables the select_send_document command without any pre-configured scope." - }, - { - "description": "Denies the select_send_document command without any pre-configured scope.", - "type": "string", - "const": "deny-select-send-document", - "markdownDescription": "Denies the select_send_document command without any pre-configured scope." - }, - { - "description": "Enables the select_send_folder command without any pre-configured scope.", - "type": "string", - "const": "allow-select-send-folder", - "markdownDescription": "Enables the select_send_folder command without any pre-configured scope." - }, - { - "description": "Denies the select_send_folder command without any pre-configured scope.", - "type": "string", - "const": "deny-select-send-folder", - "markdownDescription": "Denies the select_send_folder command without any pre-configured scope." - }, - { - "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-select-download-folder`\n- `allow-select-send-document`\n- `allow-select-send-folder`", - "type": "string", - "const": "default", - "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-select-download-folder`\n- `allow-select-send-document`\n- `allow-select-send-folder`" - } - ] - } - } -} \ No newline at end of file + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PermissionFile", + "description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.", + "type": "object", + "properties": { + "default": { + "description": "The default permission set for the plugin", + "anyOf": [ + { + "$ref": "#/definitions/DefaultPermission" + }, + { + "type": "null" + } + ] + }, + "set": { + "description": "A list of permissions sets defined", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionSet" + } + }, + "permission": { + "description": "A list of inlined permissions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + } + } + }, + "definitions": { + "DefaultPermission": { + "description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.", + "type": "object", + "required": ["permissions"], + "properties": { + "version": { + "description": "The version of the permission.", + "type": ["integer", "null"], + "format": "uint64", + "minimum": 1.0 + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": ["string", "null"] + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionSet": { + "description": "A set of direct permissions grouped together under a new name.", + "type": "object", + "required": ["description", "identifier", "permissions"], + "properties": { + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does.", + "type": "string" + }, + "permissions": { + "description": "All permissions this set contains.", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionKind" + } + } + } + }, + "Permission": { + "description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.", + "type": "object", + "required": ["identifier"], + "properties": { + "version": { + "description": "The version of the permission.", + "type": ["integer", "null"], + "format": "uint64", + "minimum": 1.0 + }, + "identifier": { + "description": "A unique identifier for the permission.", + "type": "string" + }, + "description": { + "description": "Human-readable description of what the permission does. Tauri internal convention is to use `

` headings in markdown content for Tauri documentation generation purposes.", + "type": ["string", "null"] + }, + "commands": { + "description": "Allowed or denied commands when using this permission.", + "default": { + "allow": [], + "deny": [] + }, + "allOf": [ + { + "$ref": "#/definitions/Commands" + } + ] + }, + "scope": { + "description": "Allowed or denied scoped when using this permission.", + "allOf": [ + { + "$ref": "#/definitions/Scopes" + } + ] + }, + "platforms": { + "description": "Target platforms this permission applies. By default all platforms are affected by this permission.", + "type": ["array", "null"], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "Commands": { + "description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.", + "type": "object", + "properties": { + "allow": { + "description": "Allowed command.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + }, + "deny": { + "description": "Denied command, which takes priority.", + "default": [], + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "Scopes": { + "description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```", + "type": "object", + "properties": { + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": ["array", "null"], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": ["array", "null"], + "items": { + "$ref": "#/definitions/Value" + } + } + } + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": ["macOS"] + }, + { + "description": "Windows.", + "type": "string", + "enum": ["windows"] + }, + { + "description": "Linux.", + "type": "string", + "enum": ["linux"] + }, + { + "description": "Android.", + "type": "string", + "enum": ["android"] + }, + { + "description": "iOS.", + "type": "string", + "enum": ["iOS"] + } + ] + }, + "PermissionKind": { + "type": "string", + "oneOf": [ + { + "description": "Enables the select_download_folder command without any pre-configured scope.", + "type": "string", + "const": "allow-select-download-folder", + "markdownDescription": "Enables the select_download_folder command without any pre-configured scope." + }, + { + "description": "Denies the select_download_folder command without any pre-configured scope.", + "type": "string", + "const": "deny-select-download-folder", + "markdownDescription": "Denies the select_download_folder command without any pre-configured scope." + }, + { + "description": "Enables the select_send_document command without any pre-configured scope.", + "type": "string", + "const": "allow-select-send-document", + "markdownDescription": "Enables the select_send_document command without any pre-configured scope." + }, + { + "description": "Denies the select_send_document command without any pre-configured scope.", + "type": "string", + "const": "deny-select-send-document", + "markdownDescription": "Denies the select_send_document command without any pre-configured scope." + }, + { + "description": "Enables the select_send_folder command without any pre-configured scope.", + "type": "string", + "const": "allow-select-send-folder", + "markdownDescription": "Enables the select_send_folder command without any pre-configured scope." + }, + { + "description": "Denies the select_send_folder command without any pre-configured scope.", + "type": "string", + "const": "deny-select-send-folder", + "markdownDescription": "Denies the select_send_folder command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-select-download-folder`\n- `allow-select-send-document`\n- `allow-select-send-folder`", + "type": "string", + "const": "default", + "markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-select-download-folder`\n- `allow-select-send-document`\n- `allow-select-send-folder`" + } + ] + } + } +} diff --git a/web-app/src/components/AppFooter.tsx b/web-app/src/components/AppFooter.tsx index 200a047..ae43089 100644 --- a/web-app/src/components/AppFooter.tsx +++ b/web-app/src/components/AppFooter.tsx @@ -1,7 +1,7 @@ import { buttonVariants } from './ui/button' import { CoffeeIcon, GithubIcon, GlobeIcon, SettingsIcon } from 'lucide-react' import { useTranslation } from '@/i18n' -import { AppVersion } from './AppVersionPayload' +import { VERSION_DISPLAY } from '@/lib/version' import { Separator } from './ui/separator' import { Link } from 'react-router-dom' import { handleExternalLinkClick } from '@/lib/openExternalUrl' @@ -29,7 +29,9 @@ export function AppFooter() { return (
- + + {VERSION_DISPLAY} + {CONTACTS.map((contact) => ( diff --git a/web-app/src/components/common/PulseAnimation.tsx b/web-app/src/components/common/PulseAnimation.tsx index 4532ad0..c65256b 100644 --- a/web-app/src/components/common/PulseAnimation.tsx +++ b/web-app/src/components/common/PulseAnimation.tsx @@ -1,6 +1,7 @@ import Lottie from 'lottie-react' import pulseAnimationOriginal from '../../assets/pulse.json' import { useMemo } from 'react' +import { cn } from '@/lib/utils' interface PulseAnimationProps { isTransporting: boolean @@ -50,7 +51,7 @@ export function PulseAnimation({ }, [isTransporting, hasActiveConnections]) return ( -
+
= 1) { return `${mbps.toFixed(2)} MB/s` } else { @@ -17,16 +16,146 @@ export function formatSpeed(speedBps: number): string { } } +// ─── Circular segmented ring (mobile only) ─────────────────────────────────── +// +// 30 thin arc segments arranged clockwise from the top (12 o'clock). +// Each segment occupies (360 / 30) = 12° with a small gap between them. +// Fill logic mirrors the horizontal bar: full segments + one partial segment. + +const SEGMENT_COUNT = 30 +const SEGMENT_KEYS = Array.from( + { length: SEGMENT_COUNT }, + (_, i) => `segment-${i}` +) +const SEGMENT_ANGLE = 360 / SEGMENT_COUNT // 12° per segment +const GAP_ANGLE = 2.2 // degrees of gap on each side +const ARC_ANGLE = SEGMENT_ANGLE - GAP_ANGLE * 2 + +const RING_SIZE = 200 // SVG viewBox size (px) +const CENTER = RING_SIZE / 2 // 100 +const RADIUS = 84 // arc radius +const STROKE_WIDTH = 4.5 // thinner stroke for a sleeker look + +/** Convert polar coordinates (angle from 12 o'clock, clockwise) to Cartesian. */ +function polarToCartesian(cx: number, cy: number, r: number, angleDeg: number) { + const angleRad = ((angleDeg - 90) * Math.PI) / 180 + return { + x: cx + r * Math.cos(angleRad), + y: cy + r * Math.sin(angleRad), + } +} + +/** Build an SVG arc path for a segment starting at `startAngle` spanning `sweep` degrees. */ +function arcPath(startAngle: number, sweep: number): string { + const start = polarToCartesian(CENTER, CENTER, RADIUS, startAngle) + const end = polarToCartesian(CENTER, CENTER, RADIUS, startAngle + sweep) + const largeArc = sweep > 180 ? 1 : 0 + return `M ${start.x} ${start.y} A ${RADIUS} ${RADIUS} 0 ${largeArc} 1 ${end.x} ${end.y}` +} + +interface CircularRingProps { + percentage: number +} + +function CircularRing({ percentage }: CircularRingProps) { + const { t } = useTranslation() + const filledSegments = Math.floor((percentage / 100) * SEGMENT_COUNT) + + // How far into the current (partial) segment we are, as a 0–1 fraction + const partialFraction = (percentage / 100) * SEGMENT_COUNT - filledSegments + + return ( + + {SEGMENT_KEYS.map((segmentKey, index) => { + const segmentStartAngle = index * SEGMENT_ANGLE + GAP_ANGLE + + const isFilled = index < filledSegments + const isPartial = index === filledSegments && partialFraction > 0 + + // For the partial segment we shorten the visible arc proportionally + const visibleSweep = isFilled + ? ARC_ANGLE + : isPartial + ? ARC_ANGLE * partialFraction + : 0 + + return ( + + {/* Background (unfilled) arc */} + + {/* Filled arc (rendered on top) */} + {visibleSweep > 0 && ( + + )} + + ) + })} + + ) +} + +// ─── Main component ─────────────────────────────────────────────────────────── + export function TransferProgressBar({ progress }: TransferProgressBarProps) { const { percentage } = progress const barCount = 30 const { t } = useTranslation() - const filledBars = Math.floor((percentage / 100) * barCount) return (
-
+ {/* ── Mobile layout: circular ring ── */} +
+
+ + + {/* Labels centred inside the ring */} +
+ + {percentage.toFixed(1)}% + + + {formatSpeed(progress.speedBps)} + + + {(progress.bytesTransferred / (1024 * 1024)).toFixed(2)} /{' '} + {(progress.totalBytes / (1024 * 1024)).toFixed(2)} MB + + {progress.etaSeconds !== undefined && ( + + {t('common:transfer.eta')}: {formatETA(progress.etaSeconds)} + + )} +
+
+
+ + {/* ── Desktop layout: horizontal segment bars ── */} +
{t('common:transfer.progress')} {percentage.toFixed(1)}% @@ -52,10 +181,7 @@ export function TransferProgressBar({ progress }: TransferProgressBarProps) { // biome-ignore lint/suspicious/noArrayIndexKey: The values are always static so it is okay key={index} className="relative flex-1 rounded-sm bg-input transition-all duration-300 ease-in-out" - style={{ - minWidth: '3px', - height: '100%', - }} + style={{ minWidth: '3px', height: '100%' }} >
{t('common:transfer.open')} diff --git a/web-app/src/components/layouts/RootLayout.tsx b/web-app/src/components/layouts/RootLayout.tsx index 128c2fe..1123faf 100644 --- a/web-app/src/components/layouts/RootLayout.tsx +++ b/web-app/src/components/layouts/RootLayout.tsx @@ -11,7 +11,7 @@ export function RootLayout() { return ( <> {!IS_ANDROID && } -
+
{IS_LINUX && !IS_ANDROID && } {IS_MACOS && ( diff --git a/web-app/src/components/receiver/Receiver.tsx b/web-app/src/components/receiver/Receiver.tsx index 84b59c8..15d5ac5 100644 --- a/web-app/src/components/receiver/Receiver.tsx +++ b/web-app/src/components/receiver/Receiver.tsx @@ -50,7 +50,7 @@ export function Receiver({ onTransferStateChange }: ReceiverProps) { }, [isReceiving, onTransferStateChange]) return ( -
+
{!isReceiving ? ( <>
@@ -63,7 +63,7 @@ export function Receiver({ onTransferStateChange }: ReceiverProps) { type="button" variant="ghost" onClick={() => setShowInstructionsDialog(true)} - className="absolute top-6 right-6" + className="absolute top-0 right-0 sm:top-6 sm:right-6" > diff --git a/web-app/src/components/receiver/ReceivingActiveCard.tsx b/web-app/src/components/receiver/ReceivingActiveCard.tsx index d538a3d..094ba95 100644 --- a/web-app/src/components/receiver/ReceivingActiveCard.tsx +++ b/web-app/src/components/receiver/ReceivingActiveCard.tsx @@ -42,7 +42,9 @@ export function ReceivingActiveCard({ />
-

{t('common:receiver.keepAppOpen')}

+

+ {t('common:receiver.keepAppOpen')} +

{isTransporting && transferProgress && ( @@ -53,7 +55,7 @@ export function ReceivingActiveCard({ size="icon-lg" type="button" onClick={onStopReceiving} - className="absolute top-0 right-6 rounded-full" + className="absolute top-0 right-2 sm:right-6 rounded-full font-medium transition-colors not-disabled:not-active:not-data-pressed:before:shadow-none dark:not-disabled:before:shadow-none dark:not-disabled:not-active:not-data-pressed:before:shadow-none" aria-label="Stop receiving" > diff --git a/web-app/src/components/sender/BrowseButtons.tsx b/web-app/src/components/sender/BrowseButtons.tsx index 99388fd..b567a19 100644 --- a/web-app/src/components/sender/BrowseButtons.tsx +++ b/web-app/src/components/sender/BrowseButtons.tsx @@ -12,42 +12,48 @@ export function BrowseButtons({ const { t } = useTranslation() return ( - - - - + +
+ +
+ +
+ +
) } diff --git a/web-app/src/components/sender/Dropzone.tsx b/web-app/src/components/sender/Dropzone.tsx index ac81f11..65eb77e 100644 --- a/web-app/src/components/sender/Dropzone.tsx +++ b/web-app/src/components/sender/Dropzone.tsx @@ -56,6 +56,9 @@ export function Dropzone({ const getSubText = () => { if (isLoading) return t('common:sender.pleaseWaitProcessing') if (selectedPath) { + const fileName = selectedPath.split('/').pop() ?? '' + const displayName = + fileName.length > 60 ? `${fileName.slice(0, 60)}…` : fileName return (
- {selectedPath.split('/').pop()} - + {displayName} + {showFullPath ? ( ) : ( @@ -73,7 +76,7 @@ export function Dropzone({
-

+

{getStatusText()}

{getSubText()}
diff --git a/web-app/src/components/sender/Sender.tsx b/web-app/src/components/sender/Sender.tsx index 1fa52d5..77a3621 100644 --- a/web-app/src/components/sender/Sender.tsx +++ b/web-app/src/components/sender/Sender.tsx @@ -51,35 +51,6 @@ export function Sender({ onTransferStateChange }: SenderProps) { const { t } = useTranslation() const setIsBroadcastMode = useSenderStore((state) => state.setIsBroadcastMode) - // Debug logging - useEffect(() => { - // console.log('[Sender] viewState changed:', viewState, { - // isSharing, - // isTransporting, - // transferMetadata: !!transferMetadata, - // transferMetadataDetails: transferMetadata ? { - // fileName: transferMetadata.fileName, - // wasStopped: transferMetadata.wasStopped, - // } : null, - // isBroadcastMode, - // selectedPath, - // }) - - // Warn if SUCCESS state without metadata - if (viewState === 'SUCCESS' && !transferMetadata) { - console.error( - '[Sender] ⚠️ INVALID STATE: SUCCESS without transferMetadata!', - { - viewState, - transferMetadata, - isSharing, - isTransporting, - selectedPath, - } - ) - } - }, [viewState, isSharing, isTransporting, transferMetadata, selectedPath]) - useEffect(() => { onTransferStateChange(isSharing) }, [isSharing, onTransferStateChange]) @@ -92,7 +63,7 @@ export function Sender({ onTransferStateChange }: SenderProps) { }, [viewState, isBroadcastMode, setIsBroadcastMode]) return ( -
+
{/* IDLE state: Show file selection UI */} {viewState === 'IDLE' && ( <> @@ -137,7 +108,7 @@ export function Sender({ onTransferStateChange }: SenderProps) { !isBroadcastMode) || (viewState === 'SHARING' && isBroadcastMode)) && ( <> -
+
-

+

{t('common:sender.fileLabel')}{' '} {selectedPath?.split('/').pop()}

@@ -213,7 +213,7 @@ export function SharingActiveCard({ type="button" onClick={onStopSharing} variant="destructive-outline" - className="absolute top-0 right-6 rounded-full font-medium transition-colors not-disabled:not-active:not-data-pressed:before:shadow-none dark:not-disabled:before:shadow-none dark:not-disabled:not-active:not-data-pressed:before:shadow-none" + className="absolute top-0 right-0 sm:right-6 rounded-full font-medium transition-colors not-disabled:not-active:not-data-pressed:before:shadow-none dark:not-disabled:before:shadow-none dark:not-disabled:not-active:not-data-pressed:before:shadow-none" aria-label="Stop sharing" > @@ -242,7 +242,7 @@ export function TicketDisplay({

{isBroadcastMode !== undefined && onToggleBroadcast && (
-