update recipe viewer

This commit is contained in:
Kenta420 2024-02-27 13:43:26 +07:00
parent cf5b11b267
commit a12121fca6
19 changed files with 677 additions and 363 deletions

View file

@ -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) => (

View 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

View file

@ -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 ? (