Context menu and placeholder images

This commit is contained in:
Sacha Weatherstone
2022-02-17 00:17:01 +11:00
parent 3f1297411d
commit 685661dc70
11 changed files with 326 additions and 18 deletions
+13 -10
View File
@@ -5,6 +5,7 @@ import { Connection } from '@components/Connection';
import { useRoute } from '@core/router';
import { useAppSelector } from '@hooks/useAppSelector';
import { ContextMenu } from './components/generic/ContextMenu';
import { BottomNav } from './components/menu/BottomNav';
import { Extensions } from './pages/Extensions/Index';
import { Messages } from './pages/Messages';
@@ -17,17 +18,19 @@ export const App = (): JSX.Element => {
return (
<div className={`h-screen w-screen ${darkMode ? 'dark' : ''}`}>
<Connection />
<div className="flex h-full flex-col bg-gray-200 dark:bg-secondaryDark">
<div className="flex min-h-0 w-full flex-grow">
{route.name === 'messages' && <Messages />}
{route.name === 'nodes' && <Nodes />}
{route.name === 'map' && <Map />}
{route.name === 'extensions' && <Extensions />}
{route.name === false && <NotFound />}
<ContextMenu>
<Connection />
<div className="flex h-full flex-col bg-gray-200 dark:bg-secondaryDark">
<div className="flex min-h-0 w-full flex-grow">
{route.name === 'messages' && <Messages />}
{route.name === 'nodes' && <Nodes />}
{route.name === 'map' && <Map />}
{route.name === 'extensions' && <Extensions />}
{route.name === false && <NotFound />}
</div>
<BottomNav />
</div>
<BottomNav />
</div>
</ContextMenu>
</div>
);
};
+93
View File
@@ -0,0 +1,93 @@
import React from 'react';
import { m } from 'framer-motion';
import { FiActivity, FiAperture, FiTag } from 'react-icons/fi';
export interface ContextMenuProps {
children: React.ReactNode;
}
export const ContextMenu = ({ children }: ContextMenuProps): JSX.Element => {
const [visible, setVisible] = React.useState(false);
const [position, setPosition] = React.useState({ x: 0, y: 0 });
const [selectedValue, setSelectedValue] = React.useState<string>();
const doSomething = (selectedValue: string) => {
setSelectedValue(selectedValue);
};
const showContextMenu = (event: React.MouseEvent<HTMLDivElement>) => {
event.preventDefault();
setVisible(false);
const newPosition = {
x: event.pageX,
y: event.pageY,
};
setPosition(newPosition);
setVisible(true);
};
const hideContextMenu = (event: React.MouseEvent<HTMLDivElement>) => {
setVisible(false);
};
return (
<div
className="h-full"
onContextMenu={showContextMenu}
onClick={hideContextMenu}
>
{children}
{selectedValue && <h1>{selectedValue} is selected</h1>}
{visible && (
<div
style={{ top: position.y, left: position.x }}
className="fixed z-50 w-60 gap-2 divide-y divide-gray-300 rounded-md border border-gray-300 font-medium shadow-md backdrop-blur-xl dark:divide-gray-600 dark:border-gray-600 dark:text-gray-400"
>
<div className="cursor-pointer first:rounded-t-md last:rounded-b-md hover:dark:bg-secondaryDark">
<m.div
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
className="flex gap-2 p-2"
>
<div className="my-auto">
<FiActivity />
</div>
<div className="truncate">Menu item</div>
</m.div>
</div>
<div className="cursor-pointer first:rounded-t-md last:rounded-b-md hover:dark:bg-secondaryDark">
<m.div
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
className="flex gap-2 p-2"
>
<div className="my-auto">
<FiAperture />
</div>
<div className="truncate">Menu item 2</div>
</m.div>
</div>
<div className="cursor-pointer first:rounded-t-md last:rounded-b-md hover:dark:bg-secondaryDark">
<m.div
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.99 }}
className="flex gap-2 p-2"
>
<div className="my-auto">
<FiTag />
</div>
<div className="truncate">
Menu item 3 with a very long name that should wrap
</div>
</m.div>
</div>
</div>
)}
</div>
);
};
+2
View File
@@ -85,6 +85,8 @@ export const meshtasticSlice = createSlice({
initialState,
reducers: {
addLogEvent: (state, action: PayloadAction<Types.LogEventPacket>) => {
console.log(action.payload.packet);
state.logs.push(action.payload);
},
setDeviceStatus: (state, action: PayloadAction<Types.DeviceStatusEnum>) => {
+18 -1
View File
@@ -1,5 +1,6 @@
import React from 'react';
import { AnimatePresence, m } from 'framer-motion';
import useSWR from 'swr';
import fetcher from '@app/core/utils/fetcher';
@@ -27,6 +28,7 @@ export const FileBrowser = (): JSX.Element => {
const connectionParams = useAppSelector(
(state) => state.app.connectionParams,
);
const darkMode = useAppSelector((state) => state.app.darkMode);
const { data } = useSWR<Files>(
`${connectionParams.HTTP.tls ? 'https' : 'http'}://${
@@ -42,7 +44,22 @@ export const FileBrowser = (): JSX.Element => {
<div className="my-auto w-1/3">FileName</div>
<div className="my-auto w-1/3">Actions</div>
</div>
<div className="px-4">
<div className="h-full px-4">
<AnimatePresence>
{(!data || data?.data.files.length === 0) && (
<div className="flex h-full w-full">
<m.img
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="m-auto h-64 w-64 text-green-500"
src={`/placeholders/${
darkMode ? 'Files Dark.svg' : 'Files.svg'
}`}
/>
</div>
)}
</AnimatePresence>
{data?.data.files.map((file) => (
<div
key={file.name}
+25 -2
View File
@@ -1,13 +1,14 @@
import type React from 'react';
import { m } from 'framer-motion';
import { FiArrowRight } from 'react-icons/fi';
import { AnimatePresence, m } from 'framer-motion';
import { FiArrowRight, FiPaperclip } from 'react-icons/fi';
import { useAppSelector } from '@app/hooks/useAppSelector';
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
export const Logs = (): JSX.Element => {
const logs = useAppSelector((state) => state.meshtastic.logs);
const darkMode = useAppSelector((state) => state.app.darkMode);
type lookupType = { [key: number]: string };
@@ -54,6 +55,21 @@ export const Logs = (): JSX.Element => {
className="
block h-full flex-col overflow-y-auto py-4 px-2 font-mono text-xs dark:text-gray-400"
>
<AnimatePresence>
{logs.length === 0 && (
<div className="flex h-full w-full">
<m.img
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="m-auto h-64 w-64 text-green-500"
src={`/placeholders/${
darkMode ? 'View Code Dark.svg' : 'View Code.svg'
}`}
/>
</div>
)}
</AnimatePresence>
{logs.map((log, index) => (
<tr key={index} className="group hover:bg-secondaryDark">
<m.td
@@ -87,6 +103,13 @@ export const Logs = (): JSX.Element => {
<Wrapper className={levelLookup[log.level]}>
[{Protobuf.LogRecord_Level[log.level]}]{/* </div> */}
</Wrapper>
<td
className={`m-auto ${
log.packet ? '' : 'dark:text-secondaryDark'
}`}
>
<FiPaperclip />
</td>
<td className="truncate pl-1">{log.message}</td>
</tr>
))}