update recipe viewer
This commit is contained in:
parent
cf5b11b267
commit
a12121fca6
19 changed files with 677 additions and 363 deletions
|
|
@ -1,28 +1,12 @@
|
|||
import { ToolBar } from './components/tool-bar'
|
||||
import { ScrcpyTab } from './components/scrcpy-tab'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Toaster } from '@/components/ui/toaster'
|
||||
import { ShellTab } from './components/shell-tab'
|
||||
import useAdb from '@/hooks/useAdb'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { FileManagerTab } from './components/file-manager-tab'
|
||||
|
||||
const AndroidPage: React.FC = () => {
|
||||
const { manager, device, adb, setDevice, setAdb } = useAdb(
|
||||
useShallow(state => ({
|
||||
manager: state.manager,
|
||||
device: state.device,
|
||||
adb: state.adb,
|
||||
setDevice: state.setDevice,
|
||||
setAdb: state.setAdb
|
||||
}))
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex w-full py-5">
|
||||
<ToolBar manager={manager} device={device} adb={adb} setAdb={setAdb} setDevice={setDevice} />
|
||||
</div>
|
||||
<Tabs defaultValue="scrcpy" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="scrcpy">Scrcpy</TabsTrigger>
|
||||
|
|
@ -30,10 +14,10 @@ const AndroidPage: React.FC = () => {
|
|||
<TabsTrigger value="file-manager">File Manager</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="scrcpy">
|
||||
<ScrcpyTab adb={adb} />
|
||||
<ScrcpyTab />
|
||||
</TabsContent>
|
||||
<TabsContent value="shell">
|
||||
<ShellTab adb={adb} />
|
||||
<ShellTab />
|
||||
</TabsContent>
|
||||
<TabsContent value="file-manager">
|
||||
<FileManagerTab />
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card'
|
||||
import useScrcpy from '@/hooks/scrcpy-android'
|
||||
import { type Adb } from '@yume-chan/adb'
|
||||
import useAdb from '@/hooks/useAdb'
|
||||
import { memo, useEffect, useRef } from 'react'
|
||||
import 'xterm/css/xterm.css'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
|
||||
interface ScrcpyTabProps {
|
||||
adb: Adb | undefined
|
||||
}
|
||||
export const ScrcpyTab: React.FC = memo(() => {
|
||||
const adb = useAdb(state => state.adb)
|
||||
|
||||
export const ScrcpyTab: React.FC<ScrcpyTabProps> = memo(({ adb }) => {
|
||||
const scrcpyScreenRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { scrcpyClient, decoder, connectScrcpy, onHomeClick, onBackClick, disconnectScrcpy } = useScrcpy(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import useShellAndroid from '@/hooks/shell-android'
|
||||
import { type Adb } from '@yume-chan/adb'
|
||||
import useAdb from '@/hooks/useAdb'
|
||||
import { memo, useEffect, useRef } from 'react'
|
||||
import { type Terminal } from 'xterm'
|
||||
|
||||
|
|
@ -10,11 +10,9 @@ import { FitAddon } from 'xterm-addon-fit'
|
|||
import 'xterm/css/xterm.css'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
|
||||
interface ShellTabProps {
|
||||
adb: Adb | undefined
|
||||
}
|
||||
export const ShellTab: React.FC = memo(() => {
|
||||
const adb = useAdb(state => state.adb)
|
||||
|
||||
export const ShellTab: React.FC<ShellTabProps> = memo(({ adb }) => {
|
||||
const { terminal, startTerminal, killTerminal } = useShellAndroid(
|
||||
useShallow(state => ({
|
||||
terminal: state.terminal,
|
||||
|
|
|
|||
|
|
@ -1,190 +0,0 @@
|
|||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from '@/components/ui/dialog'
|
||||
import { toast } from '@/components/ui/use-toast'
|
||||
import { Adb, AdbDaemonTransport } from '@yume-chan/adb'
|
||||
import AdbWebCredentialStore from '@yume-chan/adb-credential-web'
|
||||
import {
|
||||
ADB_DEFAULT_DEVICE_FILTER,
|
||||
AdbDaemonWebUsbDevice,
|
||||
type AdbDaemonWebUsbDeviceManager
|
||||
} from '@yume-chan/adb-daemon-webusb'
|
||||
import { useState } from 'react'
|
||||
|
||||
interface ToolBarProps {
|
||||
manager: AdbDaemonWebUsbDeviceManager | undefined
|
||||
device: AdbDaemonWebUsbDevice | undefined
|
||||
adb: Adb | undefined
|
||||
setDevice: (device: AdbDaemonWebUsbDevice | undefined) => void
|
||||
setAdb: (adb: Adb | undefined) => void
|
||||
}
|
||||
|
||||
export const ToolBar: React.FC<ToolBarProps> = ({ manager, adb, device, setAdb, setDevice }) => {
|
||||
const [name, setName] = useState<string>('')
|
||||
const [resolution, setResolution] = useState<string>('')
|
||||
const [version, setVersion] = useState<string>('')
|
||||
|
||||
async function createNewConnection() {
|
||||
console.log(device)
|
||||
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)
|
||||
}
|
||||
|
||||
if (adb) {
|
||||
const name = await adb.getProp('ro.product.model')
|
||||
const version = await adb.getProp('ro.build.version.release')
|
||||
|
||||
setName(name)
|
||||
setResolution(resolution)
|
||||
setVersion(version)
|
||||
|
||||
setAdb(adb)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
function onTerminate() {
|
||||
adb?.close()
|
||||
setAdb(undefined)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center w-full p-4 shadow-lg rounded-lg">
|
||||
{adb ? (
|
||||
<div className="flex flex-col justify-center items-start">
|
||||
<ul className="list-disc pl-4">
|
||||
<li>Name: {name}</li>
|
||||
<li>Version: {version}</li>
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col justify-center items-start">
|
||||
<h2>No Device Connected</h2>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center space-x-4">
|
||||
{adb ? (
|
||||
<DisconnectConfirmDialog onDisconnect={onDisconnect} onTerminate={onTerminate} />
|
||||
) : (
|
||||
<Button variant={'default'} onClick={createNewConnection}>
|
||||
Connect
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button variant={'default'} onClick={connectAdbDaemon}>
|
||||
Connect Adb Daemon
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface DisconnectConfirmDialogProps {
|
||||
onDisconnect: () => void
|
||||
onTerminate: () => void
|
||||
}
|
||||
|
||||
const DisconnectConfirmDialog: React.FC<DisconnectConfirmDialogProps> = ({ onDisconnect, onTerminate }) => {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant={'destructive'}>Disconnect</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Disconnect Device</DialogTitle>
|
||||
<DialogDescription>
|
||||
Do you want to also declaim device? if so press Disconnect else press Terminate
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="destructive" onClick={onDisconnect}>
|
||||
Disconnect
|
||||
</Button>
|
||||
<Button variant="secondary" onClick={onTerminate}>
|
||||
Terminate
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import useAndroidSwitcher from '@/hooks/android-switcher'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
|
||||
interface AndroidSwitcherProps {
|
||||
isCollapsed?: boolean
|
||||
androids: {
|
||||
label: string
|
||||
deviceName: string
|
||||
serial: string
|
||||
icon: React.ReactNode
|
||||
}[]
|
||||
}
|
||||
|
||||
const AndroidSwitcher: React.FC<AndroidSwitcherProps> = ({ androids, isCollapsed }) => {
|
||||
const { selectedAndroid, setSelectedAndroid } = useAndroidSwitcher(
|
||||
useShallow(state => ({
|
||||
selectedAndroid: state.selectedAndroid,
|
||||
setSelectedAndroid: state.setSelectedAndroid
|
||||
}))
|
||||
)
|
||||
|
||||
return (
|
||||
<Select defaultValue={selectedAndroid} onValueChange={setSelectedAndroid}>
|
||||
<SelectTrigger
|
||||
className={cn(
|
||||
'flex items-center gap-2 [&>span]:line-clamp-1 [&>span]:flex [&>span]:w-full [&>span]:items-center [&>span]:gap-1 [&>span]:truncate [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0',
|
||||
isCollapsed && 'flex h-9 w-9 shrink-0 items-center justify-center p-0 [&>span]:w-auto [&>svg]:hidden'
|
||||
)}
|
||||
aria-label="Select android"
|
||||
>
|
||||
<SelectValue placeholder="Select an android">
|
||||
{androids.find(android => android.serial === selectedAndroid)?.icon}
|
||||
<span className={cn('ml-2', isCollapsed && 'hidden')}>
|
||||
{androids.find(android => android.serial === selectedAndroid)?.label}
|
||||
</span>
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{androids.map(android => (
|
||||
<SelectItem key={android.serial} value={android.serial}>
|
||||
<div className="flex items-center gap-3 [&_svg]:h-4 [&_svg]:w-4 [&_svg]:shrink-0 [&_svg]:text-foreground">
|
||||
{android.icon}
|
||||
{android.deviceName}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
export default AndroidSwitcher
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import useRecipeDashboard from '@/hooks/recipe-dashboard'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { type MaterialSetting } from '@/models/recipe/schema'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
|
||||
interface MaterialListProps {
|
||||
items: MaterialSetting[]
|
||||
}
|
||||
|
||||
const MaterialList: React.FC<MaterialListProps> = ({ items }) => {
|
||||
const { selectedMaterial, setSelectedMaterial } = useRecipeDashboard(
|
||||
useShallow(state => ({
|
||||
selectedMaterial: state.selectedMaterial,
|
||||
setSelectedMaterial: state.setSelectedMaterial
|
||||
}))
|
||||
)
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-screen">
|
||||
<div className="flex flex-col gap-2 p-4 pt-0">
|
||||
{items.map(item => (
|
||||
<button
|
||||
key={item.id}
|
||||
className={cn(
|
||||
'flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all hover:bg-accent',
|
||||
selectedMaterial === item.id && 'bg-muted'
|
||||
)}
|
||||
onClick={() => setSelectedMaterial(item.id)}
|
||||
>
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="font-semibold">
|
||||
{item.id}: {item.materialName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs font-medium">{`${item.materialId}: ${item.materialName}`}</div>
|
||||
</div>
|
||||
{/* <div className="line-clamp-2 text-xs text-muted-foreground">{item.substring(0, 300)}</div>
|
||||
{item.labels.length ? (
|
||||
<div className="flex items-center gap-2">
|
||||
{item.labels.map(label => (
|
||||
<Badge key={label} variant={getBadgeVariantFromLabel(label)}>
|
||||
{label}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
) : null} */}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
export default MaterialList
|
||||
|
|
@ -143,15 +143,35 @@ const RecipeDisplay: React.FC<RecipeDisplayProps> = ({ recipes }) => {
|
|||
</div>
|
||||
<Separator />
|
||||
<div className="flex-1 whitespace-pre-wrap p-4 text-sm">
|
||||
{/* show name and productCode of recipe */}
|
||||
{recipe && (
|
||||
<div>
|
||||
<div className="font-semibold">{recipe.name}</div>
|
||||
<div className="text-xs">{recipe.productCode}</div>
|
||||
<div className="font-semibold">Product Code: {recipe.productCode}</div>
|
||||
<div>
|
||||
<span className="font-semibold">Name:</span> {recipe.name}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">Other Name:</span> {recipe.otherName}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">Description:</span> {recipe.Description}
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-semibold">Other Description:</span> {recipe.otherDescription}
|
||||
</div>
|
||||
<div>
|
||||
{
|
||||
// list all recipes
|
||||
recipe.recipes
|
||||
.filter(r => r.isUse)
|
||||
.map((recipe, index) => (
|
||||
<div key={index}>
|
||||
<span className="font-semibold">Recipe {index + 1}:</span> {recipe.materialPathId}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* show recipe description */}
|
||||
{recipe && <div className="mt-4">{recipe.Description}</div>}
|
||||
</div>
|
||||
<Separator className="mt-auto" />
|
||||
<div className="p-4">
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@ import { Separator } from '@/components/ui/separator'
|
|||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { type Recipe01, type Recipes } from '@/models/recipe/schema'
|
||||
import { memo, useMemo, useState } from 'react'
|
||||
import AndroidSwitcher from './android-switcher'
|
||||
import { Search, CupSoda, Wheat, Dessert, Cherry, WineOff } from 'lucide-react'
|
||||
import { type MaterialSetting, type Recipe01, type Recipes } from '@/models/recipe/schema'
|
||||
import { memo, useEffect, useMemo, useState } from 'react'
|
||||
import { Search, CupSoda, Wheat, Dessert, Cherry, WineOff, Server, Loader2 } from 'lucide-react'
|
||||
import Nav from './nav'
|
||||
import RecipeList from './recipe-list'
|
||||
import { isBefore, isToday } from 'date-fns'
|
||||
import { format, isBefore, isToday } from 'date-fns'
|
||||
import RecipeDisplay from './recipe-display'
|
||||
import MaterialList from './material-list'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import taoAxios from '@/lib/taoAxios'
|
||||
|
||||
interface RecipeMenuProps {
|
||||
recipes: Recipes
|
||||
|
|
@ -20,13 +23,40 @@ interface RecipeMenuProps {
|
|||
isDevBranch: boolean
|
||||
}
|
||||
const RecipeMenu: React.FC<RecipeMenuProps> = memo(({ recipes, recipe01, defaultSize, isDevBranch }) => {
|
||||
recipe01 = useMemo(() => {
|
||||
return recipe01.sort((a, b) => (a.LastChange && b.LastChange && isBefore(a.LastChange, b.LastChange) ? 1 : -1))
|
||||
}, [recipe01])
|
||||
const [recipeList, setRecipeList] = useState<Recipe01[]>(recipe01)
|
||||
|
||||
const sortedRecipe01 = useMemo(() => {
|
||||
return recipeList.sort((a, b) => (a.LastChange && b.LastChange && isBefore(a.LastChange, b.LastChange) ? 1 : -1))
|
||||
}, [recipeList])
|
||||
|
||||
const { isPending, isSuccess, isError, mutate } = useMutation({
|
||||
mutationFn: async () => {
|
||||
return taoAxios.post('/v2/recipes/', recipes, {
|
||||
params: {
|
||||
country_id: 'tha'
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
const recipesFiltered = recipe01.filter(
|
||||
item =>
|
||||
item.productCode.toLowerCase().includes(search.toLowerCase()) ||
|
||||
(item.name && item.name.toLowerCase().includes(search.toLowerCase()))
|
||||
)
|
||||
setRecipeList(recipesFiltered)
|
||||
} else {
|
||||
setRecipeList(recipe01)
|
||||
}
|
||||
}, [search])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ResizablePanel defaultSize={defaultSize} minSize={30}>
|
||||
<ResizablePanel id="recipe-panel" defaultSize={defaultSize} minSize={30}>
|
||||
<Tabs defaultValue="all">
|
||||
<div className="flex items-center px-4 py-2">
|
||||
<h1 className="text-xl font-bold">
|
||||
|
|
@ -43,18 +73,37 @@ const RecipeMenu: React.FC<RecipeMenuProps> = memo(({ recipes, recipe01, default
|
|||
</div>
|
||||
<Separator />
|
||||
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="pb-3 flex justify-end items-end">
|
||||
<Button className="bg-primary text-white" onClick={() => mutate()}>
|
||||
{isPending ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
<span>Updating...</span>
|
||||
</div>
|
||||
) : isSuccess ? (
|
||||
'Updated'
|
||||
) : isError ? (
|
||||
'Error'
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Server size={20} />
|
||||
Up Recipe to Server
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<form>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="Search" className="pl-8" />
|
||||
<Input placeholder="Search" className="pl-8" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<TabsContent value="all" className="m-0">
|
||||
<RecipeList items={recipe01} />
|
||||
<RecipeList items={sortedRecipe01} />
|
||||
</TabsContent>
|
||||
<TabsContent value="today" className="m-0">
|
||||
<RecipeList items={recipe01.filter(item => item.LastChange && isToday(item.LastChange))} />
|
||||
<RecipeList items={sortedRecipe01.filter(item => item.LastChange && isToday(item.LastChange))} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</ResizablePanel>
|
||||
|
|
@ -66,13 +115,61 @@ const RecipeMenu: React.FC<RecipeMenuProps> = memo(({ recipes, recipe01, default
|
|||
)
|
||||
})
|
||||
|
||||
interface MaterialsProps {
|
||||
recipes: Recipes
|
||||
defaultSize?: number
|
||||
isDevBranch: boolean
|
||||
}
|
||||
const Materials: React.FC<MaterialsProps> = memo(({ recipes, defaultSize, isDevBranch }) => {
|
||||
const [materialSettingList, setMaterialSettingList] = useState<MaterialSetting[]>(recipes.MaterialSetting)
|
||||
|
||||
const sortedMaterialSettingList = useMemo(() => {
|
||||
return materialSettingList.sort((a, b) => (a.id < b.id ? 1 : -1))
|
||||
}, [materialSettingList])
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (search) {
|
||||
const materialSettingsFiltered = recipes.MaterialSetting.filter(
|
||||
item =>
|
||||
item.materialName.toLowerCase().includes(search.toLowerCase()) ||
|
||||
item.id.toString().includes(search.toLowerCase())
|
||||
)
|
||||
setMaterialSettingList(materialSettingsFiltered)
|
||||
} else {
|
||||
setMaterialSettingList(recipes.MaterialSetting)
|
||||
}
|
||||
}, [search])
|
||||
|
||||
return (
|
||||
<>
|
||||
<ResizablePanel id="material-panel" defaultSize={defaultSize} minSize={30}>
|
||||
<div className="flex items-center px-4 py-2">
|
||||
<h1 className="text-xl font-bold">
|
||||
Recipe Version: {recipes.MachineSetting.configNumber} {isDevBranch ? '(Dev)' : ''}
|
||||
</h1>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<form>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="Search" className="pl-8" value={search} onChange={e => setSearch(e.target.value)} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<MaterialList items={sortedMaterialSettingList} />
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
<ResizablePanel defaultSize={defaultSize}>
|
||||
<RecipeDisplay recipes={recipes} />
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
interface RecipeEditorProps {
|
||||
androids: {
|
||||
label: string
|
||||
deviceName: string
|
||||
serial: string
|
||||
icon: React.ReactNode
|
||||
}[]
|
||||
isDevBranch: boolean
|
||||
recipes: Recipes
|
||||
defaultLayout: number[] | undefined
|
||||
|
|
@ -81,7 +178,6 @@ interface RecipeEditorProps {
|
|||
}
|
||||
|
||||
export const RecipeEditor: React.FC<RecipeEditorProps> = ({
|
||||
androids,
|
||||
recipes,
|
||||
isDevBranch,
|
||||
defaultLayout = [265, 440, 655],
|
||||
|
|
@ -106,7 +202,7 @@ export const RecipeEditor: React.FC<RecipeEditorProps> = ({
|
|||
onLayout={(sizes: number[]) => {
|
||||
document.cookie = `react-resizable-panels:layout=${JSON.stringify(sizes)}`
|
||||
}}
|
||||
className="h-full max-h-[800px] items-stretch"
|
||||
className="h-full max-h-[900px] items-stretch"
|
||||
>
|
||||
<ResizablePanel
|
||||
defaultSize={defaultLayout[0]}
|
||||
|
|
@ -125,7 +221,9 @@ export const RecipeEditor: React.FC<RecipeEditorProps> = ({
|
|||
className={cn(isCollapsed && 'min-w-[50px] transition-all duration-300 ease-in-out')}
|
||||
>
|
||||
<div className={cn('flex h-[52px] items-center justify-center', isCollapsed ? 'h-[52px]' : 'px-2')}>
|
||||
<AndroidSwitcher isCollapsed={isCollapsed} androids={androids} />
|
||||
<span className={cn('text-muted-foreground text-xs ', isCollapsed && 'hidden')}>
|
||||
TimeStamp: {format(recipes.Timestamp, 'dd-MM-yyyy HH:mm:ss')}
|
||||
</span>
|
||||
</div>
|
||||
<Separator />
|
||||
<Nav
|
||||
|
|
@ -189,6 +287,8 @@ export const RecipeEditor: React.FC<RecipeEditorProps> = ({
|
|||
defaultSize={defaultLayout[1]}
|
||||
isDevBranch={isDevBranch}
|
||||
/>
|
||||
) : showListIndex === 2 ? (
|
||||
<Materials recipes={recipes} defaultSize={defaultLayout[1]} isDevBranch={isDevBranch} />
|
||||
) : null}
|
||||
</ResizablePanelGroup>
|
||||
</TooltipProvider>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const RecipeList: React.FC<RecipeListProps> = ({ items }) => {
|
|||
<div className="flex w-full flex-col gap-1">
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="font-semibold">{item.name}</div>
|
||||
<div className="text-md">{item.name}</div>
|
||||
{item.LastChange && isToday(item.LastChange) && (
|
||||
<span className="flex h-2 w-2 rounded-full bg-blue-600" />
|
||||
)}
|
||||
|
|
@ -49,7 +49,9 @@ const RecipeList: React.FC<RecipeListProps> = ({ items }) => {
|
|||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs font-medium">{`${item.productCode}: ${item.name}`}</div>
|
||||
<div className="text-xs">
|
||||
{item.productCode}: {item.name || 'No Name'}
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="line-clamp-2 text-xs text-muted-foreground">{item.substring(0, 300)}</div>
|
||||
{item.labels.length ? (
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
import RecipeForm from './components/recipe-edit-components/recipe-form'
|
||||
|
||||
const RecipeEditPage: React.FC = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Edit Recipe</h1>
|
||||
<RecipeForm />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecipeEditPage
|
||||
|
|
@ -1,36 +1,13 @@
|
|||
import useLocalStorage from '@/hooks/localStorage'
|
||||
import RecipeEditor from './components/recipe-editor-components/recipe-editor'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import { Smartphone } from 'lucide-react'
|
||||
import { type Recipes } from '@/models/recipe/schema'
|
||||
import useFileManager from '@/hooks/filemanager-android'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import useAdb from '@/hooks/useAdb'
|
||||
import { isBefore } from 'date-fns'
|
||||
|
||||
const androids: {
|
||||
label: string
|
||||
deviceName: string
|
||||
serial: string
|
||||
icon: React.ReactNode
|
||||
}[] = [
|
||||
{
|
||||
label: 'Test Device',
|
||||
deviceName: 'Test Device',
|
||||
serial: 'Test Device',
|
||||
icon: <Smartphone />
|
||||
}
|
||||
]
|
||||
|
||||
const RecipesTablePage = () => {
|
||||
// const recipeQuery = useLocalStorage(state => state.recipeQuery)
|
||||
// const getRecipesDashboard = useRecipeDashboard(state => state.getRecipesDashboard)
|
||||
|
||||
// const { data: recipeDashboardList, isLoading } = useQuery({
|
||||
// queryKey: ['recipe-overview'],
|
||||
// queryFn: () => getRecipesDashboard(recipeQuery)
|
||||
// })
|
||||
|
||||
const { layout, collapsed } = useLocalStorage(
|
||||
useShallow(state => ({
|
||||
layout: state.layout,
|
||||
|
|
@ -77,18 +54,9 @@ const RecipesTablePage = () => {
|
|||
}, [readData])
|
||||
|
||||
return (
|
||||
// <div className="flex w-full flex-col gap-3">
|
||||
// <section>
|
||||
// <h1 className="text-3xl font-bold text-gray-900">Recipes</h1>
|
||||
// </section>
|
||||
// <section>
|
||||
// <DataTable data={recipeDashboardList ?? []} columns={columns} isLoading={isLoading} />
|
||||
// </section>
|
||||
// </div>
|
||||
<>
|
||||
{recipes ? (
|
||||
<RecipeEditor
|
||||
androids={androids}
|
||||
defaultLayout={layout}
|
||||
navCollapsedSize={4}
|
||||
recipes={recipes}
|
||||
|
|
|
|||
20
client-electron/src/pages/recipes/recipe.tsx
Normal file
20
client-electron/src/pages/recipes/recipe.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import useAdb from '@/hooks/useAdb'
|
||||
import RecipesTablePage from './recipe-table'
|
||||
|
||||
const RecipePage: React.FC = () => {
|
||||
const adb = useAdb(state => state.adb)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{adb ? (
|
||||
<RecipesTablePage />
|
||||
) : (
|
||||
<div className="flex w-full h-96 justify-center items-center">
|
||||
<div className="text-2xl font-bold">Please connect to the device</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RecipePage
|
||||
Loading…
Add table
Add a link
Reference in a new issue