diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e10762b..2d07fe0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -2228,6 +2228,11 @@ "rollup": "^2.59.0" } }, + "wouter": { + "version": "2.8.0-alpha.2", + "resolved": "https://registry.npmjs.org/wouter/-/wouter-2.8.0-alpha.2.tgz", + "integrity": "sha512-aPsL5m5rW9RiceClOmGj6t5gn9Ut2TJVr98UDi1u9MIRNYiYVflg6vFIjdDYJ4IAyH0JdnkSgGwfo0LQS3k2zg==" + }, "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index fa00aab..e8528ca 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,7 +18,8 @@ "react": "^18.0.0", "react-beautiful-dnd": "^13.1.0", "react-dom": "^18.0.0", - "react-icons": "^4.4.0" + "react-icons": "^4.4.0", + "wouter": "^2.8.0-alpha.2" }, "devDependencies": { "@types/react": "^18.0.0", diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 index 2ccb3cc..833c9c1 100755 --- a/frontend/package.json.md5 +++ b/frontend/package.json.md5 @@ -1 +1 @@ -626e939685488d9757296e235ae50971 \ No newline at end of file +273bafaffaa3c4ec09341a3248f1a7c5 \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 275c792..e425f62 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,103 +1,45 @@ +import { Box, Button, Center, Icon, Text, Tooltip } from "@chakra-ui/react"; +import { useQuery } from "@tanstack/react-query"; +import { BiPlug } from "react-icons/bi"; import { - Box, - Button, - Center, - Code, - Flex, - Icon, - IconButton, - Popover, - PopoverBody, - PopoverCloseButton, - PopoverContent, - PopoverHeader, - PopoverTrigger, - Table, - Tbody, - Td, - Text, - Th, - Thead, - Tooltip, - Tr, - useToast, -} from "@chakra-ui/react"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { useState } from "react"; -import { - FiArrowDown, - FiArrowUp, - FiMenu, + FiPackage, FiRefreshCcw, FiSmartphone, FiUpload, } from "react-icons/fi"; -import { BiMenu, BiPlug } from "react-icons/bi"; -import { MdDragIndicator } from "react-icons/md"; -import { - ChangePackOrder, - GetDeviceInfos, - InstallPack, - ListPacks, - OpenFile, -} from "../wailsjs/go/main/App"; +import { Link, Route } from "wouter"; +import { ListPacks } from "../wailsjs/go/main/App"; import { DetailsModal } from "./components/DetailsModal"; +import { DeviceModal } from "./components/DeviceModal"; +import { Header } from "./components/Header"; import { IsInstallingModal } from "./components/IsInstallingModal"; import { NewPackModal } from "./components/NewPackModal"; -import { PackTag } from "./components/PackTag"; +import { PackList } from "./components/PackList"; import { SyncMdMenu } from "./components/SyncMdMenu"; -import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd"; -import { lunii } from "../wailsjs/go/models"; -import { useChangePackOrder } from "./hooks/useChangePackOrder"; +import { useDeviceQuery } from "./hooks/useApi"; +import { useInstallPack } from "./hooks/useInstallPack"; function App() { const { data: device, refetch: refetchDevice, isLoading: isLoadingDevice, - } = useQuery(["device"], GetDeviceInfos); - const { data: packs, refetch: refetchPacks } = useQuery( - ["packs"], - ListPacks, - { - refetchOnMount: true, - enabled: !!device, - } - ); - - const [isInstalling, setIsInstalling] = useState(false); - const toast = useToast(); - - const handleInstallStory = async () => { - const packPath = await OpenFile("Select your pack"); - if (!packPath) return; - - setIsInstalling(true); - try { - await InstallPack(packPath); - toast({ - title: "The pack was installed on the device", - status: "success", - isClosable: true, - }); - await refetchPacks(); - } catch (e) { - toast({ - title: "Could not install pack on device", - status: "warning", - description: - "Something went wrong while installing the pack on your device", - }); - } - setIsInstalling(false); - }; - - const { mutate: handleChangePackOrder } = useChangePackOrder(); + } = useDeviceQuery(); if (isLoadingDevice) return null; return ( + + + + + + + + + + {!device && ( <> @@ -110,7 +52,16 @@ function App() { Refresh - + + +
@@ -122,126 +73,10 @@ function App() { {device && ( - - - - - - - - - Details - - - Serial Number {device.serialNumber} - Version{" "} - - {device.firmwareVersionMajor}. - {device.firmwareVersionMinor} - - - - - - - - - - - - - - - - - Title - - - - { - if (!a.destination) return; - handleChangePackOrder({ - id: a.draggableId, - position: a.destination?.index, - }); - }} - > - - {(provided) => ( - - {packs?.map((p, i) => ( - - {(provided) => ( - - - } - variant="ghost" - mr={1} - {...provided.dragHandleProps} - /> - - - - {p.title || p.uuid} - - - - - - - - - )} - - ))} - - )} - - - +
+ )} - ); } diff --git a/frontend/src/components/DetailsModal.tsx b/frontend/src/components/DetailsModal.tsx index e8890b2..dfb95c6 100644 --- a/frontend/src/components/DetailsModal.tsx +++ b/frontend/src/components/DetailsModal.tsx @@ -22,17 +22,18 @@ import { DeleteModal } from "./DeleteModal"; import { PackTag } from "./PackTag"; import parse from "html-react-parser"; import { ListPacks, RemovePack } from "../../wailsjs/go/main/App"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useLocation, useRoute } from "wouter"; -export const DetailsModal: FC<{ - uuid: number[]; -}> = ({ uuid }) => { - const { isOpen, onOpen, onClose } = useDisclosure(); - const { data: packs, refetch: refetchPacks } = useQuery(["packs"], ListPacks); - const pack = packs?.find((p) => p.uuid === uuid); +export const DetailsModal: FC = () => { + const [, setLocation] = useLocation(); + const [, params] = useRoute("/pack/:uuid"); const toast = useToast(); + const { data: packs } = useQuery(["packs"], ListPacks); + const pack = packs?.find((p) => (p.uuid as any) === params?.uuid); if (!pack) return null; + const queryClient = useQueryClient(); const parsedDescription = parse(pack.description); @@ -43,20 +44,13 @@ export const DetailsModal: FC<{ status: "success", isClosable: true, }); - onClose(); - await refetchPacks(); + await queryClient.invalidateQueries(["packs"]); + setLocation("/"); }; return ( <> - } - onClick={onOpen} - /> - - + setLocation("/")}> diff --git a/frontend/src/components/DeviceModal.tsx b/frontend/src/components/DeviceModal.tsx new file mode 100644 index 0000000..e8c6643 --- /dev/null +++ b/frontend/src/components/DeviceModal.tsx @@ -0,0 +1,64 @@ +import { + Box, + Button, + Code, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Progress, + Text, +} from "@chakra-ui/react"; +import { useQuery } from "@tanstack/react-query"; +import { FC } from "react"; +import { useLocation, useRoute } from "wouter"; +import { GetDeviceInfos } from "../../wailsjs/go/main/App"; +import { useDeviceQuery } from "../hooks/useApi"; +import { formatBytes } from "../utils"; + +export const DeviceModal: FC = () => { + const { data: device } = useDeviceQuery(); + const [_, setLocation] = useLocation(); + + if (!device) return null; + + return ( + <> + setLocation("/")}> + + + My Lunii + + + + Serial Number {device.serialNumber} + + + Version{" "} + + {device.firmwareVersionMajor}.{device.firmwareVersionMinor} + + + + Space{" "} + + {formatBytes(device.diskUsage.used)} /{" "} + {formatBytes(device.diskUsage.free)} + + + + + + + + + + + ); +}; diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx new file mode 100644 index 0000000..d2ead2d --- /dev/null +++ b/frontend/src/components/Header.tsx @@ -0,0 +1,54 @@ +import { Button, Box } from "@chakra-ui/react"; +import { FiSmartphone, FiUpload, FiPackage } from "react-icons/fi"; +import { Link } from "wouter"; +import { useInstallPack } from "../hooks/useInstallPack"; +import { IsInstallingModal } from "./IsInstallingModal"; +import { SyncMdMenu } from "./SyncMdMenu"; + +export const Header = () => { + const { mutate: handleInstallPack, isLoading: isInstalling } = + useInstallPack(); + return ( + + + + + + + + + + + + + + + + ); +}; diff --git a/frontend/src/components/IsInstallingModal.tsx b/frontend/src/components/IsInstallingModal.tsx index f31cea9..df99d9c 100644 --- a/frontend/src/components/IsInstallingModal.tsx +++ b/frontend/src/components/IsInstallingModal.tsx @@ -21,7 +21,7 @@ export const IsInstallingModal: FC<{ isOpen: boolean }> = ({ isOpen }) => { Installing Pack Wait a few moments, it's almost ready. - + diff --git a/frontend/src/components/NewPackModal.tsx b/frontend/src/components/NewPackModal.tsx index 9825c61..3ba6d00 100644 --- a/frontend/src/components/NewPackModal.tsx +++ b/frontend/src/components/NewPackModal.tsx @@ -1,66 +1,46 @@ import { Alert, - AlertDialog, - AlertDialogBody, - AlertDialogContent, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogOverlay, AlertIcon, Box, Button, Flex, - Icon, Link, + Modal, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, Text, - Tooltip, - useDisclosure, useToast, } from "@chakra-ui/react"; -import React, { useState } from "react"; -import { BiCog, BiMessage, BiPackage, BiQuestionMark } from "react-icons/bi"; -import { FiCloudLightning, FiFile, FiFolder, FiPackage } from "react-icons/fi"; +import { useState } from "react"; +import { FiFile, FiFolder, FiPackage } from "react-icons/fi"; +import { useLocation } from "wouter"; import { CreatePack, OpenDirectory, SaveFile } from "../../wailsjs/go/main/App"; import { basename, dirname } from "../utils"; export const NewPackModal = () => { - const { isOpen, onOpen, onClose } = useDisclosure(); - const cancelRef = React.useRef() as any; const toast = useToast(); const [directoryPath, setDirectoryPath] = useState(""); const [destinationPath, setDestinationPath] = useState(""); + const [_, setLocation] = useLocation(); const handleClose = () => { - setDirectoryPath(""); - setDestinationPath(""); - onClose(); + setLocation("/"); }; return ( <> - - - - - - + + + + Creating a new pack - + - + To create a new pack, first select a structured directory from your system @@ -108,17 +88,14 @@ export const NewPackModal = () => { )} - + - - + + {directoryPath && destinationPath && ( )} - - - - + + + + ); }; diff --git a/frontend/src/components/PackList.tsx b/frontend/src/components/PackList.tsx new file mode 100644 index 0000000..942a57b --- /dev/null +++ b/frontend/src/components/PackList.tsx @@ -0,0 +1,98 @@ +import { Flex, IconButton, Box } from "@chakra-ui/react"; +import { useQuery } from "@tanstack/react-query"; + +import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; +import { FiMoreVertical } from "react-icons/fi"; +import { MdDragIndicator } from "react-icons/md"; +import { Link } from "wouter"; +import { ListPacks } from "../../wailsjs/go/main/App"; +import { useDeviceQuery } from "../hooks/useApi"; +import { useChangePackOrder } from "../hooks/useChangePackOrder"; +import { PackTag } from "./PackTag"; + +export const PackList = () => { + const { data: device } = useDeviceQuery(); + const { mutate: handleChangePackOrder } = useChangePackOrder(); + + const { data: packs } = useQuery(["packs"], ListPacks, { + enabled: !!device, + }); + + return ( + + + + Title + + + + { + if (!a.destination) return; + handleChangePackOrder({ + id: a.draggableId, + position: a.destination?.index, + }); + }} + > + + {(provided) => ( + + {packs?.map((p, i) => ( + + {(provided) => ( + + + } + variant="ghost" + mr={1} + {...provided.dragHandleProps} + /> + + + + {p.title || p.uuid} + + + + + + + } + /> + + + + )} + + ))} + + )} + + + + ); +}; diff --git a/frontend/src/components/SyncMdMenu.tsx b/frontend/src/components/SyncMdMenu.tsx index 488758d..fa7186c 100644 --- a/frontend/src/components/SyncMdMenu.tsx +++ b/frontend/src/components/SyncMdMenu.tsx @@ -6,7 +6,7 @@ import { MenuItem, useToast, } from "@chakra-ui/react"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; import { FiCloud, FiDownload, FiHardDrive } from "react-icons/fi"; import { ListPacks, @@ -17,7 +17,8 @@ import { } from "../../wailsjs/go/main/App"; export const SyncMdMenu = () => { - const { data: packs, refetch } = useQuery(["packs"], ListPacks); + const { data: packs } = useQuery(["packs"], ListPacks); + const queryClient = useQueryClient(); const toast = useToast(); const handleSyncLuniiStoreMetadata = async () => { @@ -36,7 +37,7 @@ export const SyncMdMenu = () => { }); } - await refetch(); + await queryClient.invalidateQueries(["packs"]); }; const handleSyncStudioMetadata = async (customPath = false) => { @@ -60,7 +61,7 @@ export const SyncMdMenu = () => { description: err as string, }); } - await refetch(); + await queryClient.invalidateQueries(["packs"]); }; return ( diff --git a/frontend/src/hooks/useApi.ts b/frontend/src/hooks/useApi.ts new file mode 100644 index 0000000..df37bc2 --- /dev/null +++ b/frontend/src/hooks/useApi.ts @@ -0,0 +1,7 @@ +import { useQuery } from "@tanstack/react-query"; +import { GetDeviceInfos } from "../../wailsjs/go/main/App"; + +export const useDeviceQuery = () => + useQuery(["device"], GetDeviceInfos, { + staleTime: Infinity, + }); diff --git a/frontend/src/hooks/useChangePackOrder.tsx b/frontend/src/hooks/useChangePackOrder.ts similarity index 100% rename from frontend/src/hooks/useChangePackOrder.tsx rename to frontend/src/hooks/useChangePackOrder.ts diff --git a/frontend/src/hooks/useInstallPack.ts b/frontend/src/hooks/useInstallPack.ts new file mode 100644 index 0000000..daa3175 --- /dev/null +++ b/frontend/src/hooks/useInstallPack.ts @@ -0,0 +1,36 @@ +import { useToast } from "@chakra-ui/react"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import { OpenFile, InstallPack, ListPacks } from "../../wailsjs/go/main/App"; + +export const useInstallPack = () => { + const toast = useToast(); + const [isLoading, setIsLoading] = useState(false); + const queryClient = useQueryClient(); + + const mutate = async () => { + const packPath = await OpenFile("Select your pack"); + if (!packPath) return; + + setIsLoading(true); + try { + await InstallPack(packPath); + toast({ + title: "The pack was installed on the device", + status: "success", + isClosable: true, + }); + await queryClient.invalidateQueries(["device"]); + await queryClient.invalidateQueries(["packs"]); + } catch (e) { + toast({ + title: "Could not install pack on device", + status: "warning", + description: + "Something went wrong while installing the pack on your device", + }); + } + setIsLoading(false); + }; + return { mutate, isLoading }; +}; diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index 5c4f2d1..e63a69c 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -5,3 +5,15 @@ export const basename = (path: string) => { export const dirname = (path: string) => { return path.replace(basename(path), ""); }; + +export const formatBytes = (bytes: number, decimals = 2) => { + if (bytes === 0) return "0 Bytes"; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; +}; diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 5700c67..5dd9fd4 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1,5 +1,21 @@ export namespace lunii { + export class DiskUsage { + free: number; + used: number; + total: number; + + static createFrom(source: any = {}) { + return new DiskUsage(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.free = source["free"]; + this.used = source["used"]; + this.total = source["total"]; + } + } export class Device { mountPoint: string; uuid: number[]; @@ -10,6 +26,8 @@ export namespace lunii { firmwareVersionMinor: number; sdCardSize: number; sdCardUsed: number; + // Go type: DiskUsage + diskUsage?: any; static createFrom(source: any = {}) { return new Device(source); @@ -26,7 +44,26 @@ export namespace lunii { this.firmwareVersionMinor = source["firmwareVersionMinor"]; this.sdCardSize = source["sdCardSize"]; this.sdCardUsed = source["sdCardUsed"]; + this.diskUsage = this.convertValues(source["diskUsage"], null); } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } } export class Metadata { uuid: number[]; diff --git a/go.mod b/go.mod index 2746c8b..1f87ee3 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/blang/semver v3.5.1+incompatible github.com/google/uuid v1.3.0 - github.com/olup/lunii-cli v0.0.0-20220726200600-95d1c00bfe0d + github.com/olup/lunii-cli v0.0.0-20220727052208-9ff36a20c348 github.com/rhysd/go-github-selfupdate v1.2.3 github.com/wailsapp/wails/v2 v2.0.0-beta.42 ) @@ -55,6 +55,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/qiniu/audio v0.2.1 // indirect github.com/qiniu/x v1.11.9 // indirect + github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.8.1 // indirect github.com/tcnksm/go-gitconfig v0.1.2 // indirect diff --git a/go.sum b/go.sum index 7599653..62b9940 100644 --- a/go.sum +++ b/go.sum @@ -170,8 +170,6 @@ github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n5 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/olup/lunii-cli v0.0.0-20220726200600-95d1c00bfe0d h1:X7qACZbLkpzfwYFKrsMJqGEsNsFQT69lTbb3rDwyD5Q= -github.com/olup/lunii-cli v0.0.0-20220726200600-95d1c00bfe0d/go.mod h1:sHaY9/XAS3GfnM4Hz/moy2IFy5XOmxW1JkiCidfgIvY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= @@ -213,6 +211,8 @@ github.com/qiniu/x v1.11.9 h1:IfQNdeNcK43Q1+b/LdrcqmWjlhxq051YVBnua8J2qN8= github.com/qiniu/x v1.11.9/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= github.com/rhysd/go-github-selfupdate v1.2.3 h1:iaa+J202f+Nc+A8zi75uccC8Wg3omaM7HDeimXA22Ag= github.com/rhysd/go-github-selfupdate v1.2.3/go.mod h1:mp/N8zj6jFfBQy/XMYoWsmfzxazpPAODuqarmPDe2Rg= +github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285 h1:d54EL9l+XteliUfUCGsEwwuk65dmmxX85VXF+9T6+50= +github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285/go.mod h1:fxIDly1xtudczrZeOOlfaUvd2OPb2qZAPuWdU2BsBTk= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=