windows integration without confirmation dialog
Massimo Melina committed
Oct 27, 2023 at 00:18 UTC
bb98e3cce753b02e5e03c50862adfa6bf4e4fa4b
3 files changed
+39
-42
admin/src/VfsMenuBar.ts
+3
-10
@@ -36,13 +36,14 @@ export default function VfsMenuBar({ status }: any) {
36
status?.platform === 'win32' && h(Btn, {
37
icon: Microsoft,
38
variant: 'outlined',
39
+ doneMessage: true,
40
...(!integrated?.is ? {
41
children: "System integration",
42
onClick: () => windowsIntegration().then(reload),
43
} : {
44
confirm: true,
45
children: "Remove integration",
45
- onClick: () => exec('windows_remove').then(reload),
46
+ onClick: () => apiCall('windows_remove').then(reload),
47
})
48
}),
49
)
@@ -57,13 +58,5 @@ async function windowsIntegration() {
58
} }),
59
)
60
return await confirmDialog(msg)
60
- && exec('windows_integration')
61
-}
62
-
63
-async function exec(api: string) {
64
- const hint = alertDialog("Click YES to the next 2 dialogs. The second dialog may not appear, and you need to click on the bottom bar.", 'warning')
65
- const { finish } = await apiCall(api, {}, { timeout: false })
66
- hint.close()
67
- return finish ? alertDialog("To finish the process, please execute the file you'll find on your desktop: " + basename(finish))
68
- : alertDialog("Done!", 'success')
61
+ && apiCall('windows_integration')
62
}
admin/src/mui.ts
+10
-4
@@ -17,7 +17,7 @@ import {
17
import { formatPerc, WIKI_URL } from '../../src/cross'
18
import { dontBotherWithKeys, useStateMounted } from '@hfs/shared'
19
import { Promisable } from '@hfs/mui-grid-form'
20
-import { alertDialog, confirmDialog } from './dialog'
20
+import { alertDialog, confirmDialog, toast } from './dialog'
21
import { LoadingButton, LoadingButtonProps } from '@mui/lab'
22
import { Link as RouterLink } from 'react-router-dom'
23
@@ -53,7 +53,7 @@ export function IconProgress({ icon, progress, offset, addTitle, sx }: IconProgr
53
value: (offset || 1e-7) * 100,
54
variant: 'determinate',
55
size: 32,
56
- sx: { display: 'flex', ...sx }, // workaround: without this the element is has 0 width when the space is crammy (monitor/file)
56
+ sx: { display: 'flex', ...sx }, // workaround: without this the element has 0 width when the space is crammy (monitor/file)
57
}),
58
})
59
)
@@ -135,10 +135,11 @@ interface BtnProps extends Omit<LoadingButtonProps,'disabled'|'title'|'onClick'>
135
progress?: boolean | number
136
link?: string
137
confirm?: boolean | string
138
+ doneMessage?: boolean | string
139
tooltipProps?: TooltipProps
140
onClick: (...args: Parameters<NonNullable<ButtonProps['onClick']>>) => Promisable<any>
141
}
141
-export function Btn({ icon, title, onClick, disabled, progress, link, tooltipProps, confirm, ...rest }: BtnProps) {
142
+export function Btn({ icon, title, onClick, disabled, progress, link, tooltipProps, confirm, doneMessage, ...rest }: BtnProps) {
143
const [loading, setLoading] = useStateMounted(false)
144
if (typeof disabled === 'string') {
145
title = disabled
@@ -160,7 +161,12 @@ export function Btn({ icon, title, onClick, disabled, progress, link, tooltipPro
161
const ret = onClick?.apply(this,args)
162
if (ret && ret instanceof Promise) {
163
setLoading(true)
163
- ret.catch(alertDialog).finally(()=> setLoading(false))
164
+ ret.then(async res => {
165
+ if (doneMessage)
166
+ toast(doneMessage === true ? "Operation completed" : doneMessage, 'success')
167
+ return res
168
+ }, alertDialog)
169
+ .finally(()=> setLoading(false))
170
}
171
}
172
})
src/api.vfs.ts
+26
-28
@@ -3,7 +3,7 @@
3
import { getNodeName, isSameFilenameAs, nodeIsDirectory, saveVfs, urlToNode, vfs, VfsNode, applyParentToChild,
4
permsFromParent } from './vfs'
5
import _ from 'lodash'
6
-import { stat, unlink, writeFile } from 'fs/promises'
6
+import { stat } from 'fs/promises'
7
import { ApiError, ApiHandlers, SendListReadable } from './apiMiddleware'
8
import { dirname, extname, join, resolve } from 'path'
9
import { dirStream, enforceFinal, isDirectory, isWindowsDrive, makeMatcher, PERM_KEYS, VfsNodeAdminSend } from './misc'
@@ -14,8 +14,6 @@ import {
14
import { getDrives } from './util-os'
15
import { Stats } from 'fs'
16
import { getBaseUrlOrDefault, getServerStatus } from './listen'
17
-import { homedir } from 'os'
18
-import open from 'open'
17
import { promisify } from 'util'
18
import { execFile } from 'child_process'
19
@@ -78,7 +76,7 @@ const apis: ApiHandlers = {
76
if (_.isEmpty(oldParent!.children))
77
delete oldParent!.children
78
;(parentNode.children ||= []).push(fromNode)
81
- await saveVfs()
79
+ saveVfs()
80
return {}
81
},
82
@@ -96,7 +94,7 @@ const apis: ApiHandlers = {
94
delete props.masks
95
Object.assign(n, props)
96
simplifyName(n)
99
- await saveVfs()
97
+ saveVfs()
98
return n
99
},
100
@@ -123,7 +121,7 @@ const apis: ApiHandlers = {
121
child.name = name
122
simplifyName(child)
123
;(parentNode.children ||= []).unshift(child)
126
- await saveVfs()
124
+ saveVfs()
125
const link = getBaseUrlOrDefault()
126
+ (parent ? enforceFinal('/', parent) : '/')
127
+ encodeURIComponent(getNodeName(child))
@@ -211,18 +209,21 @@ const apis: ApiHandlers = {
209
},
210
211
async windows_integration() {
214
- return { finish: await windowsIntegration() }
212
+ await windowsIntegration()
213
+ return {}
214
},
215
216
async windows_integrated() {
217
return {
219
- is: await promisify(execFile)('reg', ['query', WINDOWS_REG_KEY])
218
+ is: await reg('query', WINDOWS_REG_KEY)
219
.then(x => x.stdout.includes('REG_SZ'), () => false)
220
}
221
},
222
224
- async windows_remove() { // not using `reg delete` because it doesn't ask for admin permissions
225
- return { finish: await runReg(['*', 'Directory'].map(k => `\n\n[-${WINDOWS_REG_KEY.replace('*', k)}]`).join('')) }
223
+ async windows_remove() {
224
+ for (const k of ['*', 'Directory'])
225
+ await reg('delete', WINDOWS_REG_KEY.replace('*',k), '/f')
226
+ return {}
227
},
228
229
}
@@ -245,27 +246,24 @@ function simplifyName(node: VfsNode) {
246
delete node.name
247
}
248
248
-const WINDOWS_REG_KEY = 'HKEY_CLASSES_ROOT\\*\\shell\\AddToHFS3'
249
-const WINDOWS_REG_KEY2 = WINDOWS_REG_KEY + '\\command'
249
+const WINDOWS_REG_KEY = 'HKCU\\Software\\Classes\\*\\shell\\AddToHFS3'
250
251
-export async function runReg(content: string) {
252
- const path = homedir() + '\\desktop\\hfs-windows-menu.reg'
253
- await writeFile(path, 'Windows Registry Editor Version 5.00\n\n' + content, 'utf8')
254
- try {
255
- await open(path, { wait: true})
256
- await unlink(path)
257
- }
258
- catch { return path }
259
-}
251
+if (IS_WINDOWS) // legacy 0.49.0-beta7 2023-10-27. Remove in 0.50
252
+ for (const k of ['*', 'Directory'])
253
+ reg('delete', `HKCR\\${k}\\shell\\AddToHFS3`, '/f').catch(() => {})
254
255
export async function windowsIntegration() {
256
const status = await getServerStatus()
257
const url = 'http://localhost:' + status.http.port
264
- return runReg(['*', 'Directory'].map(k => `
265
-[${WINDOWS_REG_KEY.replace('*', k)}]
266
-@="Add to HFS (new)"
258
+ for (const k of ['*', 'Directory']) {
259
+ await reg('add', WINDOWS_REG_KEY.replace('*', k), '/ve', '/f', '/d', 'Add to HFS (new)')
260
+ await reg('add', WINDOWS_REG_KEY.replace('*', k) + '\\command', '/ve', '/f', '/d', `powershell -Command "
261
+ $j = '{ \\"source\\": "' + ('%1'|convertTo-json) + '" }'; $wsh = New-Object -ComObject Wscript.Shell;
262
+ try { $res = Invoke-WebRequest -Uri '${url}/~/api/add_vfs' -Method POST -Headers @{ 'x-hfs-anti-csrf' = '1' } -ContentType 'application/json' -TimeoutSec 1 -Body $j;
263
+ $json = $res.Content | ConvertFrom-Json; $link = $json.link; $link | Set-Clipboard; } catch { $wsh.Popup('Server is down', 0, 'Error', 16); }"`)
264
+ }
265
+}
266
268
-[${WINDOWS_REG_KEY2.replace('*', k)}]
269
-@="powershell -Command \\"$p = '%1'.Replace('\\\\', '\\\\\\\\'); $j = '{ \\\\\\"source\\\\\\": \\\\\\"' + $p + '\\\\\\" }'; $wsh = New-Object -ComObject Wscript.Shell; try { $res = Invoke-WebRequest -Uri '${url}/~/api/add_vfs' -Method POST -Headers @{ 'x-hfs-anti-csrf' = '1' } -ContentType 'application/json' -TimeoutSec 1 -Body $j; $json = $res.Content | ConvertFrom-Json; $link = $json.link; $link | Set-Clipboard; } catch { $wsh.Popup('Server is down', 0, 'Error', 16); }\\""
270
- `).join(''))
271
-}
\ No newline at end of file
267
+function reg(...pars: string[]) {
268
+ return promisify(execFile)('reg', pars)
269
+}