UX: Add "disconnect" option to context menu

This commit is contained in:
aleksey-hoffman committed 2026-07-06 14:37:13 +03:00
1 parent 2013cdb98d
commit 0d77129ff7
35 files changed
+426 -18

No files matched your search

+9
View File
@@ -82,6 +82,15 @@ pub fn unmount_drive(device_path: String, mount_point: String) -> Result<(), Str
mount::unmount_drive(device_path, mount_point)
}
#[tauri::command]
pub fn disconnect_drive(
device_path: String,
mount_point: String,
drive_type: String,
) -> Result<(), String> {
mount::disconnect_drive(device_path, mount_point, drive_type)
}
#[tauri::command]
pub fn mount_network_share(params: NetworkShareParams) -> Result<String, String> {
network_shares::mount_network_share(params)
+85
View File
@@ -63,6 +63,79 @@ pub fn mount_drive(device_path: String) -> Result<String, String> {
}
}
pub fn disconnect_drive(
device_path: String,
mount_point: String,
drive_type: String,
) -> Result<(), String> {
if drive_type == "Network" {
#[cfg(windows)]
{
return disconnect_windows_network_drive(&mount_point);
}
#[cfg(not(windows))]
{
return unmount_drive(device_path, mount_point);
}
}
#[cfg(windows)]
{
let _ = (device_path, mount_point);
return Err("Disconnect is not supported for this drive on Windows".to_string());
}
#[cfg(not(windows))]
{
unmount_drive(device_path, mount_point)
}
}
#[cfg(windows)]
fn disconnect_windows_network_drive(mount_point: &str) -> Result<(), String> {
let drive_letter = parse_windows_drive_letter(mount_point)
.ok_or_else(|| format!("Invalid network drive mount point: {}", mount_point.trim()))?;
let output = std::process::Command::new("net")
.args(["use", &drive_letter, "/delete", "/y"])
.output()
.map_err(|run_error| format!("Failed to run 'net use': {}", run_error))?;
if output.status.success() {
return Ok(());
}
let stderr = super::drives_platform::decode_windows_command_output(&output.stderr);
let stdout = super::drives_platform::decode_windows_command_output(&output.stdout);
let message = if !stderr.trim().is_empty() {
stderr
} else {
stdout
};
Err(format!("net use failed: {}", message.trim()))
}
#[cfg(windows)]
fn parse_windows_drive_letter(mount_point: &str) -> Option<String> {
let trimmed = mount_point.trim().trim_end_matches(['\\', '/']);
if trimmed.len() < 2 {
return None;
}
let mut characters = trimmed.chars();
let drive_letter = characters.next()?;
let colon = characters.next()?;
if colon != ':' || !drive_letter.is_ascii_alphabetic() {
return None;
}
Some(format!("{}:", drive_letter.to_ascii_uppercase()))
}
pub fn unmount_drive(device_path: String, mount_point: String) -> Result<(), String> {
#[cfg(target_os = "linux")]
{
@@ -136,3 +209,15 @@ fn linux_unmount(device_path: &str, mount_point: &str) -> Result<(), String> {
mount_point
))
}
#[cfg(all(test, windows))]
mod tests {
use super::parse_windows_drive_letter;
#[test]
fn parse_windows_drive_letter_accepts_common_mount_points() {
assert_eq!(parse_windows_drive_letter("Z:\\"), Some("Z:".to_string()));
assert_eq!(parse_windows_drive_letter("z:/"), Some("Z:".to_string()));
assert_eq!(parse_windows_drive_letter("Apps"), None);
}
}
+1
View File
@@ -308,6 +308,7 @@ pub fn run() {
dir_reader::get_mountable_devices,
dir_reader::mount_drive,
dir_reader::unmount_drive,
dir_reader::disconnect_drive,
dir_reader::mount_network_share,
dir_size::get_dir_size,
dir_size::get_dir_sizes_batch,
@@ -17,6 +17,7 @@ import PermanentDeleteConfirmDialog from '@/modules/navigator/components/file-br
import { useDirEntryActions } from '@/composables/use-dir-entry-actions';
import { CONTEXT_MENU_OPEN_COUNT_KEY } from './index';
import type { DirEntry } from '@/types/dir-entry';
import type { DriveEntryMetadata } from '@/types/drive-info';
import { getPathDisplayName } from '@/utils/normalize-path';
const props = withDefaults(defineProps<{
@@ -25,11 +26,13 @@ const props = withDefaults(defineProps<{
disableDestructiveActions?: boolean;
disableDropTarget?: boolean;
isCurrentDirectoryContext?: boolean;
driveMetadata?: DriveEntryMetadata | null;
}>(), {
isFile: false,
disableDestructiveActions: false,
disableDropTarget: false,
isCurrentDirectoryContext: false,
driveMetadata: null,
});
const {
@@ -103,6 +106,7 @@ const dirEntry = computed<DirEntry>(() => {
is_dir: !props.isFile,
is_symlink: false,
is_hidden: name.startsWith('.'),
drive_metadata: props.driveMetadata,
};
});
+22
View File
@@ -28,6 +28,8 @@ import { openNavigatorNavigablePath } from '@/utils/open-navigator-directory';
import { usePermanentDeleteConfirm } from '@/composables/use-permanent-delete-confirm';
import { usePlatformStore } from '@/stores/runtime/platform';
import { openNativeProperties } from '@/utils/open-native-properties';
import { disconnectDriveForEntry } from '@/utils/disconnect-drive';
import { refreshDrives } from '@/modules/home/composables/use-drives';
export function useDirEntryActions() {
const { t } = useI18n();
@@ -445,6 +447,21 @@ export function useDirEntryActions() {
}
}
async function disconnectDriveEntry(entry: DirEntry) {
const result = await disconnectDriveForEntry(entry);
if (!result.success) {
console.error('Failed to disconnect drive:', result.error);
}
try {
await refreshDrives();
}
catch (refreshError) {
console.error('Failed to refresh drives after disconnect:', refreshError);
}
}
function handleContextMenuAction(action: ContextMenuAction, entries: DirEntry[]) {
if (entries.length === 0) return;
@@ -495,6 +512,11 @@ export function useDirEntryActions() {
case 'properties':
void openProperties(entries);
break;
case 'disconnect':
if (entries.length === 1) {
void disconnectDriveEntry(entries[0]);
}
break;
case 'rename':
case 'open-with':
case 'edit-tags':
+1
View File
@@ -749,6 +749,7 @@
"driveNotMounted": "未挂载",
"mounting": "挂载中",
"unmount": "卸载",
"disconnect": "断开连接",
"mount": "挂载",
"mountableDevices": "挂载设备",
"mountableDevicesDescription": "发现的可挂载设备",
+1
View File
@@ -749,6 +749,7 @@
"driveNotMounted": "Nicht eingehängt",
"mounting": "Einhängen",
"unmount": "Aushängen",
"disconnect": "Trennen",
"mount": "Einhängen",
"mountableDevices": "Gerät einhängen",
"mountableDevicesDescription": "Erkannte Geräte, die eingehängt werden können",
+1
View File
@@ -779,6 +779,7 @@
"driveNotMounted": "Not mounted",
"mounting": "Mounting",
"unmount": "Unmount",
"disconnect": "Disconnect",
"mount": "Mount",
"mountableDevices": "Mount a device",
"mountableDevicesDescription": "Discovered devices that can be mounted",
+1
View File
@@ -749,6 +749,7 @@
"driveNotMounted": "No montado",
"mounting": "Montando",
"unmount": "Desmontar",
"disconnect": "Desconectar",
"mount": "Montar",
"mountableDevices": "Montar un dispositivo",
"mountableDevicesDescription": "Dispositivos descubiertos que se pueden montar",
+1
View File
@@ -745,6 +745,7 @@
"driveNotMounted": "نصب نشده",
"mounting": "در حال نصب",
"unmount": "جدا کردن",
"disconnect": "قطع اتصال",
"mount": "نصب",
"mountableDevices": "نصب دستگاه",
"mountableDevicesDescription": "دستگاه‌های کشف‌شده قابل نصب",
+1
View File
@@ -749,6 +749,7 @@
"driveNotMounted": "Non monté",
"mounting": "Montage",
"unmount": "Démonter",
"disconnect": "Déconnecter",
"mount": "Monter",
"mountableDevices": "Monter un périphérique",
"mountableDevicesDescription": "Périphériques détectés pouvant être montés",
+1
View File
@@ -779,6 +779,7 @@
"driveNotMounted": "לא מעוגן",
"mounting": "מעגן",
"unmount": "בטל עיגון",
"disconnect": "נתק",
"mount": "עגן",
"mountableDevices": "עגן התקן",
"mountableDevicesDescription": "התקנים שזוהו וניתנים לעיגון",
+1
View File
@@ -779,6 +779,7 @@
"driveNotMounted": "नहीं लगाया हुआ है",
"mounting": "बढ़ते",
"unmount": "अनमाउंट",
"disconnect": "डिस्कनेक्ट करें",
"mount": "पर्वत",
"mountableDevices": "एक उपकरण माउंट करें",
"mountableDevicesDescription": "ऐसे उपकरण खोजे गए जिन्हें माउंट किया जा सकता है",
+1
View File
@@ -749,6 +749,7 @@
"driveNotMounted": "Non montato",
"mounting": "Montaggio",
"unmount": "Smonta",
"disconnect": "Disconnetti",
"mount": "Monta",
"mountableDevices": "Monta un dispositivo",
"mountableDevicesDescription": "Dispositivi rilevati che possono essere montati",
+1
View File
@@ -749,6 +749,7 @@
"driveNotMounted": "未マウント",
"mounting": "マウント中",
"unmount": "アンマウント",
"disconnect": "切断",
"mount": "マウント",
"mountableDevices": "デバイスをマウント",
"mountableDevicesDescription": "マウント可能なデバイス",
+1
View File
@@ -748,6 +748,7 @@
"driveNotMounted": "Não montado",
"mounting": "Montando",
"unmount": "Desmontar",
"disconnect": "Desconectar",
"mount": "Montar",
"mountableDevices": "Montar um dispositivo",
"mountableDevicesDescription": "Dispositivos descobertos que podem ser montados",
+1
View File
@@ -779,6 +779,7 @@
"driveNotMounted": "Не подключено",
"mounting": "Подключение",
"unmount": "Отключить",
"disconnect": "Отключить",
"mount": "Подключить",
"mountableDevices": "Подключить устройство",
"mountableDevicesDescription": "Обнаруженные устройства, которые можно подключить",
+1
View File
@@ -779,6 +779,7 @@
"driveNotMounted": "Ni priklopljeno",
"mounting": "Priklapljanje",
"unmount": "Odklopi",
"disconnect": "Prekini povezavo",
"mount": "Priklopi",
"mountableDevices": "Priklopi napravo",
"mountableDevicesDescription": "Odkrite naprave, ki jih je mogoče priklopiti",
+1
View File
@@ -749,6 +749,7 @@
"driveNotMounted": "Bağlı değil",
"mounting": "Bağlanıyor",
"unmount": "Çıkar",
"disconnect": "Bağlantıyı kes",
"mount": "Bağla",
"mountableDevices": "Bir aygıt bağla",
"mountableDevicesDescription": "Bağlanabilir bulunan aygıtlar",
+1
View File
@@ -779,6 +779,7 @@
"driveNotMounted": "منسلک نہیں",
"mounting": "منسلک ہو رہا ہے",
"unmount": "الگ کریں",
"disconnect": "منقطع کریں",
"mount": "منسلک کریں",
"mountableDevices": "منسلک ہونے والی ڈیوائسز",
"mountableDevicesDescription": "ملنے والی ڈیوائسز جن کو منسلک کیا جا سکے",
+1
View File
@@ -749,6 +749,7 @@
"driveNotMounted": "Chưa gắn kết",
"mounting": "Đang gắn kết",
"unmount": "Gỡ bỏ",
"disconnect": "Ngắt kết nối",
"mount": "Gắn kết",
"mountableDevices": "Gắn kết thiết bị",
"mountableDevicesDescription": "Các thiết bị được phát hiện có thể gắn kết",
@@ -15,6 +15,7 @@ import DriveCard from './drive-card.vue';
import MountDialog from './mount-dialog.vue';
import { getStaggerSlideUpBinding } from '@/utils/stagger-animation';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { createDriveEntryMetadata } from '@/utils/drive-icon';
const { t } = useI18n();
const { drives, isLoading, error, refresh } = useDrives();
@@ -87,6 +88,7 @@ const sectionTitle = computed(() => {
v-for="(drive, itemIndex) in drives"
:key="drive.path"
:path="drive.path"
:drive-metadata="createDriveEntryMetadata(drive)"
>
<DriveCard
v-bind="getStaggerSlideUpBinding(itemIndex, {initialDelayMs: 300})"
+1 -1
View File
@@ -199,4 +199,4 @@ export function useDrives() {
};
}
export { drives as sharedDrives, getDriveByPath };
export { drives as sharedDrives, getDriveByPath, refresh as refreshDrives };
@@ -35,6 +35,8 @@ import { isContextMenuActionVisible } from '@/modules/navigator/components/file-
import { resolveNavigableItemTarget } from '@/utils/resolve-navigable-item-target';
import type { CreateLinksResult, LinkCreationKind } from '@/utils/link-operations';
import { getFileBrowserVisualEntryOrder } from '../file-browser-entry-groups';
import { disconnectDriveForEntry } from '@/utils/disconnect-drive';
import { refreshDrives } from '@/modules/home/composables/use-drives';
export const FILE_BROWSER_REVEAL_STALE_FOCUS_GUARD_MS = 500;
@@ -342,6 +344,22 @@ export function useFileBrowserSelection(
});
}
async function disconnectDriveEntry(entry: DirEntry) {
const result = await disconnectDriveForEntry(entry);
if (!result.success) {
console.error('Failed to disconnect drive:', result.error);
}
try {
await refreshDrives();
onRefresh();
}
catch (refreshError) {
console.error('Failed to refresh after drive disconnect:', refreshError);
}
}
function copyItems(entries: DirEntry[]) {
if (!canPerformAction('copy', entries)) {
return;
@@ -987,6 +1005,12 @@ export function useFileBrowserSelection(
startShare(entries);
}
break;
case 'disconnect':
if (entries.length === 1 && canPerformAction('disconnect', entries)) {
void disconnectDriveEntry(entries[0]);
}
break;
}
@@ -400,6 +400,14 @@ function handleCreateLink(linkKind: LinkCreationKind) {
{{ shortcutsStore.getShortcutLabel('openNewTab') }}
</ContextMenuShortcut>
</component>
<component
:is="menuItemComponent"
v-if="isActionVisible('disconnect')"
@select="emitAction('disconnect')"
>
<UnplugIcon :size="16" />
<span>{{ t('disconnect') }}</span>
</component>
<component
:is="menuItemComponent"
v-if="isActionVisible('copy-path')"
@@ -38,7 +38,7 @@ export interface GroupedEntries {
others: DirEntry[];
}
export type ContextMenuAction = 'rename' | 'copy' | 'cut' | 'link' | 'paste' | 'delete' | 'delete-permanently' | 'open-with' | 'properties' | 'quick-view' | 'print' | 'share' | 'open-in-new-tab' | 'toggle-favorite' | 'edit-tags' | 'copy-path' | 'create-file' | 'create-directory';
export type ContextMenuAction = 'rename' | 'copy' | 'cut' | 'link' | 'paste' | 'delete' | 'delete-permanently' | 'open-with' | 'properties' | 'quick-view' | 'print' | 'share' | 'open-in-new-tab' | 'toggle-favorite' | 'edit-tags' | 'copy-path' | 'create-file' | 'create-directory' | 'disconnect';
export type SelectionType = 'single' | 'multiple';
export type EntryType = 'file' | 'directory';
@@ -79,4 +79,26 @@ describe('isContextMenuActionVisible', () => {
expect(isContextMenuActionVisible('delete', entries, { platform: 'windows' })).toBe(true);
expect(isContextMenuActionVisible('rename', entries, { platform: 'windows' })).toBe(true);
});
it('shows disconnect for network drive entries', () => {
const entries: DirEntry[] = [{
...createDirectoryEntry('Z:/'),
name: 'test (Z:)',
drive_metadata: {
drive_type: 'Network',
is_removable: false,
is_mounted: true,
mount_point: 'Z:\\',
device_path: 'Z:\\',
},
}];
expect(isContextMenuActionVisible('disconnect', entries, { platform: 'windows' })).toBe(true);
});
it('hides disconnect for fixed local drive roots', () => {
const entries = [createDirectoryEntry('D:/')];
expect(isContextMenuActionVisible('disconnect', entries, { platform: 'windows' })).toBe(false);
});
});
@@ -5,6 +5,7 @@
import type { DirEntry } from '@/types/dir-entry';
import type { ContextMenuAction, ContextMenuItemConfig, EntryType, SelectionType } from '@/modules/navigator/components/file-browser/types';
import { isActionBlockedByEntryPolicy } from '@/utils/entry-action-policy';
import { canDisconnectDriveEntry } from '@/utils/drive-disconnect-policy';
const CONTEXT_MENU_ITEMS: ContextMenuItemConfig[] = [
{
@@ -119,6 +120,14 @@ export function isContextMenuActionVisible(
return isContextMenuActionVisible('delete', entries, options);
}
if (action === 'disconnect') {
if (entries.length !== 1 || !entries[0].is_dir) {
return false;
}
return canDisconnectDriveEntry(entries[0], options?.platform ?? null);
}
const config = CONTEXT_MENU_ITEMS.find(item => item.action === action);
if (!config) {
+3
View File
@@ -21,6 +21,9 @@ export type DriveInfo = {
export type DriveEntryMetadata = {
drive_type: string;
is_removable: boolean;
is_mounted: boolean;
mount_point: string;
device_path: string;
};
export type MountableDevice = {
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// License: GNU GPLv3 or later. See the license file in the project root for more information.
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
import { describe, expect, it } from 'vitest';
import type { DirEntry } from '@/types/dir-entry';
import {
canDisconnectDriveEntry,
canDisconnectDriveMetadata,
getDisconnectDriveTarget,
} from '@/utils/drive-disconnect-policy';
function createNetworkDriveEntry(path = 'Z:/'): DirEntry {
return {
name: 'test (Z:)',
path,
is_file: false,
is_dir: true,
is_hidden: false,
is_symlink: false,
size: 1000,
item_count: null,
created_time: 0,
modified_time: 0,
accessed_time: 0,
ext: null,
mime: null,
drive_metadata: {
drive_type: 'Network',
is_removable: false,
is_mounted: true,
mount_point: 'Z:\\',
device_path: 'Z:\\',
},
};
}
describe('canDisconnectDriveEntry', () => {
it('shows disconnect for network drive entries using metadata', () => {
expect(canDisconnectDriveEntry(createNetworkDriveEntry(), 'windows')).toBe(true);
});
it('does not show disconnect for regular directories', () => {
const entry: DirEntry = {
...createNetworkDriveEntry('D:/Apps'),
drive_metadata: undefined,
};
expect(canDisconnectDriveEntry(entry, 'windows')).toBe(false);
});
});
describe('getDisconnectDriveTarget', () => {
it('builds a backend command target from entry metadata', () => {
const entry = createNetworkDriveEntry();
const target = getDisconnectDriveTarget(entry);
expect(target).toEqual({
devicePath: 'Z:\\',
mountPoint: 'Z:\\',
driveType: 'Network',
});
});
});
describe('canDisconnectDriveMetadata', () => {
it('allows network metadata on every platform', () => {
expect(canDisconnectDriveMetadata({
drive_type: 'Network',
is_removable: false,
is_mounted: true,
mount_point: 'Z:\\',
device_path: 'Z:\\',
}, 'windows')).toBe(true);
});
it('blocks unmounted metadata', () => {
expect(canDisconnectDriveMetadata({
drive_type: 'Network',
is_removable: false,
is_mounted: false,
mount_point: 'Z:\\',
device_path: 'Z:\\',
}, 'windows')).toBe(false);
});
});
+18 -16
View File
@@ -6,33 +6,35 @@ import { describe, expect, it } from 'vitest';
import { HardDriveIcon, NetworkIcon, UsbIcon } from '@lucide/vue';
import UbuntuWslIcon from '@/components/icons/ubuntu-wsl-icon.vue';
import { getDriveIconComponent } from '@/utils/drive-icon';
import type { DriveEntryMetadata } from '@/types/drive-info';
function createDriveMetadata(
driveType: string,
isRemovable: boolean,
): DriveEntryMetadata {
return {
drive_type: driveType,
is_removable: isRemovable,
is_mounted: true,
mount_point: '',
device_path: '',
};
}
describe('getDriveIconComponent', () => {
it('returns the wsl icon for wsl drives', () => {
expect(getDriveIconComponent({
drive_type: 'WSL',
is_removable: false,
})).toBe(UbuntuWslIcon);
expect(getDriveIconComponent(createDriveMetadata('WSL', false))).toBe(UbuntuWslIcon);
});
it('returns the network icon for network drives', () => {
expect(getDriveIconComponent({
drive_type: 'Network',
is_removable: false,
})).toBe(NetworkIcon);
expect(getDriveIconComponent(createDriveMetadata('Network', false))).toBe(NetworkIcon);
});
it('returns the usb icon for removable drives', () => {
expect(getDriveIconComponent({
drive_type: 'SSD',
is_removable: true,
})).toBe(UsbIcon);
expect(getDriveIconComponent(createDriveMetadata('SSD', true))).toBe(UsbIcon);
});
it('returns the hard drive icon for fixed local drives', () => {
expect(getDriveIconComponent({
drive_type: 'SSD',
is_removable: false,
})).toBe(HardDriveIcon);
expect(getDriveIconComponent(createDriveMetadata('SSD', false))).toBe(HardDriveIcon);
});
});
@@ -74,6 +74,9 @@ describe('virtual-locations', () => {
expect(driveEntry.drive_metadata).toEqual({
drive_type: 'SSD',
is_removable: false,
is_mounted: true,
mount_point: 'C:\\',
device_path: '\\\\.\\C:',
});
expect(driveEntry.item_count).toBeNull();
});
+42
View File
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// License: GNU GPLv3 or later. See the license file in the project root for more information.
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
import { invoke } from '@tauri-apps/api/core';
import type { DirEntry } from '@/types/dir-entry';
import {
getDisconnectDriveTarget,
type DisconnectDriveResult,
type DisconnectDriveTarget,
} from '@/utils/drive-disconnect-policy';
export async function disconnectDrive(target: DisconnectDriveTarget): Promise<DisconnectDriveResult> {
try {
await invoke('disconnect_drive', {
devicePath: target.devicePath,
mountPoint: target.mountPoint,
driveType: target.driveType,
});
return { success: true };
}
catch (disconnectError) {
return {
success: false,
error: disconnectError instanceof Error ? disconnectError.message : String(disconnectError),
};
}
}
export async function disconnectDriveForEntry(entry: DirEntry): Promise<DisconnectDriveResult> {
const target = getDisconnectDriveTarget(entry);
if (!target) {
return {
success: false,
error: `Drive metadata is missing for ${entry.path}`,
};
}
return disconnectDrive(target);
}
+67
View File
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// License: GNU GPLv3 or later. See the license file in the project root for more information.
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
import type { DirEntry } from '@/types/dir-entry';
import type { DriveEntryMetadata } from '@/types/drive-info';
export type DisconnectDriveTarget = {
devicePath: string;
mountPoint: string;
driveType: string;
};
export type DisconnectDriveResult = {
success: boolean;
error?: string;
};
export function canDisconnectDriveMetadata(
metadata: DriveEntryMetadata,
platform: string | null,
): boolean {
if (!metadata.is_mounted) {
return false;
}
if (metadata.drive_type === 'Network') {
return true;
}
if (metadata.drive_type === 'WSL') {
return false;
}
if (platform === 'windows') {
return false;
}
return metadata.is_removable;
}
export function canDisconnectDriveEntry(
entry: DirEntry,
platform: string | null,
): boolean {
if (!entry.is_dir) {
return false;
}
return entry.drive_metadata
? canDisconnectDriveMetadata(entry.drive_metadata, platform)
: false;
}
export function getDisconnectDriveTarget(entry: DirEntry): DisconnectDriveTarget | null {
const metadata = entry.drive_metadata;
if (!metadata) {
return null;
}
return {
devicePath: metadata.device_path,
mountPoint: metadata.mount_point,
driveType: metadata.drive_type,
};
}
+3
View File
@@ -12,6 +12,9 @@ export function createDriveEntryMetadata(drive: DriveInfo): DriveEntryMetadata {
return {
drive_type: drive.drive_type,
is_removable: drive.is_removable,
is_mounted: drive.is_mounted,
mount_point: drive.mount_point,
device_path: drive.device_path,
};
}