update recipe viewer
This commit is contained in:
parent
cf5b11b267
commit
a12121fca6
19 changed files with 677 additions and 363 deletions
|
|
@ -13,7 +13,7 @@ interface SideBarProps {
|
|||
|
||||
const Sidebar: React.FC<SideBarProps> = ({ menuList }) => {
|
||||
return (
|
||||
<aside className="fixed top-0 left-0 z-40 w-64 pt-20 h-screen">
|
||||
<aside className="fixed top-0 left-0 z-40 w-64 pt-[90px] h-screen">
|
||||
<div className="h-full px-3 pb-4 overflow-y-auto bg-zinc-700">
|
||||
<ul className="space-y-2 font-medium">
|
||||
{menuList.map((item, index) => (
|
||||
|
|
|
|||
343
client-electron/src/components/device-switcher.tsx
Normal file
343
client-electron/src/components/device-switcher.tsx
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from './ui/popover'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from './ui/dialog'
|
||||
import { Button } from './ui/button'
|
||||
import { CaretSortIcon, CheckIcon, PlusCircledIcon } from '@radix-ui/react-icons'
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator
|
||||
} from './ui/command'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Label } from './ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'
|
||||
import useAdb from '@/hooks/useAdb'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import type { AdbDaemonWebUsbConnection } from '@yume-chan/adb-daemon-webusb'
|
||||
import { ADB_DEFAULT_DEVICE_FILTER, AdbDaemonWebUsbDevice } from '@yume-chan/adb-daemon-webusb'
|
||||
import { Adb, AdbDaemonTransport } from '@yume-chan/adb'
|
||||
import { toast } from './ui/use-toast'
|
||||
import AdbWebCredentialStore from '@yume-chan/adb-credential-web'
|
||||
|
||||
type PopoverTriggerProps = React.ComponentPropsWithoutRef<typeof PopoverTrigger>
|
||||
|
||||
interface TeamSwitcherProps extends PopoverTriggerProps {}
|
||||
|
||||
const DeviceSwitcher = ({ className }: TeamSwitcherProps) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [showNewDeviceDialog, setShowNewDeviceDialog] = useState(false)
|
||||
const [connectedDevices, setConnectedDevices] = useState<AdbDaemonWebUsbDevice[]>([])
|
||||
|
||||
const [newConnectionState, setNewConnectionState] = useState<'connection' | 'connecting'>('connection')
|
||||
const [selectedConnectionType, setSelectedConnectionType] = useState<string | undefined>()
|
||||
|
||||
const { manager, device, adb, setDevice, setAdb } = useAdb(
|
||||
useShallow(state => ({
|
||||
manager: state.manager,
|
||||
device: state.device,
|
||||
adb: state.adb,
|
||||
setDevice: state.setDevice,
|
||||
setAdb: state.setAdb
|
||||
}))
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
const getDevices = async () => {
|
||||
const devices = await manager?.getDevices()
|
||||
console.log(devices)
|
||||
setConnectedDevices(devices || [])
|
||||
}
|
||||
getDevices()
|
||||
}
|
||||
}, [open, manager])
|
||||
|
||||
async function connectDeviceAdbUsb(device: AdbDaemonWebUsbDevice) {
|
||||
let connection: AdbDaemonWebUsbConnection | undefined
|
||||
try {
|
||||
connection = await device.connect()
|
||||
} catch (e) {
|
||||
toast({
|
||||
duration: 5000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to connect to device',
|
||||
description: (e as Error).message
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (connection) {
|
||||
const credentialStore: AdbWebCredentialStore = new AdbWebCredentialStore()
|
||||
|
||||
const transport = await AdbDaemonTransport.authenticate({
|
||||
serial: device.serial,
|
||||
connection: connection,
|
||||
credentialStore: credentialStore
|
||||
})
|
||||
|
||||
const adb = new Adb(transport)
|
||||
setAdb(adb)
|
||||
setDevice(device)
|
||||
}
|
||||
}
|
||||
|
||||
async function createNewUsbConnection() {
|
||||
let selectedDevice: AdbDaemonWebUsbDevice | undefined = undefined
|
||||
if (!device) {
|
||||
console.log('no device')
|
||||
|
||||
selectedDevice = await manager?.requestDevice({
|
||||
filters: [
|
||||
{
|
||||
...ADB_DEFAULT_DEVICE_FILTER,
|
||||
serialNumber: 'd'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
if (!selectedDevice) {
|
||||
return
|
||||
} else {
|
||||
setDevice(selectedDevice)
|
||||
}
|
||||
} else {
|
||||
selectedDevice = device
|
||||
}
|
||||
|
||||
// create transport and connect to device
|
||||
let adb: Adb | null = null
|
||||
let connection
|
||||
try {
|
||||
if (selectedDevice instanceof AdbDaemonWebUsbDevice) {
|
||||
connection = await selectedDevice.connect()
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
duration: 5000,
|
||||
variant: 'destructive',
|
||||
title: 'Failed to connect to device',
|
||||
description: (e as Error).message
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (connection) {
|
||||
const credentialStore: AdbWebCredentialStore = new AdbWebCredentialStore()
|
||||
|
||||
const transport = await AdbDaemonTransport.authenticate({
|
||||
serial: selectedDevice.serial,
|
||||
connection: connection,
|
||||
credentialStore: credentialStore
|
||||
})
|
||||
|
||||
adb = new Adb(transport)
|
||||
setAdb(adb)
|
||||
setNewConnectionState('connection')
|
||||
setShowNewDeviceDialog(false)
|
||||
}
|
||||
}
|
||||
|
||||
// async function connectAdbDaemon() {
|
||||
// if (!window.electronRuntime) {
|
||||
// toast({
|
||||
// duration: 5000,
|
||||
// variant: 'destructive',
|
||||
// title: 'Failed to connect to adb daemon',
|
||||
// description: 'This feature is only available in the desktop app'
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
// // create connection
|
||||
// await window.ipcRenderer.invoke('adb')
|
||||
|
||||
// const result = await window.ipcRenderer.invoke('adb:shell', 'ls')
|
||||
// console.log(result)
|
||||
// }
|
||||
|
||||
function onDisconnect() {
|
||||
device?.raw.forget()
|
||||
setDevice(undefined)
|
||||
|
||||
adb?.close()
|
||||
setAdb(undefined)
|
||||
}
|
||||
|
||||
const selectConnectionTypeDialog = useMemo(() => {
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Connect New Device</DialogTitle>
|
||||
<DialogDescription>Connect a new device to manage your recipes or control your devices. </DialogDescription>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<div className="space-y-4 py-2 pb-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="plan">Connection type</Label>
|
||||
<Select onValueChange={setSelectedConnectionType}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select connection type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="usb">
|
||||
<span className="font-medium">USB</span> -{' '}
|
||||
<span className="text-muted-foreground">Connect device via USB</span>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowNewDeviceDialog(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" onClick={() => setNewConnectionState('connecting')}>
|
||||
Continue
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
)
|
||||
}, [selectedConnectionType])
|
||||
|
||||
const connectingDialog = useMemo(() => {
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Connect New Device</DialogTitle>
|
||||
<DialogDescription>Connect a new device to manage your recipes or control your devices. </DialogDescription>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<div className="space-y-4 py-2 pb-4">
|
||||
<div className="space-y-2">
|
||||
{/* <Label htmlFor="plan">Connection type</Label>
|
||||
<Select onValueChange={setSelectedConnectionType}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select connection type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="usb">
|
||||
<span className="font-medium">USB</span> -{' '}
|
||||
<span className="text-muted-foreground">Connect device via USB</span>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select> */}
|
||||
<span className="text-sm">Please connect your device via USB</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setNewConnectionState('connection')}>
|
||||
Back
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
createNewUsbConnection()
|
||||
}}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
)
|
||||
}, [selectedConnectionType])
|
||||
|
||||
return (
|
||||
<Dialog open={showNewDeviceDialog} onOpenChange={setShowNewDeviceDialog}>
|
||||
<div className="flex space-x-5 justify-center items-center">
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
aria-label="Select a Device"
|
||||
className={cn('w-[400px] justify-between', className)}
|
||||
>
|
||||
{device ? device.name : 'Select a Device'}
|
||||
<CaretSortIcon className="ml-auto h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[400px] p-0">
|
||||
<Command>
|
||||
<CommandList>
|
||||
<CommandInput placeholder="Search device..." />
|
||||
<CommandEmpty>No device found.</CommandEmpty>
|
||||
<CommandGroup heading={'Devices'}>
|
||||
{connectedDevices.length > 0 ? (
|
||||
connectedDevices.map(device => (
|
||||
<CommandItem
|
||||
key={device.serial}
|
||||
onSelect={() => {
|
||||
connectDeviceAdbUsb(device)
|
||||
setOpen(false)
|
||||
}}
|
||||
className="text-sm"
|
||||
>
|
||||
{device.name}
|
||||
<CheckIcon
|
||||
className={cn(
|
||||
'ml-auto h-4 w-4',
|
||||
device?.serial === device.serial ? 'opacity-100' : 'opacity-0'
|
||||
)}
|
||||
/>
|
||||
</CommandItem>
|
||||
))
|
||||
) : (
|
||||
<span className="text-sm ml-2">Not found device connected.</span>
|
||||
)}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
<CommandSeparator />
|
||||
<CommandList>
|
||||
<CommandGroup>
|
||||
<DialogTrigger asChild>
|
||||
<CommandItem
|
||||
onSelect={() => {
|
||||
setOpen(false)
|
||||
setShowNewDeviceDialog(true)
|
||||
}}
|
||||
>
|
||||
<PlusCircledIcon className="mr-2 h-5 w-5" />
|
||||
Connect New Devices
|
||||
</CommandItem>
|
||||
</DialogTrigger>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{device && (
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
onDisconnect()
|
||||
}}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{newConnectionState === 'connection'
|
||||
? selectConnectionTypeDialog
|
||||
: newConnectionState === 'connecting'
|
||||
? connectingDialog
|
||||
: null}
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default DeviceSwitcher
|
||||
|
|
@ -15,6 +15,7 @@ import { Link } from 'react-router-dom'
|
|||
import userAuthStore from '@/hooks/userAuth'
|
||||
import { Button } from './ui/button'
|
||||
import Logo from '@/assets/tao_logo.svg'
|
||||
import DeviceSwitcher from './device-switcher'
|
||||
|
||||
const DropdownMenuUser: React.FC = () => {
|
||||
return (
|
||||
|
|
@ -71,8 +72,11 @@ const Navbar: React.FC = () => {
|
|||
<nav className="flex justify-center items-center fixed top-0 z-50 w-full py-5 bg-zinc-700">
|
||||
<div className="flex justify-between items-center mx-auto w-full max-w-screen-2xl px-6 xs:px-8 sm:px-16">
|
||||
<Link to="/">
|
||||
<img src={Logo} alt="logo" width={40} height={40} />
|
||||
<img src={Logo} alt="logo" width={45} height={51} />
|
||||
</Link>
|
||||
<div className="flex justify-center items-center">
|
||||
<DeviceSwitcher />
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex items-center ms-3">
|
||||
{userInfo ? (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue