framer-motion#m TypeScript Examples

The following examples show how to use framer-motion#m. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Logs.tsx    From meshtastic-web with GNU General Public License v3.0 6 votes vote down vote up
Wrapper = ({
  className,
  children,
}: {
  className?: string;
  children: React.ReactNode;
}): JSX.Element => (
  <td className={className}>
    <m.div
      className="-my-0.5 flex max-w-min cursor-pointer truncate rounded-sm px-0.5 hover:bg-gray-400 dark:hover:bg-gray-700"
      whileHover={{ scale: 1.01 }}
      whileTap={{ scale: 0.99 }}
    >
      {children}
    </m.div>
  </td>
)
Example #2
Source File: Card.tsx    From meshtastic-web with GNU General Public License v3.0 6 votes vote down vote up
Card = ({
  className,
  title,
  actions,
  border,
  children,
}: CardProps): JSX.Element => {
  return (
    <div
      className={`flex h-full w-full flex-col rounded-md drop-shadow-md ${
        border ? 'border border-gray-400 dark:border-gray-600' : ''
      } ${className ?? ''}`}
    >
      {(title || actions) && (
        <div className="w-full select-none justify-between rounded-t-md border-b border-gray-400 bg-gray-200 p-2 px-2 text-lg font-medium dark:border-gray-600 dark:bg-tertiaryDark dark:text-white">
          <div className="handle flex h-8 justify-between">
            <div className="my-auto ml-2 truncate">{title}</div>
            {actions}
          </div>
        </div>
      )}

      <m.div
        className={`flex flex-grow select-none flex-col gap-4 bg-white p-4 dark:bg-primaryDark  ${
          title || actions ? 'rounded-b-md' : 'rounded-md'
        }`}
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        transition={{ duration: 0.1 }}
      >
        {children}
      </m.div>
    </div>
  );
}
Example #3
Source File: ContextItem.tsx    From meshtastic-web with GNU General Public License v3.0 6 votes vote down vote up
ContextItem = ({ title, icon }: ContextItem): JSX.Element => {
  return (
    <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">{icon}</div>
        <div className="truncate">{title}</div>
      </m.div>
    </div>
  );
}
Example #4
Source File: BottomNavItem.tsx    From meshtastic-web with GNU General Public License v3.0 6 votes vote down vote up
BottomNavItem = ({
  tooltip,
  onClick,
  className,
  children,
}: BottomNavItemProps) => {
  return (
    <Tooltip disabled={!tooltip} content={tooltip}>
      <div
        onClick={onClick}
        className={`group flex h-full cursor-pointer select-none p-1 hover:bg-gray-300 dark:text-white dark:hover:bg-primaryDark ${className}`}
      >
        <m.div className="flex w-full gap-1" whileTap={{ scale: 0.99 }}>
          {children}
        </m.div>
      </div>
    </Tooltip>
  );
}
Example #5
Source File: SidebarItem.tsx    From meshtastic-web with GNU General Public License v3.0 6 votes vote down vote up
SidebarItem = ({
  selected,
  setSelected,
  actions,
  children,
}: SidebarItemProps): JSX.Element => {
  return (
    <div
      onClick={(): void => {
        setSelected();
      }}
      className={`mx-2 flex cursor-pointer select-none rounded-md border bg-gray-200 p-2 shadow-md first:mt-2 last:mb-2 hover:border-primary dark:bg-tertiaryDark dark:hover:border-primary ${
        selected ? 'border-primary' : 'border-gray-400 dark:border-gray-600'
      }`}
    >
      <m.div
        className="flex w-full justify-between"
        whileHover={{ scale: 1.01 }}
        whileTap={{ scale: 0.99 }}
      >
        <div className="flex gap-2">{children}</div>

        <div className="flex gap-1">{actions}</div>
      </m.div>
    </div>
  );
}
Example #6
Source File: IconButton.tsx    From meshtastic-web with GNU General Public License v3.0 6 votes vote down vote up
IconButton = ({
  icon,
  tooltip,
  nested,
  active,
  disabled,
  className,
  ...props
}: IconButtonProps): JSX.Element => {
  return (
    <Tooltip disabled={!tooltip} content={tooltip}>
      <button
        type="button"
        disabled={disabled}
        className={`rounded-md p-2 hover:bg-gray-300 ${
          active ? 'bg-gray-300 dark:bg-secondaryDark' : ''
        } ${
          nested ? 'dark:hover:bg-primaryDark' : 'dark:hover:bg-secondaryDark'
        } ${
          disabled ? 'cursor-not-allowed text-gray-400 dark:text-gray-700' : ''
        } ${className ?? ''}`}
        {...props}
      >
        <m.div
          whileHover={{ scale: 1.01 }}
          whileTap={{ scale: 0.95 }}
          className="my-auto text-gray-600 dark:text-gray-400"
        >
          {icon}
        </m.div>
        <span className="sr-only">Refresh</span>
      </button>
    </Tooltip>
  );
}
Example #7
Source File: Tab.tsx    From meshtastic-web with GNU General Public License v3.0 5 votes vote down vote up
Tab = ({
  link,
  icon,
  title,
  active,
  activeRight,
  activeLeft,
}: TabProps): JSX.Element => {
  return (
    <div
      className={`max-w-[10rem] md:flex-grow ${
        active
          ? 'bg-white dark:bg-primaryDark'
          : 'bg-gray-300 dark:bg-secondaryDark'
      }`}
    >
      <div
        className={`group flex flex-grow cursor-pointer select-none py-2 hover:underline dark:text-white ${
          active
            ? 'z-10 rounded-t-lg bg-gray-300 shadow-inner dark:bg-secondaryDark'
            : 'bg-white drop-shadow-md dark:bg-primaryDark'
        } ${activeRight ? 'rounded-br-lg' : ''} ${
          activeLeft ? 'rounded-bl-lg' : ''
        }`}
        {...(link && link)}
      >
        <div
          className={`my-auto w-full px-3 ${
            active || activeLeft
              ? ''
              : 'border-l border-gray-400 dark:border-gray-600'
          }`}
        >
          <m.div
            className="flex gap-2"
            whileHover={{ scale: 1.01 }}
            whileTap={{ scale: 0.99 }}
          >
            <div className="my-auto">{icon}</div>
            <div className="hidden md:flex">{title}</div>
          </m.div>
        </div>
      </div>
    </div>
  );
}
Example #8
Source File: FileBrowser.tsx    From meshtastic-web with GNU General Public License v3.0 5 votes vote down vote up
FileBrowser = (): JSX.Element => {
  const connectionParams = useAppSelector(
    (state) => state.app.connectionParams,
  );
  const appState = useAppSelector((state) => state.app);
  const meshtasticState = useAppSelector((state) => state.meshtastic);

  const { data } = useSWR<Files>(
    `${connectionParams.HTTP.tls ? 'https' : 'http'}://${
      connectionParams.HTTP.address
    }${
      meshtasticState.radio.hardware.firmwareVersion.includes('1.2')
        ? '/json/spiffs/browse/static'
        : '/json/fs/browse/static'
    }`,
    fetcher,
  );

  return (
    <div className="flex h-full p-4">
      <Card
        title="File Browser"
        actions={<Button icon={<FiFilePlus />}>Upload File</Button>}
        className="flex-grow flex-col"
      >
        <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={appState.darkMode ? '/Files_Dark.svg' : '/Files.svg'}
                />
              </div>
            )}
          </AnimatePresence>
          {data?.data.files.map((file) => (
            <div
              key={file.name}
              className="flex h-10 w-full border-b border-gray-400 px-4 font-medium dark:border-gray-600 dark:text-white"
            >
              <div className="my-auto  w-1/3">
                <a
                  target="_blank"
                  rel="noopener noreferrer"
                  href={`${connectionParams.HTTP.tls ? 'https' : 'http'}://${
                    connectionParams.HTTP.address
                  }/${file.name.replace('static/', '')}`}
                >
                  {file.name.replace('static/', '').replace('.gz', '')}
                </a>
              </div>
              <div className="my-auto  w-1/3"></div>
            </div>
          ))}
        </div>
      </Card>
    </div>
  );
}
Example #9
Source File: Button.tsx    From meshtastic-web with GNU General Public License v3.0 5 votes vote down vote up
Button = ({
  icon,
  className,
  border,
  size = ButtonSize.Medium,
  confirmAction,
  onClick,
  disabled,
  children,
}: ButtonProps): JSX.Element => {
  const [hasConfirmed, setHasConfirmed] = useState(false);

  const handleConfirm = (): void => {
    if (typeof confirmAction == 'function') {
      if (hasConfirmed) {
        void confirmAction();
      }
      setHasConfirmed(true);
      setTimeout(() => {
        setHasConfirmed(false);
      }, 3000);
    }
  };

  return (
    <m.button
      whileHover={{ scale: 1.01 }}
      whileTap={{ scale: 0.97 }}
      onClick={handleConfirm}
      className={`flex select-none items-center space-x-3 rounded-md border border-transparent text-sm focus-within:border-primary focus-within:shadow-border dark:text-white dark:focus-within:border-primary
        ${
          size === ButtonSize.Small
            ? 'p-0'
            : size === ButtonSize.Medium
            ? 'p-2'
            : 'p-4'
        }
      ${
        disabled
          ? 'cursor-not-allowed bg-white dark:bg-primaryDark'
          : 'cursor-pointer hover:bg-white hover:drop-shadow-md dark:hover:bg-secondaryDark'
      } ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${
        className ?? ''
      }`}
      onClickCapture={onClick}
    >
      {icon && (
        <div className="text-gray-500 dark:text-gray-400">
          {hasConfirmed ? <FiCheck /> : icon}
        </div>
      )}

      <span>{children}</span>
    </m.button>
  );
}
Example #10
Source File: SidebarOverlay.tsx    From meshtastic-web with GNU General Public License v3.0 5 votes vote down vote up
SidebarOverlay = ({
  title,
  open,
  close,
  direction,
  children,
}: SidebarOverlayProps): JSX.Element => {
  return (
    <AnimatePresence>
      {open && (
        <m.div
          className="absolute z-30 flex h-full w-full flex-col bg-white dark:bg-primaryDark"
          animate={direction === 'x' ? { translateX: 0 } : { translateY: 0 }}
          initial={
            direction === 'x' ? { translateX: '-100%' } : { translateY: '100%' }
          }
          exit={
            direction === 'x' ? { translateX: '-100%' } : { translateY: '100%' }
          }
          transition={{ type: 'just' }}
        >
          {/* @ts-expect-error */}
          <AnimateSharedLayout>
            {/* <div className="flex gap-2 border-b border-gray-400 p-2 dark:border-gray-600"> */}
            <div className="bg-white px-1 pt-1 drop-shadow-md dark:bg-primaryDark">
              <div className="flex h-10 gap-1">
                <div className="my-auto">
                  <IconButton
                    onClick={(): void => {
                      close();
                    }}
                    icon={<FiArrowLeft />}
                  />
                </div>
                <div className="my-auto text-lg font-medium dark:text-white">
                  {title}
                </div>
              </div>
            </div>
            <div className="flex-grow overflow-y-auto">{children}</div>
          </AnimateSharedLayout>
        </m.div>
      )}
    </AnimatePresence>
  );
}
Example #11
Source File: ExternalSection.tsx    From meshtastic-web with GNU General Public License v3.0 5 votes vote down vote up
ExternalSection = ({
  title,
  icon,
  active,
  onClick,
}: ExternalSectionProps): JSX.Element => {
  const [open, setOpen] = useState(false);
  const toggleOpen = (): void => setOpen(!open);
  return (
    <m.div
      onClick={(): void => {
        onClick();
      }}
    >
      <m.div
        layout
        className={`w-full cursor-pointer select-none overflow-hidden border-l-4 bg-gray-200 dark:bg-tertiaryDark dark:text-gray-400 ${
          active
            ? 'border-l-primary dark:border-l-primary'
            : 'border-gray-400 dark:border-secondaryDark'
        }`}
      >
        <m.div
          layout
          onClick={toggleOpen}
          whileHover={{ scale: 1.01 }}
          whileTap={{ scale: 0.99 }}
          className="flex justify-between gap-2 border-b border-gray-400 p-2 text-sm font-medium dark:border-primaryDark"
        >
          <m.div className="flex gap-2 ">
            <m.div className="my-auto">{icon}</m.div>
            {title}
          </m.div>
          <m.div className="my-auto">
            <FiChevronRight />
          </m.div>
        </m.div>
      </m.div>
    </m.div>
  );
}
Example #12
Source File: CollapsibleSection.tsx    From meshtastic-web with GNU General Public License v3.0 5 votes vote down vote up
CollapsibleSection = ({
  title,
  icon,
  status,
  children,
}: CollapsibleSectionProps): JSX.Element => {
  const [open, setOpen] = useState(false);
  const toggleOpen = (): void => setOpen(!open);
  return (
    <m.div>
      <m.div
        layout
        onClick={toggleOpen}
        className={`w-full cursor-pointer select-none overflow-hidden border-l-4 border-b bg-gray-200 p-2 text-sm font-medium dark:border-primaryDark dark:bg-tertiaryDark dark:text-gray-400 ${
          open
            ? 'border-l-primary dark:border-l-primary'
            : 'border-gray-400 dark:border-secondaryDark'
        }`}
      >
        <m.div
          layout
          whileHover={{ scale: 1.01 }}
          whileTap={{ scale: 0.99 }}
          className="my-auto flex justify-between gap-2"
        >
          <m.div className="flex gap-2">
            <m.div className="my-auto flex gap-2">
              {status !== undefined ? (
                <>
                  <div
                    className={`my-auto h-2 w-2 rounded-full ${
                      status ? 'bg-green-500' : 'bg-red-500'
                    }`}
                  />
                  {icon}
                </>
              ) : (
                <>{icon}</>
              )}
            </m.div>
            {title}
          </m.div>
          <m.div
            animate={open ? 'open' : 'closed'}
            initial={{ rotate: 180 }}
            variants={{
              open: { rotate: 0 },
              closed: { rotate: 180 },
            }}
            transition={{ type: 'just' }}
            className="my-auto"
          >
            <FiArrowUp />
          </m.div>
        </m.div>
      </m.div>
      <AnimatePresence>
        {open && (
          <m.div
            className="p-2"
            layout
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
          >
            {children}
          </m.div>
        )}
      </AnimatePresence>
    </m.div>
  );
}
Example #13
Source File: Modal.tsx    From meshtastic-web with GNU General Public License v3.0 5 votes vote down vote up
Modal = ({
  open,
  bgDismiss,
  onClose,
  actions,
  ...props
}: ModalProps): JSX.Element => {
  const darkMode = useAppSelector((state) => state.app.darkMode);

  return (
    <AnimatePresence>
      {open && (
        <m.div
          className={`fixed inset-0  ${darkMode ? 'dark' : ''} ${
            open ? 'z-30' : 'z-0'
          }`}
        >
          <m.div
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 0.1 }}
            className="fixed h-full w-full backdrop-blur-md backdrop-brightness-75 backdrop-filter"
            onClick={(): void => {
              bgDismiss && onClose();
            }}
          />
          <m.div className="text-center ">
            <span
              className="inline-block h-screen align-middle "
              aria-hidden="true"
            >
              &#8203;
            </span>
            <div className="inline-block w-full max-w-3xl align-middle">
              <Card
                border
                actions={
                  <div className="flex gap-2">
                    {actions}
                    <IconButton
                      tooltip="Close"
                      icon={<FiX />}
                      onClick={onClose}
                    />
                  </div>
                }
                className="relative flex-col"
                {...props}
              />
            </div>
          </m.div>
        </m.div>
      )}
    </AnimatePresence>
  );
}
Example #14
Source File: Logs.tsx    From meshtastic-web with GNU General Public License v3.0 4 votes vote down vote up
Logs = (): JSX.Element => {
  const dispatch = useAppDispatch();

  const meshtasticState = useAppSelector((state) => state.meshtastic);
  const appState = useAppSelector((state) => state.app);

  type lookupType = { [key: number]: string };

  const emitterLookup: lookupType = {
    [Types.Emitter.sendText]: 'text-rose-500',
    [Types.Emitter.sendPacket]: 'text-pink-500',
    [Types.Emitter.sendRaw]: 'text-fuchsia-500',
    [Types.Emitter.setConfig]: 'text-purple-500',
    [Types.Emitter.confirmSetConfig]: 'text-violet-500',
    [Types.Emitter.setOwner]: 'text-indigo-500',
    [Types.Emitter.setChannel]: 'text-blue-500',
    [Types.Emitter.confirmSetChannel]: 'text-sky-500',
    [Types.Emitter.deleteChannel]: 'text-cyan-500',
    [Types.Emitter.getChannel]: 'text-teal-500',
    [Types.Emitter.getAllChannels]: 'text-emerald-500',
    [Types.Emitter.getConfig]: 'text-green-500',
    [Types.Emitter.getOwner]: 'text-lime-500',
    [Types.Emitter.configure]: 'text-yellow-500',
    [Types.Emitter.handleFromRadio]: 'text-amber-500',
    [Types.Emitter.handleMeshPacket]: 'text-orange-500',
    [Types.Emitter.connect]: 'text-red-500',
    [Types.Emitter.ping]: 'text-stone-500',
    [Types.Emitter.readFromRadio]: 'text-zinc-500',
    [Types.Emitter.writeToRadio]: 'text-gray-500',
    [Types.Emitter.setDebugMode]: 'text-slate-500',
  };

  const levelLookup: lookupType = {
    [Protobuf.LogRecord_Level.UNSET]: 'text-green-500',
    [Protobuf.LogRecord_Level.CRITICAL]: 'text-purple-500',
    [Protobuf.LogRecord_Level.ERROR]: 'text-red-500',
    [Protobuf.LogRecord_Level.WARNING]: 'text-orange-500',
    [Protobuf.LogRecord_Level.INFO]: 'text-blue-500',
    [Protobuf.LogRecord_Level.DEBUG]: 'text-neutral-500',
    [Protobuf.LogRecord_Level.TRACE]: 'text-slate-500',
  };

  return (
    <div className="flex h-full flex-col gap-4 p-4">
      <Card
        title="Device Logs"
        actions={
          <IconButton
            icon={<FiTrash />}
            onClick={(): void => {
              dispatch(clearLogs());
            }}
          />
        }
        className="flex-grow overflow-y-auto"
      >
        <table className="table-cell flex-grow">
          <tbody
            className="
          block h-full flex-col overflow-y-auto font-mono text-xs dark:text-gray-400"
          >
            <AnimatePresence>
              {meshtasticState.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={
                      appState.darkMode
                        ? '/View_Code_Dark.svg'
                        : '/View_Code.svg'
                    }
                  />
                </div>
              )}

              {meshtasticState.logs.map((log, index) => (
                <m.tr
                  key={index}
                  initial={{ opacity: 0 }}
                  animate={{ opacity: 1 }}
                  exit={{ opacity: 0 }}
                  transition={{ duration: 0.3 }}
                  className="group hover:bg-gray-300 dark:hover:bg-secondaryDark"
                >
                  <m.td
                    className="w-6 cursor-pointer"
                    whileHover={{ scale: 1.01 }}
                    whileTap={{ scale: 0.99 }}
                  >
                    <div className="m-auto pl-2 text-white group-hover:text-black dark:text-primaryDark dark:group-hover:text-gray-400">
                      <FiArrowRight />
                    </div>
                  </m.td>
                  <Wrapper>
                    {log.date
                      .toLocaleString(undefined, {
                        year: 'numeric',
                        month: '2-digit',
                        day: '2-digit',

                        hour: '2-digit',
                        minute: '2-digit',
                        second: '2-digit',
                      })
                      .replaceAll('/', '-')
                      .replace(',', '')}
                  </Wrapper>
                  <Wrapper>
                    <div className={emitterLookup[log.emitter]}>
                      [{Types.EmitterScope[log.scope]}.
                      {Types.Emitter[log.emitter]}]
                    </div>
                  </Wrapper>
                  <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="w-full truncate pl-1">{log.message}</td>
                </m.tr>
                // </ContextMenu>
              ))}
            </AnimatePresence>
          </tbody>
        </table>
      </Card>
    </div>
  );
}
Example #15
Source File: Connection.tsx    From meshtastic-web with GNU General Public License v3.0 4 votes vote down vote up
Connection = (): JSX.Element => {
  const dispatch = useAppDispatch();

  const meshtasticState = useAppSelector((state) => state.meshtastic);
  const appState = useAppSelector((state) => state.app);
  const chromiunm = !!window.chrome;

  useEffect(() => {
    if (!import.meta.env.VITE_PUBLIC_HOSTED) {
      dispatch(
        setConnectionParams({
          type: connType.HTTP,
          params: {
            address: connectionUrl,
            tls: false,
            receiveBatchRequests: false,
            fetchInterval: 2000,
          },
        }),
      );
      void setConnection(connType.HTTP);
    }
  }, [dispatch]);

  useEffect(() => {
    if (meshtasticState.ready) {
      dispatch(closeConnectionModal());
    }
  }, [meshtasticState.ready, dispatch]);

  return (
    <Modal
      title="Connect to a device"
      open={appState.connectionModalOpen}
      onClose={(): void => {
        dispatch(closeConnectionModal());
      }}
    >
      <div className="flex max-w-3xl flex-col gap-4 md:flex-row">
        <div className="flex flex-col md:w-1/2">
          <div className="flex flex-grow flex-col space-y-2">
            <Select
              label="Connection Method"
              optionsEnum={connType}
              value={appState.connType}
              onChange={(e): void => {
                dispatch(setConnType(parseInt(e.target.value)));
              }}
              disabled={
                meshtasticState.deviceStatus ===
                Types.DeviceStatusEnum.DEVICE_CONNECTED
              }
            />
            {appState.connType === connType.HTTP && (
              <HTTP
                connecting={
                  meshtasticState.deviceStatus ===
                  Types.DeviceStatusEnum.DEVICE_CONNECTED
                }
              />
            )}

            {appState.connType === connType.BLE &&
              (chromiunm ? (
                <BLE
                  connecting={
                    meshtasticState.deviceStatus ===
                    Types.DeviceStatusEnum.DEVICE_CONNECTED
                  }
                />
              ) : (
                <div className="rounded-md border border-red-500 bg-red-500 bg-opacity-10 p-8 dark:text-white">
                  <p>Unsupported.</p>
                  <p>Please use a Chromium based browser.</p>
                </div>
              ))}

            {appState.connType === connType.SERIAL &&
              (chromiunm ? (
                <Serial
                  connecting={
                    meshtasticState.deviceStatus ===
                    Types.DeviceStatusEnum.DEVICE_CONNECTED
                  }
                />
              ) : (
                <div className="rounded-md border border-red-500 bg-red-500 bg-opacity-10 p-8 dark:text-white">
                  <p>Unsupported.</p>
                  <p>Please use a Chromium based browser.</p>
                </div>
              ))}
          </div>
        </div>
        <div className="md:w-1/2">
          <div className="h-96 overflow-y-auto rounded-md border border-gray-400 bg-gray-200 p-2 drop-shadow-md dark:border-gray-600 dark:bg-tertiaryDark dark:text-gray-400">
            {meshtasticState.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-40 w-40 text-green-500"
                  src={
                    appState.darkMode ? '/View_Code_Dark.svg' : '/View_Code.svg'
                  }
                />
              </div>
            )}
            {meshtasticState.logs
              .filter((log) => {
                return ![
                  Types.Emitter.handleFromRadio,
                  Types.Emitter.handleMeshPacket,
                  Types.Emitter.sendPacket,
                ].includes(log.emitter);
              })
              .map((log, index) => (
                <div key={index} className="flex">
                  <div className="truncate font-mono text-sm">
                    {log.message}
                  </div>
                </div>
              ))}
          </div>
        </div>
      </div>
    </Modal>
  );
}
Example #16
Source File: ChannelChat.tsx    From meshtastic-web with GNU General Public License v3.0 4 votes vote down vote up
ChannelChat = ({
  channel,
  selectedIndex,
  setSelectedIndex,
}: ChannelChatProps): JSX.Element => {
  const myNodeNum = useAppSelector(
    (state) => state.meshtastic.radio.hardware,
  ).myNodeNum;
  const nodes = useAppSelector((state) => state.meshtastic.nodes).filter(
    (node) => node.data.num !== myNodeNum,
  );
  const chats = useAppSelector((state) => state.meshtastic.chats);
  const channels = useAppSelector(
    (state) => state.meshtastic.radio.channels,
  ).filter((ch) => ch.role !== Protobuf.Channel_Role.DISABLED);

  return (
    <SidebarItem
      key={channel.index}
      selected={channel.index === selectedIndex}
      setSelected={(): void => {
        setSelectedIndex(channel.index);
      }}
      actions={<IconButton nested icon={<FiSettings />} />}
    >
      <Tooltip
        content={
          channel.settings?.name.length
            ? channel.settings.name
            : `CH: ${channel.index}`
        }
      >
        <div className="flex h-8 w-8 rounded-full bg-gray-300 dark:bg-primaryDark dark:text-white">
          <div className="m-auto">
            {channel.role === Protobuf.Channel_Role.PRIMARY ? (
              <MdPublic />
            ) : (
              <p>
                {channel.settings?.name.length
                  ? channel.settings.name.substring(0, 3).toUpperCase()
                  : `CH: ${channel.index}`}
              </p>
            )}
          </div>
        </div>
      </Tooltip>
      {chats[channel.index]?.messages.length ? (
        <>
          <div className="mx-2 flex h-8">
            {[
              ...new Set(
                chats[channel.index]?.messages.flatMap(({ message }) => [
                  message.packet.from,
                ]),
              ),
            ]
              .sort()
              .map((nodeId) => {
                return (
                  <Tooltip
                    key={nodeId}
                    content={
                      nodes.find((node) => node.data.num === nodeId)?.data.user
                        ?.longName ?? 'UNK'
                    }
                  >
                    <div className="flex h-full">
                      <m.div
                        whileHover={{ scale: 1.1 }}
                        className="my-auto -ml-2"
                      >
                        <Hashicon value={nodeId.toString()} size={20} />
                      </m.div>
                    </div>
                  </Tooltip>
                );
              })}
          </div>
          <div className="my-auto ml-auto text-xs font-semibold dark:text-gray-400">
            {chats[channel.index].messages.length ? (
              <TimeAgo datetime={chats[channel.index].lastInterraction} />
            ) : (
              <div>No messages</div>
            )}
          </div>
        </>
      ) : (
        <div className="my-auto dark:text-white">No messages</div>
      )}
    </SidebarItem>
  );
}
Example #17
Source File: NodeCard.tsx    From meshtastic-web with GNU General Public License v3.0 4 votes vote down vote up
NodeCard = ({
  node,
  isMyNode,
  selected,
  setSelected,
}: NodeCardProps): JSX.Element => {
  const { map } = useMapbox();
  const [infoOpen, setInfoOpen] = useState(false);
  const [PositionConfidence, setPositionConfidence] =
    useState<PositionConfidence>('none');

  useEffect(() => {
    setPositionConfidence(
      node.position
        ? new Date(node.position.posTimestamp * 1000) >
          new Date(new Date().getTime() - 1000 * 60 * 30)
          ? 'high'
          : 'low'
        : 'none',
    );
  }, [node.position]);
  return (
    <>
      <SidebarItem
        selected={selected}
        setSelected={setSelected}
        actions={
          <>
            <IconButton
              nested
              tooltip={PositionConfidence !== 'none' ? 'Fly to Node' : ''}
              disabled={PositionConfidence === 'none'}
              onClick={(e): void => {
                e.stopPropagation();
                setSelected();
                if (PositionConfidence !== 'none' && node.position) {
                  map?.flyTo({
                    center: new LngLat(
                      node.position.longitudeI / 1e7,
                      node.position.latitudeI / 1e7,
                    ),
                    zoom: 16,
                  });
                }
              }}
              icon={
                PositionConfidence === 'high' ? (
                  <MdGpsFixed />
                ) : PositionConfidence === 'low' ? (
                  <MdGpsNotFixed />
                ) : (
                  <MdGpsOff />
                )
              }
            />
            <IconButton
              nested
              tooltip="Show Node Info"
              onClick={(e): void => {
                e.stopPropagation();
                setInfoOpen(true);
              }}
              icon={<FiAlignLeft />}
            />
          </>
        }
      >
        <div className="flex dark:text-white">
          <div className="relative m-auto">
            {isMyNode && (
              <Tooltip content="Your Node">
                <m.div
                  whileHover={{ scale: 1.05 }}
                  className="absolute -right-1 -top-1 rounded-full bg-yellow-500 p-0.5"
                >
                  <BiCrown className="h-3 w-3" />
                </m.div>
              </Tooltip>
            )}
            <Hashicon value={node.num.toString()} size={32} />
          </div>
        </div>
        <div className="my-auto mr-auto text-xs font-semibold dark:text-gray-400">
          {node.lastHeard
            ? new Date(node.lastHeard).toLocaleTimeString(undefined, {
                hour: '2-digit',
                minute: '2-digit',
              })
            : 'Never'}
        </div>
      </SidebarItem>
      <SidebarOverlay
        title={`Node ${node.user?.longName ?? 'UNK'} `}
        open={infoOpen}
        close={(): void => {
          setInfoOpen(false);
        }}
        direction="x"
      >
        <CollapsibleSection title="User" icon={<FiUser />}>
          <div className="flex  p-2">
            <div className="m-auto flex flex-col gap-2">
              <Hashicon value={node.num.toString()} size={180} />
              <div className="text-center text-lg font-medium dark:text-white">
                {node?.user?.longName || 'Unknown'}
              </div>
            </div>
          </div>
        </CollapsibleSection>
        <CollapsibleSection title="Location" icon={<FiMapPin />}>
          <>
            <div className="flex h-10 select-none justify-between rounded-md border border-gray-400 bg-transparent bg-gray-300 px-1 text-gray-500 dark:border-gray-600 dark:bg-secondaryDark dark:text-gray-400 ">
              {node.position ? (
                <>
                  <div className="my-auto px-1">
                    {(node.position.latitudeI / 1e7).toPrecision(6)}
                    ,&nbsp;
                    {(node.position?.longitudeI / 1e7).toPrecision(6)}
                  </div>
                  <CopyButton
                    data={
                      node.position
                        ? `${node.position.latitudeI / 1e7},${
                            node.position.longitudeI / 1e7
                          }`
                        : ''
                    }
                  />
                </>
              ) : (
                <div className="my-auto px-1">No location data received</div>
              )}
            </div>
          </>
        </CollapsibleSection>
        <CollapsibleSection title="Line of Sight" icon={<IoTelescope />}>
          <div>Info</div>
        </CollapsibleSection>
        <CollapsibleSection title="Administration" icon={<FiSliders />}>
          <div>Info</div>
        </CollapsibleSection>
        <CollapsibleSection title="Debug" icon={<FiCode />}>
          <>
            <div className="fixed right-0 mr-6">
              <CopyButton data={JSON.stringify(node)} />
            </div>
            <JSONPretty className="max-w-sm" data={node} />
          </>
        </CollapsibleSection>
      </SidebarOverlay>
    </>
  );
}