Merge branch 'master' into i18n

This commit is contained in:
Thomas Göttgens 2023-05-12 15:41:21 +02:00 committed by GitHub
commit 5d062a97a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 4821 additions and 4689 deletions

View file

@ -1,9 +0,0 @@
{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:@docusaurus/recommended"],
"parser": "@typescript-eslint/parser",
"plugins": ["@docusaurus", "@typescript-eslint"],
"root": true
}

View file

@ -1,3 +0,0 @@
{
"trailingComma": "none"
}

17
.trunk/configs/rome.json Normal file
View file

@ -0,0 +1,17 @@
{
"$schema": "../../node_modules/rome/configuration_schema.json",
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentSize": 2
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"organizeImports": {
"enabled": true
}
}

View file

@ -1,14 +1,14 @@
module.exports = {
plugins: [
{
name: "preset-default",
params: {
overrides: {
removeViewBox: false, // https://github.com/svg/svgo/issues/1128
sortAttrs: true,
removeOffCanvasPaths: true
}
}
}
]
plugins: [
{
name: "preset-default",
params: {
overrides: {
removeViewBox: false, // https://github.com/svg/svgo/issues/1128
sortAttrs: true,
removeOffCanvasPaths: true,
},
},
},
],
};

View file

@ -7,15 +7,17 @@ plugins:
ref: v0.0.8
uri: https://github.com/trunk-io/plugins
lint:
disabled:
- eslint
- prettier
enabled:
- rome@12.0.0
- markdownlint@0.33.0
- actionlint@1.6.22
- gitleaks@8.15.2
- git-diff-check
- shellcheck@0.9.0
- prettier@2.8.2
- shfmt@3.5.0
- eslint@8.31.0
- svgo@3.0.2
runtimes:
enabled:

View file

@ -6,6 +6,8 @@ sidebar_label: Radio Settings
sidebar_position: 1
---
import {FrequencyCalculator} from "/src/components/tools/FrequencyCalculator";
:::info
Meshtastic is **not** LoRaWAN, Helium or TTN (TheThingsNetwork). Meshtastic uses the full spectrum frequency range designated to LoRa technology per region. This allows for several hundred possible frequency channels in the US region alone.
:::
@ -14,6 +16,10 @@ Meshtastic is **not** LoRaWAN, Helium or TTN (TheThingsNetwork). Meshtastic uses
Power limits will generally be lifted in the software if `is_licensed` is set to `true`. See [HAM Mode](/docs/faq#amateur-radio-ham) for more information.
:::
## Channel Frequency Calculator
<FrequencyCalculator />
## Europe Frequency Bands
### 433 MHz

View file

@ -7,7 +7,7 @@ sidebar_label: Power
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import calculateADC from "@site/src/utils/calculateADC";
import calculateADC from "/src/utils/calculateADC";
The power config options are: Power Saving, Shutdown after losing power, ADC Multiplier Override Wait Bluetooth Interval, Mesh Super Deep Sleep Timeout, Super Deep Sleep Interval, Light Sleep Interval and Minimum Wake Interval. Power config uses an admin message sending a `Config.Power` protobuf.

View file

@ -4,152 +4,152 @@ require("dotenv").config();
/** @type {import('@docusaurus/types').Config} */
const config = {
title: "Meshtastic",
tagline:
"An open source, off-grid, decentralized, mesh network built to run on affordable, low-power devices",
url: "https://meshtastic.org",
baseUrl: "/",
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn",
favicon: "design/web/favicon.ico",
organizationName: "meshtastic",
projectName: "meshtastic",
themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ {
announcementBar: {
id: "2_0",
content:
'🎉 Meshtastic 2.0 Has Now Launched! Check it Out <a href="/2.0">Here</a> 🎉'
},
docs: {
sidebar: {
autoCollapseCategories: true
}
},
navbar: {
title: "Meshtastic",
hideOnScroll: true,
logo: {
alt: "Meshtastic Logo",
src: "design/logo/svg/Mesh_Logo_Black.svg",
srcDark: "design/logo/svg/Mesh_Logo_White.svg"
},
items: [
{
label: "Docs",
to: "docs/introduction"
},
{
label: "Downloads",
to: "downloads"
},
title: "Meshtastic",
tagline:
"An open source, off-grid, decentralized, mesh network built to run on affordable, low-power devices",
url: "https://meshtastic.org",
baseUrl: "/",
onBrokenLinks: "throw",
onBrokenMarkdownLinks: "warn",
favicon: "design/web/favicon.ico",
organizationName: "meshtastic",
projectName: "meshtastic",
themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ {
announcementBar: {
id: "2_0",
content:
'🎉 Meshtastic 2.0 Has Now Launched! Check it Out <a href="/2.0">Here</a> 🎉',
},
docs: {
sidebar: {
autoCollapseCategories: true,
},
},
navbar: {
title: "Meshtastic",
hideOnScroll: true,
logo: {
alt: "Meshtastic Logo",
src: "design/logo/svg/Mesh_Logo_Black.svg",
srcDark: "design/logo/svg/Mesh_Logo_White.svg",
},
items: [
{
label: "Docs",
to: "docs/introduction",
},
{
label: "Downloads",
to: "downloads",
},
{
type: "localeDropdown",
position: "right",
className: "header-language-link"
},
{
label: "About",
position: "right",
items: [
{
label: "Introduction",
to: "docs/introduction"
},
{
label: "Getting Started",
to: "docs/getting-started"
},
{
label: "Contributing",
to: "docs/contributing"
},
{
label: "Legal",
to: "docs/legal"
},
{
label: "FAQs",
to: "docs/faq"
}
]
},
{
href: "https://github.com/meshtastic",
position: "right",
className: "header-github-link",
"aria-label": "GitHub repository"
}
]
},
footer: {
copyright: `<a href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss">Powered by ▲ Vercel</a> | Meshtastic® is a registered trademark of Meshtastic LLC. | <a href="/docs/legal">Legal Information</a>.`
},
algolia: {
appId: "IG2GQB8L3V",
// trunk-ignore(gitleaks/generic-api-key)
apiKey: "2e4348812173ec7ea6f7879c7032bb21",
indexName: "meshtastic",
contextualSearch: false,
searchPagePath: "search"
},
colorMode: {
respectPrefersColorScheme: true
},
mermaid: {
theme: { light: "base", dark: "base" },
options: {
themeVariables: {
primaryColor: "#67EA94",
primaryTextColor: "var(--tw-prose-headings)",
primaryBorderColor: "#4D4D4D",
lineColor: "#EAD667",
secondaryColor: "#EA67BD",
tertiaryColor: "#677CEA"
}
}
}
},
plugins: [
() => {
return {
name: "docusaurus-tailwindcss",
configurePostCss(postcssOptions) {
postcssOptions.plugins.push(require("tailwindcss"));
postcssOptions.plugins.push(require("autoprefixer"));
return postcssOptions;
}
};
}
],
presets: [
[
"@docusaurus/preset-classic",
/** @type {import('@docusaurus/preset-classic').Options} */
{
docs: {
sidebarPath: require.resolve("./sidebars.js"),
editUrl: "https://github.com/meshtastic/meshtastic/edit/master/",
breadcrumbs: false,
showLastUpdateAuthor: true,
editLocalizedFiles: true
},
theme: {
customCss: require.resolve("./src/css/custom.css")
}
}
]
],
customFields: {
API_URL: process.env.API_URL
},
{
label: "About",
position: "right",
items: [
{
label: "Introduction",
to: "docs/introduction",
},
{
label: "Getting Started",
to: "docs/getting-started",
},
{
label: "Contributing",
to: "docs/contributing",
},
{
label: "Legal",
to: "docs/legal",
},
{
label: "FAQs",
to: "docs/faq",
},
],
},
{
href: "https://github.com/meshtastic",
position: "right",
className: "header-github-link",
"aria-label": "GitHub repository",
},
],
},
footer: {
copyright: `<a href="https://vercel.com/?utm_source=meshtastic&utm_campaign=oss">Powered by ▲ Vercel</a> | Meshtastic® is a registered trademark of Meshtastic LLC. | <a href="/docs/legal">Legal Information</a>.`,
},
algolia: {
appId: "IG2GQB8L3V",
// trunk-ignore(gitleaks/generic-api-key)
apiKey: "2e4348812173ec7ea6f7879c7032bb21",
indexName: "meshtastic",
contextualSearch: false,
searchPagePath: "search",
},
colorMode: {
respectPrefersColorScheme: true,
},
mermaid: {
theme: { light: "base", dark: "base" },
options: {
themeVariables: {
primaryColor: "#67EA94",
primaryTextColor: "var(--tw-prose-headings)",
primaryBorderColor: "#4D4D4D",
lineColor: "#EAD667",
secondaryColor: "#EA67BD",
tertiaryColor: "#677CEA",
},
},
},
},
plugins: [
() => {
return {
name: "docusaurus-tailwindcss",
configurePostCss(postcssOptions) {
postcssOptions.plugins.push(require("tailwindcss"));
postcssOptions.plugins.push(require("autoprefixer"));
return postcssOptions;
},
};
},
],
presets: [
[
"@docusaurus/preset-classic",
/** @type {import('@docusaurus/preset-classic').Options} */
{
docs: {
sidebarPath: require.resolve("./sidebars.js"),
editUrl: "https://github.com/meshtastic/meshtastic/edit/master/",
breadcrumbs: false,
showLastUpdateAuthor: true,
editLocalizedFiles: true,
},
theme: {
customCss: require.resolve("./src/css/custom.css"),
},
},
],
],
customFields: {
API_URL: process.env.API_URL,
},
i18n: {
defaultLocale: "en",
locales: ["en", "de", "pt-br", "is"]
locales: ["en", "de", "pt-br", "is"],
},
markdown: {
mermaid: true
},
themes: ["@docusaurus/theme-mermaid"]
markdown: {
mermaid: true,
},
themes: ["@docusaurus/theme-mermaid"],
};
module.exports = config;

View file

@ -14,41 +14,38 @@
"crowdin:sync": "docusaurus write-translations && crowdin upload && crowdin download"
},
"dependencies": {
"@algolia/client-search": "^4.16.0",
"@algolia/client-search": "^4.17.0",
"@crowdin/cli": "^3.2.2",
"@docusaurus/core": "2.4.0",
"@docusaurus/plugin-content-docs": "2.4.0",
"@docusaurus/preset-classic": "2.4.0",
"@docusaurus/theme-mermaid": "^2.4.0",
"@headlessui/react": "^1.7.13",
"@heroicons/react": "^2.0.17",
"@headlessui/react": "^1.7.14",
"@heroicons/react": "^2.0.18",
"@mdx-js/react": "^1.6.22",
"@meshtastic/meshtasticjs": "2.1.6-0",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
"@meshtastic/meshtasticjs": "2.1.9-0",
"autoprefixer": "^10.4.14",
"base64-js": "^1.5.1",
"dotenv": "^16.0.3",
"eslint": "^8.37.0",
"framer-motion": "^6.5.1",
"postcss": "^8.4.21",
"postcss": "^8.4.23",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-icons": "^4.8.0",
"react-responsive-carousel": "^3.2.23",
"swr": "^2.1.1",
"tailwindcss": "^3.3.1",
"swr": "^2.1.5",
"tailwindcss": "^3.3.2",
"url-search-params-polyfill": "^8.1.1",
"use-breakpoint": "^3.0.7"
},
"devDependencies": {
"@docusaurus/eslint-plugin": "^2.4.0",
"@docusaurus/module-type-aliases": "2.4.0",
"@tailwindcss/typography": "^0.5.9",
"@tsconfig/docusaurus": "^1.0.7",
"@types/node": "^18.15.11",
"@types/react": "^18.0.31",
"@types/react-dom": "^18.0.11",
"typescript": "^5.0.3"
"@types/node": "^20.1.3",
"@types/react": "^18.2.6",
"@types/react-dom": "^18.2.4",
"rome": "^12.0.0",
"typescript": "^5.0.4"
}
}

File diff suppressed because it is too large Load diff

View file

@ -2,10 +2,10 @@
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
module.exports = {
Sidebar: [
{
type: "autogenerated",
dirName: "."
}
]
Sidebar: [
{
type: "autogenerated",
dirName: ".",
},
],
};

View file

@ -1,25 +1,27 @@
import React from "react";
export const BatteryCalculator = (): JSX.Element => {
return (
<div className="card">
<div className="card__header">
<h3>Battery Calculator</h3>
</div>
<div className="card__body" style={{ display: "flex", gap: "2rem" }}>
<div>
<input placeholder="Search" />
<input placeholder="Search" />
<input placeholder="Search" />
<input placeholder="Search" />
</div>
<div></div>
</div>
<div className="card__footer">
<button className="button button--secondary button--block">
See All
</button>
</div>
</div>
);
return (
<div className="card">
<div className="card__header">
<h3>Battery Calculator</h3>
</div>
<div className="card__body" style={{ display: "flex", gap: "2rem" }}>
<div>
<input placeholder="Search" />
<input placeholder="Search" />
<input placeholder="Search" />
<input placeholder="Search" />
</div>
</div>
<div className="card__footer">
<button
type="button"
className="button button--secondary button--block"
>
See All
</button>
</div>
</div>
);
};

View file

@ -3,14 +3,14 @@ import React from "react";
import { HTMLMotionProps, motion } from "framer-motion";
export const Button = ({ children, ...props }: HTMLMotionProps<"div">) => {
return (
<motion.div
{...props}
whileHover={{ scale: 1.1, backgroundColor: "var(--tertiary)" }}
whileTap={{ scale: 1.0 }}
className="m-auto flex cursor-pointer rounded-full bg-secondary p-3 shadow-md"
>
<div className="m-auto">{children}</div>
</motion.div>
);
return (
<motion.div
{...props}
whileHover={{ scale: 1.1, backgroundColor: "var(--tertiary)" }}
whileTap={{ scale: 1.0 }}
className="m-auto flex cursor-pointer rounded-full bg-secondary p-3 shadow-md"
>
<div className="m-auto">{children}</div>
</motion.div>
);
};

View file

@ -1,13 +1,13 @@
import React from "react";
export interface ColorModeProps {
children: React.ReactNode;
children: React.ReactNode;
}
export const Dark = ({ children }: ColorModeProps): JSX.Element => {
return <div className="hideLight">{children}</div>;
return <div className="hideLight">{children}</div>;
};
export const Light = ({ children }: ColorModeProps): JSX.Element => {
return <div className="hideDark">{children}</div>;
return <div className="hideDark">{children}</div>;
};

View file

@ -5,45 +5,45 @@ import { AnimatePresence, motion } from "framer-motion";
import { Dialog } from "@headlessui/react";
export interface ModalProps {
open: boolean;
onClose: () => void;
children: React.ReactNode;
open: boolean;
onClose: () => void;
children: React.ReactNode;
}
export const Modal = ({ open, onClose, children }: ModalProps): JSX.Element => {
return (
<AnimatePresence initial={false} exitBeforeEnter={true}>
<Dialog
as="div"
className="fixed inset-0 z-10 overflow-y-auto"
open={open}
onClose={onClose}
>
<div className="min-h-screen px-0.5 text-center md:px-4">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<Dialog.Overlay className="fixed inset-0 backdrop-blur-md" />
</motion.div>
return (
<AnimatePresence initial={false} exitBeforeEnter={true}>
<Dialog
as="div"
className="fixed inset-0 z-10 overflow-y-auto"
open={open}
onClose={onClose}
>
<div className="min-h-screen px-0.5 text-center md:px-4">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<Dialog.Overlay className="fixed inset-0 backdrop-blur-md" />
</motion.div>
<span
className="inline-block h-screen align-middle"
aria-hidden="true"
>
&#8203;
</span>
<div className="inline-block w-full transform text-left align-middle transition-all 2xl:max-w-7xl">
<div className="group relative">
<div className="animate-tilt absolute -inset-0.5 rotate-2 rounded-lg bg-accent shadow-md transition duration-1000 group-hover:opacity-100 group-hover:duration-200"></div>
<div className="relative flex flex-col overflow-hidden rounded-2xl bg-base shadow-md md:aspect-[2/1] md:flex-row md:bg-primary">
{children}
</div>
</div>
</div>
</div>
</Dialog>
</AnimatePresence>
);
<span
className="inline-block h-screen align-middle"
aria-hidden="true"
>
&#8203;
</span>
<div className="inline-block w-full transform text-left align-middle transition-all 2xl:max-w-7xl">
<div className="group relative">
<div className="animate-tilt absolute -inset-0.5 rotate-2 rounded-lg bg-accent shadow-md transition duration-1000 group-hover:opacity-100 group-hover:duration-200" />
<div className="relative flex flex-col overflow-hidden rounded-2xl bg-base shadow-md md:aspect-[2/1] md:flex-row md:bg-primary">
{children}
</div>
</div>
</div>
</div>
</Dialog>
</AnimatePresence>
);
};

View file

@ -3,23 +3,23 @@ import React from "react";
import Layout from "@theme/Layout";
export interface PageLayoutProps {
title: string;
description: string;
children: React.ReactNode;
title: string;
description: string;
children: React.ReactNode;
}
export const PageLayout = ({
title,
description,
children
title,
description,
children,
}: PageLayoutProps): JSX.Element => {
return (
<Layout title={title} description={description}>
{children}
</Layout>
);
return (
<Layout title={title} description={description}>
{children}
</Layout>
);
};
export interface ColorModeProps {
children: React.ReactNode;
children: React.ReactNode;
}

View file

@ -1,18 +1,18 @@
import React from "react";
export interface BadgeProps {
name: string;
color: string;
icon: React.ReactNode;
name: string;
color: string;
icon: React.ReactNode;
}
export const Badge = ({ name, color, icon }: BadgeProps): JSX.Element => {
return (
<div
className={`flex h-min cursor-pointer gap-1 rounded-md px-1 text-white shadow-md hover:opacity-80 ${color}`}
>
<div className="my-1">{icon}</div>
<span className="hidden truncate md:flex">{name}</span>
</div>
);
return (
<div
className={`flex h-min cursor-pointer gap-1 rounded-md px-1 text-white shadow-md hover:opacity-80 ${color}`}
>
<div className="my-1">{icon}</div>
<span className="hidden truncate md:flex">{name}</span>
</div>
);
};

View file

@ -3,19 +3,19 @@ import React from "react";
import { Tab } from "@headlessui/react";
export interface CardTabProps {
title: string;
title: string;
}
export const CardTab = ({ title }: CardTabProps): JSX.Element => {
return (
<Tab
className={({ selected }) =>
`w-1/3 truncate rounded-md px-3 py-2 text-sm font-medium hover:bg-tertiary ${
selected ? "bg-secondary shadow-md" : ""
}`
}
>
{title}
</Tab>
);
return (
<Tab
className={({ selected }) =>
`w-1/3 truncate rounded-md px-3 py-2 text-sm font-medium hover:bg-tertiary ${
selected ? "bg-secondary shadow-md" : ""
}`
}
>
{title}
</Tab>
);
};

View file

@ -1,58 +1,61 @@
import React from "react";
import { IDevice, Stability } from "@site/src/data/device";
import { IDevice, Stability } from "../../data/device";
export interface HardwareCardProps {
device: IDevice;
setDevice: () => void;
device: IDevice;
setDevice: () => void;
}
export const HardwareCard = ({
device,
setDevice
device,
setDevice,
}: HardwareCardProps): JSX.Element => {
return (
<li
className="group relative"
onClick={() => {
setDevice();
}}
>
<div className="overflow-hidden rounded-lg">
<div
className={`flex aspect-[4/3] overflow-hidden ${device.misc.Gradient}`}
>
<img
src={device.images.Front}
alt=""
className="pointer-events-none m-auto max-h-full max-w-full object-cover p-2 group-hover:opacity-75"
/>
</div>
<button type="button" className="absolute inset-0 focus:outline-none">
<span className="sr-only">View details for {device.name}</span>
</button>
</div>
<div className="flex">
<div>
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-primaryInv">
{device.name}
</p>
<p className="pointer-events-none flex gap-1 text-sm font-medium text-mute">
<div
className={`my-auto h-3 w-3 rounded-full ${
device.misc.Stability === Stability.Broken
? "bg-red-500"
: device.misc.Stability === Stability.Unstable
? "bg-orange-500"
: device.misc.Stability === Stability.Semi
? "bg-cyan-500"
: "bg-green-500"
}`}
/>
<div className="my-auto">{Stability[device.misc.Stability]}</div>
</p>
</div>
</div>
</li>
);
return (
<li
className="group relative"
onClick={() => {
setDevice();
}}
onKeyDown={() => {
setDevice();
}}
>
<div className="overflow-hidden rounded-lg">
<div
className={`flex aspect-[4/3] overflow-hidden ${device.misc.Gradient}`}
>
<img
src={device.images.Front}
alt=""
className="pointer-events-none m-auto max-h-full max-w-full object-cover p-2 group-hover:opacity-75"
/>
</div>
<button type="button" className="absolute inset-0 focus:outline-none">
<span className="sr-only">View details for {device.name}</span>
</button>
</div>
<div className="flex">
<div>
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-primaryInv">
{device.name}
</p>
<p className="pointer-events-none flex gap-1 text-sm font-medium text-mute">
<div
className={`my-auto h-3 w-3 rounded-full ${
device.misc.Stability === Stability.Broken
? "bg-red-500"
: device.misc.Stability === Stability.Unstable
? "bg-orange-500"
: device.misc.Stability === Stability.Semi
? "bg-cyan-500"
: "bg-green-500"
}`}
/>
<div className="my-auto">{Stability[device.misc.Stability]}</div>
</p>
</div>
</div>
</li>
);
};

View file

@ -5,7 +5,7 @@ import { FiBluetooth, FiChevronRight, FiWifi, FiX } from "react-icons/fi";
import { useBreakpoint } from "use-breakpoint";
import { Tab } from "@headlessui/react";
import type { IDevice } from "@site/src/data/device";
import type { IDevice } from "../../data/device";
import { Button } from "../../components/Button";
import { BREAKPOINTS } from "../../utils/breakpoints";
@ -18,156 +18,156 @@ import { PowerTab } from "./Tabs/PowerTab";
import { VariantSelectButton } from "./VariantSelectButton";
export interface HardwareModal {
device: IDevice;
open: boolean;
close: () => void;
device: IDevice;
open: boolean;
close: () => void;
}
export const HardwareModal = ({
device,
open,
close
device,
open,
close,
}: HardwareModal): JSX.Element => {
const [hideDetails, setHideDetails] = useState(false);
const { breakpoint } = useBreakpoint(BREAKPOINTS, "md");
const [hideDetails, setHideDetails] = useState(false);
const { breakpoint } = useBreakpoint(BREAKPOINTS, "md");
return (
<Modal open={open} onClose={close}>
<div className="absolute right-0 z-20 m-2 md:flex">
<Button onClick={close}>
<FiX />
</Button>
</div>
<div className="absolute inset-0">
<motion.div
layout
animate={
breakpoint === "sm"
? hideDetails
? "hiddenSm"
: "visibleSm"
: hideDetails
? "hidden"
: "visible"
}
variants={{
hidden: { width: "100%", height: "100%" },
hiddenSm: { height: "100%", width: "100%" },
visible: { width: "20%", height: "100%" },
visibleSm: { height: "33%", width: "100%" }
}}
transition={{
type: "just"
}}
className="flex flex-col md:h-full md:flex-row"
>
<motion.div
layout
className={`relative z-10 flex h-full w-full rounded-t-2xl md:rounded-l-2xl md:rounded-tr-none ${device.misc.Gradient}`}
>
<motion.img
layout
src={device.images.Front}
alt=""
className="pointer-events-none m-auto max-h-full max-w-full object-cover p-2"
/>
<div className="absolute -bottom-5 z-20 flex w-full md:bottom-auto md:-right-5 md:h-full md:w-auto">
<Button
animate={
breakpoint === "sm"
? hideDetails
? "hiddenSm"
: "visibleSm"
: hideDetails
? "hidden"
: "visible"
}
variants={{
hidden: { rotate: 180 },
hiddenSm: { rotate: -90 },
visible: { rotate: 0 },
visibleSm: { rotate: 90 }
}}
onClick={() => {
setHideDetails(!hideDetails);
}}
>
<FiChevronRight />
</Button>
</div>
<AnimatePresence>
{!hideDetails && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={`absolute -bottom-5 z-20 flex md:mt-0 md:hidden md:pb-2 ${
hideDetails ? "opacity-0" : "opacity-100"
}`}
>
<VariantSelectButton options={device.variants} />
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute -bottom-3 right-0 m-auto mr-2 ml-auto flex md:inset-x-1 md:bottom-4 md:mt-2"
>
<div className="m-auto flex gap-2">
{device.features.BLE && (
<Badge
name="Bluetooth"
color="bg-blue-500"
icon={<FiBluetooth />}
/>
)}
{device.features.WiFi && (
<Badge
name="WiFi"
color="bg-orange-500"
icon={<FiWifi />}
/>
)}
</div>
</motion.div>
</>
)}
</AnimatePresence>
</motion.div>
<div
className={`h-7 bg-base opacity-0 md:h-auto md:w-7 ${
hideDetails ? "flex" : "hidden"
}`}
/>
</motion.div>
</div>
<div className="z-[1] mt-[25%] flex h-full flex-col md:ml-[20%] md:mt-0 md:w-4/5">
<div className="z-0 hidden pb-2 md:flex">
<VariantSelectButton options={device.variants} />
</div>
<div
className={`mt-1 flex flex-grow rounded-2xl bg-base p-2 shadow-inner transition-opacity duration-100 ease-linear md:mt-0 md:rounded-l-none md:rounded-r-2xl md:p-4 ${
hideDetails ? "opacity-0" : "opacity-100"
}`}
>
<Tab.Group
as="div"
className="flex flex-grow flex-col rounded-2xl bg-primary p-2"
>
<Tab.List className="flex gap-2">
<CardTab title="Info" />
<CardTab title="Power" />
<CardTab title="Pinout" />
</Tab.List>
<Tab.Panels as="div" className="flex-grow overflow-y-auto">
<InfoTab device={device} />
<PowerTab device={device} />
<PinoutTab device={device} />
</Tab.Panels>
</Tab.Group>
</div>
</div>
</Modal>
);
return (
<Modal open={open} onClose={close}>
<div className="absolute right-0 z-20 m-2 md:flex">
<Button onClick={close}>
<FiX />
</Button>
</div>
<div className="absolute inset-0">
<motion.div
layout
animate={
breakpoint === "sm"
? hideDetails
? "hiddenSm"
: "visibleSm"
: hideDetails
? "hidden"
: "visible"
}
variants={{
hidden: { width: "100%", height: "100%" },
hiddenSm: { height: "100%", width: "100%" },
visible: { width: "20%", height: "100%" },
visibleSm: { height: "33%", width: "100%" },
}}
transition={{
type: "just",
}}
className="flex flex-col md:h-full md:flex-row"
>
<motion.div
layout
className={`relative z-10 flex h-full w-full rounded-t-2xl md:rounded-l-2xl md:rounded-tr-none ${device.misc.Gradient}`}
>
<motion.img
layout
src={device.images.Front}
alt=""
className="pointer-events-none m-auto max-h-full max-w-full object-cover p-2"
/>
<div className="absolute -bottom-5 z-20 flex w-full md:bottom-auto md:-right-5 md:h-full md:w-auto">
<Button
animate={
breakpoint === "sm"
? hideDetails
? "hiddenSm"
: "visibleSm"
: hideDetails
? "hidden"
: "visible"
}
variants={{
hidden: { rotate: 180 },
hiddenSm: { rotate: -90 },
visible: { rotate: 0 },
visibleSm: { rotate: 90 },
}}
onClick={() => {
setHideDetails(!hideDetails);
}}
>
<FiChevronRight />
</Button>
</div>
<AnimatePresence>
{!hideDetails && (
<>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className={`absolute -bottom-5 z-20 flex md:mt-0 md:hidden md:pb-2 ${
hideDetails ? "opacity-0" : "opacity-100"
}`}
>
<VariantSelectButton options={device.variants} />
</motion.div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute -bottom-3 right-0 m-auto mr-2 ml-auto flex md:inset-x-1 md:bottom-4 md:mt-2"
>
<div className="m-auto flex gap-2">
{device.features.BLE && (
<Badge
name="Bluetooth"
color="bg-blue-500"
icon={<FiBluetooth />}
/>
)}
{device.features.WiFi && (
<Badge
name="WiFi"
color="bg-orange-500"
icon={<FiWifi />}
/>
)}
</div>
</motion.div>
</>
)}
</AnimatePresence>
</motion.div>
<div
className={`h-7 bg-base opacity-0 md:h-auto md:w-7 ${
hideDetails ? "flex" : "hidden"
}`}
/>
</motion.div>
</div>
<div className="z-[1] mt-[25%] flex h-full flex-col md:ml-[20%] md:mt-0 md:w-4/5">
<div className="z-0 hidden pb-2 md:flex">
<VariantSelectButton options={device.variants} />
</div>
<div
className={`mt-1 flex flex-grow rounded-2xl bg-base p-2 shadow-inner transition-opacity duration-100 ease-linear md:mt-0 md:rounded-l-none md:rounded-r-2xl md:p-4 ${
hideDetails ? "opacity-0" : "opacity-100"
}`}
>
<Tab.Group
as="div"
className="flex flex-grow flex-col rounded-2xl bg-primary p-2"
>
<Tab.List className="flex gap-2">
<CardTab title="Info" />
<CardTab title="Power" />
<CardTab title="Pinout" />
</Tab.List>
<Tab.Panels as="div" className="flex-grow overflow-y-auto">
<InfoTab device={device} />
<PowerTab device={device} />
<PinoutTab device={device} />
</Tab.Panels>
</Tab.Group>
</div>
</div>
</Modal>
);
};

View file

@ -5,44 +5,44 @@ import { Tab } from "@headlessui/react";
import type { IDevice } from "../../../data/device";
export interface InfoTabProps {
device: IDevice;
device: IDevice;
}
export const InfoTab = ({ device }: InfoTabProps): JSX.Element => {
return (
<Tab.Panel>
<div className="px-4 py-5 sm:p-0">
<dl className="sm:divide-y sm:divide-gray-200">
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6">
<dt className="text-sm font-medium text-secondaryInv">
BLE/WiFi Version
</dt>
<dd className="mt-1 flex gap-1 text-sm text-tertiaryInv sm:col-span-2 sm:mt-0">
<span className="rounded-md bg-secondary px-0.5">
{device.specifications.BLEVersion}
</span>
/
<span className="rounded-md bg-secondary px-0.5">
{device.specifications.WiFiVersion}
</span>
</dd>
</div>
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6">
<dt className="text-sm font-medium text-secondaryInv">
BLE/WiFi Antenna
</dt>
<dd className="mt-1 flex gap-1 text-sm text-tertiaryInv sm:col-span-2 sm:mt-0">
<span className="rounded-md bg-secondary px-0.5">
{device.specifications.BLEAntenna}
</span>
/
<span className="rounded-md bg-secondary px-0.5">
{device.specifications.WiFiAntenna}
</span>
</dd>
</div>
</dl>
</div>
</Tab.Panel>
);
return (
<Tab.Panel>
<div className="px-4 py-5 sm:p-0">
<dl className="sm:divide-y sm:divide-gray-200">
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6">
<dt className="text-sm font-medium text-secondaryInv">
BLE/WiFi Version
</dt>
<dd className="mt-1 flex gap-1 text-sm text-tertiaryInv sm:col-span-2 sm:mt-0">
<span className="rounded-md bg-secondary px-0.5">
{device.specifications.BLEVersion}
</span>
/
<span className="rounded-md bg-secondary px-0.5">
{device.specifications.WiFiVersion}
</span>
</dd>
</div>
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:px-6">
<dt className="text-sm font-medium text-secondaryInv">
BLE/WiFi Antenna
</dt>
<dd className="mt-1 flex gap-1 text-sm text-tertiaryInv sm:col-span-2 sm:mt-0">
<span className="rounded-md bg-secondary px-0.5">
{device.specifications.BLEAntenna}
</span>
/
<span className="rounded-md bg-secondary px-0.5">
{device.specifications.WiFiAntenna}
</span>
</dd>
</div>
</dl>
</div>
</Tab.Panel>
);
};

View file

@ -5,32 +5,32 @@ import { Tab } from "@headlessui/react";
import type { IDevice } from "../../../data/device";
export interface PinoutTabProps {
device: IDevice;
device: IDevice;
}
export const PinoutTab = ({ device }: PinoutTabProps): JSX.Element => {
return (
<Tab.Panel className="flex">
<div className="m-auto flex gap-4 rounded-lg bg-slate-700 px-2 py-1 shadow-md">
{[
device.pinout.slice(0, device.misc.pinoutSplit),
device.pinout.slice(device.misc.pinoutSplit, device.pinout.length)
].map((group, index) => (
<div key={index}>
{group.map((pin, pinIndex) => (
<div
className={`flex gap-1 ${
index === 0 ? "flex-row" : "flex-row-reverse"
}`}
key={pinIndex}
>
<div className="m-auto h-3 w-3 rounded-full border bg-yellow-500" />
<span className="m-auto font-mono text-white">{pin.label}</span>
</div>
))}
</div>
))}
</div>
</Tab.Panel>
);
return (
<Tab.Panel className="flex">
<div className="m-auto flex gap-4 rounded-lg bg-slate-700 px-2 py-1 shadow-md">
{[
device.pinout.slice(0, device.misc.pinoutSplit),
device.pinout.slice(device.misc.pinoutSplit, device.pinout.length),
].map((group, index) => (
<div key={index}>
{group.map((pin, pinIndex) => (
<div
className={`flex gap-1 ${
index === 0 ? "flex-row" : "flex-row-reverse"
}`}
key={pinIndex}
>
<div className="m-auto h-3 w-3 rounded-full border bg-yellow-500" />
<span className="m-auto font-mono text-white">{pin.label}</span>
</div>
))}
</div>
))}
</div>
</Tab.Panel>
);
};

View file

@ -5,9 +5,9 @@ import { Tab } from "@headlessui/react";
import type { IDevice } from "../../../data/device";
export interface PowerTabProps {
device: IDevice;
device: IDevice;
}
export const PowerTab = (): JSX.Element => {
return <Tab.Panel className="h-32">Content 1</Tab.Panel>;
return <Tab.Panel className="h-32">Content 1</Tab.Panel>;
};

View file

@ -5,84 +5,84 @@ import { FiCheck } from "react-icons/fi";
import { HiSelector } from "react-icons/hi";
import { Listbox, Transition } from "@headlessui/react";
import type { Variant } from "@site/src/data/device.js";
import type { Variant } from "../../data/device";
export interface VariantSelectButtonProps {
options: Variant[];
options: Variant[];
}
export const VariantSelectButton = ({
options
options,
}: VariantSelectButtonProps): JSX.Element => {
const [selected, setSelected] = useState(options[options.length - 1]);
const [selected, setSelected] = useState(options[options.length - 1]);
return (
<Listbox value={selected} onChange={setSelected}>
{({ open }) => (
<>
<div className="relative select-none">
<Listbox.Button as={Fragment}>
<motion.button
whileHover={{ backgroundColor: "var(--tertiary)" }}
whileTap={{ scale: 0.99 }}
className="relative -mt-5 ml-2 flex w-fit gap-1 rounded-lg bg-secondary p-2 py-2 pl-3 pr-10 text-lg font-medium leading-6 shadow-md md:mt-2"
>
<span className="block truncate">{selected.name}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<HiSelector
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
</motion.button>
</Listbox.Button>
return (
<Listbox value={selected} onChange={setSelected}>
{({ open }) => (
<>
<div className="relative select-none">
<Listbox.Button as={Fragment}>
<motion.button
whileHover={{ backgroundColor: "var(--tertiary)" }}
whileTap={{ scale: 0.99 }}
className="relative -mt-5 ml-2 flex w-fit gap-1 rounded-lg bg-secondary p-2 py-2 pl-3 pr-10 text-lg font-medium leading-6 shadow-md md:mt-2"
>
<span className="block truncate">{selected.name}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<HiSelector
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
</motion.button>
</Listbox.Button>
<Transition
show={open}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-primary py-1 shadow-md ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{options.map((variant, index) => (
<Listbox.Option
key={index}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-3 pr-9 ${
active ? "bg-secondary" : ""
}`
}
value={variant}
>
{({ selected, active }) => (
<>
<span
className={`block truncate ${
selected ? "font-semibold" : "font-normal"
}`}
>
{variant.name}
</span>
<Transition
show={open}
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-primary py-1 shadow-md ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
{options.map((variant, index) => (
<Listbox.Option
key={index}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-3 pr-9 ${
active ? "bg-secondary" : ""
}`
}
value={variant}
>
{({ selected, active }) => (
<>
<span
className={`block truncate ${
selected ? "font-semibold" : "font-normal"
}`}
>
{variant.name}
</span>
{selected ? (
<span
className={`absolute inset-y-0 right-0 flex items-center pr-4 ${
active ? "" : "text-primaryInv"
}`}
>
<FiCheck className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</>
)}
</Listbox>
);
{selected ? (
<span
className={`absolute inset-y-0 right-0 flex items-center pr-4 ${
active ? "" : "text-primaryInv"
}`}
>
<FiCheck className="h-5 w-5" aria-hidden="true" />
</span>
) : null}
</>
)}
</Listbox.Option>
))}
</Listbox.Options>
</Transition>
</div>
</>
)}
</Listbox>
);
};

View file

@ -3,29 +3,29 @@ import React from "react";
import { FiExternalLink } from "react-icons/fi";
export interface SocialCardProps {
children: React.ReactNode;
color: string;
link: string;
children: React.ReactNode;
color: string;
link: string;
}
export const SocialCard = ({
children,
color,
link
children,
color,
link,
}: SocialCardProps): JSX.Element => {
return (
<div
className={`group relative flex h-24 w-36 min-w-max flex-shrink-0 rounded-xl shadow-xl ${color} m-2`}
>
{children}
<a
className="absolute top-0 left-0 right-0 bottom-0 hidden rounded-xl border border-accent bg-secondary bg-opacity-95 text-2xl shadow-xl group-hover:flex"
href={link}
rel="noreferrer"
target="_blank"
>
<FiExternalLink className="m-auto" />
</a>
</div>
);
return (
<div
className={`group relative flex h-24 w-36 min-w-max flex-shrink-0 rounded-xl shadow-xl ${color} m-2`}
>
{children}
<a
className="absolute top-0 left-0 right-0 bottom-0 hidden rounded-xl border border-accent bg-secondary bg-opacity-95 text-2xl shadow-xl group-hover:flex"
href={link}
rel="noreferrer"
target="_blank"
>
<FiExternalLink className="m-auto" />
</a>
</div>
);
};

View file

@ -0,0 +1,336 @@
import React, { useEffect } from "react";
import { Protobuf, Types } from "@meshtastic/meshtasticjs";
interface Region {
freq_start: number;
freq_end: number;
duty_cycle: number;
spacing: number;
power_limit: number;
}
interface Modem {
bw: number;
cr: number;
sf: number;
}
const RegionData = new Map<Protobuf.Config_LoRaConfig_RegionCode, Region>([
[
Protobuf.Config_LoRaConfig_RegionCode.US,
{
freq_start: 902.0,
freq_end: 928.0,
duty_cycle: 100,
spacing: 0,
power_limit: 30,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.EU_433,
{
freq_start: 433.0,
freq_end: 434.0,
duty_cycle: 10,
spacing: 0,
power_limit: 12,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.EU_868,
{
freq_start: 869.4,
freq_end: 869.65,
duty_cycle: 10,
spacing: 0,
power_limit: 27,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.CN,
{
freq_start: 470.0,
freq_end: 510.0,
duty_cycle: 100,
spacing: 0,
power_limit: 19,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.JP,
{
freq_start: 920.8,
freq_end: 927.8,
duty_cycle: 100,
spacing: 0,
power_limit: 16,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.ANZ,
{
freq_start: 915.0,
freq_end: 928.0,
duty_cycle: 100,
spacing: 0,
power_limit: 30,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.RU,
{
freq_start: 868.7,
freq_end: 869.2,
duty_cycle: 100,
spacing: 0,
power_limit: 20,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.KR,
{
freq_start: 920.0,
freq_end: 923.0,
duty_cycle: 100,
spacing: 0,
power_limit: 0,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.TW,
{
freq_start: 920.0,
freq_end: 925.0,
duty_cycle: 100,
spacing: 0,
power_limit: 0,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.IN,
{
freq_start: 865.0,
freq_end: 867.0,
duty_cycle: 100,
spacing: 0,
power_limit: 30,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.NZ_865,
{
freq_start: 864.0,
freq_end: 868.0,
duty_cycle: 100,
spacing: 0,
power_limit: 36,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.TH,
{
freq_start: 920.0,
freq_end: 925.0,
duty_cycle: 100,
spacing: 0,
power_limit: 16,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.UA_433,
{
freq_start: 433.0,
freq_end: 434.7,
duty_cycle: 10,
spacing: 0,
power_limit: 10,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.UA_868,
{
freq_start: 868.0,
freq_end: 868.6,
duty_cycle: 1,
spacing: 0,
power_limit: 14,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.LORA_24,
{
freq_start: 2400.0,
freq_end: 2483.5,
duty_cycle: 100,
spacing: 0,
power_limit: 10,
},
],
[
Protobuf.Config_LoRaConfig_RegionCode.UNSET,
{
freq_start: 902.0,
freq_end: 928.0,
duty_cycle: 100,
spacing: 0,
power_limit: 30,
},
],
]);
const modemPresets = new Map<Protobuf.Config_LoRaConfig_ModemPreset, Modem>([
[
Protobuf.Config_LoRaConfig_ModemPreset.SHORT_FAST,
{
bw: 250,
cr: 8,
sf: 7,
},
],
[
Protobuf.Config_LoRaConfig_ModemPreset.SHORT_SLOW,
{
bw: 250,
cr: 8,
sf: 8,
},
],
[
Protobuf.Config_LoRaConfig_ModemPreset.MEDIUM_FAST,
{
bw: 250,
cr: 8,
sf: 9,
},
],
[
Protobuf.Config_LoRaConfig_ModemPreset.MEDIUM_SLOW,
{
bw: 250,
cr: 8,
sf: 10,
},
],
[
Protobuf.Config_LoRaConfig_ModemPreset.LONG_FAST,
{
bw: 250,
cr: 8,
sf: 11,
},
],
[
Protobuf.Config_LoRaConfig_ModemPreset.LONG_MODERATE,
{
bw: 125,
cr: 8,
sf: 11,
},
],
[
Protobuf.Config_LoRaConfig_ModemPreset.LONG_SLOW,
{
bw: 125,
cr: 8,
sf: 12,
},
],
[
Protobuf.Config_LoRaConfig_ModemPreset.VERY_LONG_SLOW,
{
bw: 62.5,
cr: 8,
sf: 12,
},
],
]);
export const FrequencyCalculator = (): JSX.Element => {
const [modemPreset, setModemPreset] =
React.useState<Protobuf.Config_LoRaConfig_ModemPreset>(
Protobuf.Config_LoRaConfig_ModemPreset.LONG_FAST,
);
const [region, setRegion] =
React.useState<Protobuf.Config_LoRaConfig_RegionCode>(
Protobuf.Config_LoRaConfig_RegionCode.US,
);
const [channel, setChannel] = React.useState<Types.ChannelNumber>(
Types.ChannelNumber.PRIMARY,
);
const [numChannels, setNumChannels] = React.useState<number>(0);
const [channelFrequency, setChannelFrequency] = React.useState<number>(0);
useEffect(() => {
const selectedRegion = RegionData.get(region);
const selectedModemPreset = modemPresets.get(modemPreset);
setNumChannels(
Math.floor(
(selectedRegion.freq_end - selectedRegion.freq_start) /
(selectedRegion.spacing + selectedModemPreset.bw / 1000),
),
);
setChannelFrequency(
selectedRegion.freq_start +
selectedModemPreset.bw / 2000 +
channel * (selectedModemPreset.bw / 1000),
);
}, [modemPreset, region, channel]);
return (
<div className="flex flex-col border-l-[5px] shadow-md my-4 border-accent rounded-lg p-4 bg-secondary gap-2">
<div className="flex gap-2">
<label>Modem Preset:</label>
<select
value={modemPreset}
onChange={(e) =>
setModemPreset(
parseInt(
e.target.value,
) as Protobuf.Config_LoRaConfig_ModemPreset,
)
}
>
{Array.from(modemPresets.keys()).map((key) => (
<option key={key} value={key}>
{Protobuf.Config_LoRaConfig_ModemPreset[key]}
</option>
))}
</select>
</div>
<div className="flex gap-2">
<label>Region:</label>
<select
value={region}
onChange={(e) => setRegion(parseInt(e.target.value))}
>
{Array.from(RegionData.keys()).map((key) => (
<option key={key} value={key}>
{Protobuf.Config_LoRaConfig_RegionCode[key]}
</option>
))}
</select>
</div>
<div className="flex gap-2">
<label>Channel:</label>
<select
value={channel}
onChange={(e) => setChannel(parseInt(e.target.value))}
>
{Array.from(Array(numChannels).keys()).map((key) => (
<option key={key} value={key}>
{key}
</option>
))}
</select>
</div>
<div className="flex gap-2">
<label className="font-semibold">Number of channels:</label>
<input type="number" disabled value={numChannels} />
</div>
<div className="flex gap-2">
<label className="font-semibold">Channel Frequency:</label>
<input type="number" disabled value={channelFrequency} />
</div>
</div>
);
};

View file

@ -1,28 +1,28 @@
export type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;
? {
[P in keyof T]?: DeepPartial<T[P]>;
}
: T;
export enum UseCase {
Solar,
Router,
Portable
Solar = 0,
Router = 1,
Portable = 2,
}
enum PinUsage {
LoRa,
GNSS
LoRa = 0,
GNSS = 1,
}
export interface Pin {
offset: {
x: number;
y: number;
};
name: string;
label: string;
usage?: PinUsage;
offset: {
x: number;
y: number;
};
name: string;
label: string;
usage?: PinUsage;
}
export type DeviceName = "tbeam" | "techo";
@ -44,51 +44,51 @@ export type LORAModule = "SX1276" | "SX1262";
export type Variant = DeepPartial<Omit<IDevice, "variants">> & { name: string };
export enum Stability {
Stable,
Semi,
Unstable,
Broken
Stable = 0,
Semi = 1,
Unstable = 2,
Broken = 3,
}
export type Module =
| "cannedMessage"
| "externalNotification"
| "rangeTest"
| "rotaryEncoder"
| "storeAndForward"
| "telemetry";
| "cannedMessage"
| "externalNotification"
| "rangeTest"
| "rotaryEncoder"
| "storeAndForward"
| "telemetry";
export interface IDevice {
name: string;
misc: {
SuggestedUse: UseCase[];
Stability: Stability;
Gradient: string;
pinoutSplit: number;
};
images: {
Front: string;
Back: string;
};
features: {
BLE: boolean;
WiFi: boolean;
Modules: Module[];
};
specifications: {
BLEVersion?: BLEVersion;
BLEAntenna?: AntennaType;
WiFiVersion?: string;
WiFiAntenna?: AntennaType;
Chipset: Chipset;
Driver: SerialAdapter;
GNSS?: GNSSModule;
FlashSize: number;
Frequencies: Frequency[];
LoRa: LORAModule;
PSRAM: number;
RAM?: number;
};
pinout: Pin[];
variants: Variant[];
name: string;
misc: {
SuggestedUse: UseCase[];
Stability: Stability;
Gradient: string;
pinoutSplit: number;
};
images: {
Front: string;
Back: string;
};
features: {
BLE: boolean;
WiFi: boolean;
Modules: Module[];
};
specifications: {
BLEVersion?: BLEVersion;
BLEAntenna?: AntennaType;
WiFiVersion?: string;
WiFiAntenna?: AntennaType;
Chipset: Chipset;
Driver: SerialAdapter;
GNSS?: GNSSModule;
FlashSize: number;
Frequencies: Frequency[];
LoRa: LORAModule;
PSRAM: number;
RAM?: number;
};
pinout: Pin[];
variants: Variant[];
}

View file

@ -1,66 +1,66 @@
import { IDevice, Stability, UseCase } from "../device";
export const heltec: IDevice = {
name: "Heltec",
misc: {
Stability: Stability.Unstable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-pink-300 via-purple-300 to-indigo-400"
},
images: {
Front: "/img/hardware/heltec-v2.png",
Back: ""
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry"
]
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M"
}
}
]
name: "Heltec",
misc: {
Stability: Stability.Unstable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-pink-300 via-purple-300 to-indigo-400",
},
images: {
Front: "/img/hardware/heltec-v2.png",
Back: "",
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry",
],
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined,
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable,
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
},
},
],
};

View file

@ -1,66 +1,66 @@
import { IDevice, Stability, UseCase } from "../device";
export const hydra: IDevice = {
name: "Hydra",
misc: {
Stability: Stability.Stable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-indigo-200 via-red-200 to-yellow-100"
},
images: {
Front: "/img/hardware/Hydra-PCB.2.1.svg",
Back: ""
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry"
]
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M"
}
}
]
name: "Hydra",
misc: {
Stability: Stability.Stable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-indigo-200 via-red-200 to-yellow-100",
},
images: {
Front: "/img/hardware/Hydra-PCB.2.1.svg",
Back: "",
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry",
],
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined,
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable,
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
},
},
],
};

View file

@ -1,66 +1,66 @@
import { IDevice, Stability, UseCase } from "../device";
export const nano_g1: IDevice = {
name: "Nano G1",
misc: {
Stability: Stability.Unstable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-green-200 to-green-500"
},
images: {
Front: "/img/hardware/nano_g1_front.svg",
Back: "/img/hardware/nano_g1_back.svg"
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry"
]
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M"
}
}
]
name: "Nano G1",
misc: {
Stability: Stability.Unstable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-green-200 to-green-500",
},
images: {
Front: "/img/hardware/nano_g1_front.svg",
Back: "/img/hardware/nano_g1_back.svg",
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry",
],
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined,
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable,
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
},
},
],
};

View file

@ -1,66 +1,66 @@
import { IDevice, Stability, UseCase } from "../device";
export const rak19001: IDevice = {
name: "WisBlock 19001",
misc: {
Stability: Stability.Stable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-indigo-300 to-purple-400"
},
images: {
Front: "/img/hardware/rak/RAK19001.png",
Back: ""
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry"
]
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M"
}
}
]
name: "WisBlock 19001",
misc: {
Stability: Stability.Stable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-indigo-300 to-purple-400",
},
images: {
Front: "/img/hardware/rak/RAK19001.png",
Back: "",
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry",
],
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined,
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable,
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
},
},
],
};

View file

@ -1,66 +1,66 @@
import { IDevice, Stability, UseCase } from "../device";
export const rak19003: IDevice = {
name: "WisBlock 19003",
misc: {
Stability: Stability.Stable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-b from-orange-500 to-yellow-300"
},
images: {
Front: "/img/hardware/rak/RAK19003.png",
Back: ""
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry"
]
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M"
}
}
]
name: "WisBlock 19003",
misc: {
Stability: Stability.Stable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-b from-orange-500 to-yellow-300",
},
images: {
Front: "/img/hardware/rak/RAK19003.png",
Back: "",
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry",
],
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined,
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable,
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
},
},
],
};

View file

@ -1,277 +1,277 @@
import { IDevice, Stability, UseCase } from "../device";
export const tbeam: IDevice = {
name: "T-Beam",
misc: {
Stability: Stability.Stable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500",
pinoutSplit: 13
},
images: {
Front: "/img/hardware/tbeam-v1.1.svg",
Back: ""
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry"
]
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M"
}
}
],
pinout: [
{
label: "VP",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "VN",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "RST",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "15",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "35",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "32",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "33",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "25",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "14",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "13",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "2",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "GND",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "5V",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "TX",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "RX",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "23",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "4",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "0",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "GND",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "3V3",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "GND",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "22",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "21",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "3.3V",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "LoRa2",
name: "IO1",
offset: {
x: 5,
y: 5
}
},
{
label: "LoRa1",
name: "IO1",
offset: {
x: 5,
y: 5
}
}
]
name: "T-Beam",
misc: {
Stability: Stability.Stable,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500",
pinoutSplit: 13,
},
images: {
Front: "/img/hardware/tbeam-v1.1.svg",
Back: "",
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry",
],
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined,
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable,
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
},
},
],
pinout: [
{
label: "VP",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "VN",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "RST",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "15",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "35",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "32",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "33",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "25",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "14",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "13",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "2",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "GND",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "5V",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "TX",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "RX",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "23",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "4",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "0",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "GND",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "3V3",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "GND",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "22",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "21",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "3.3V",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "LoRa2",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
{
label: "LoRa1",
name: "IO1",
offset: {
x: 5,
y: 5,
},
},
],
};

View file

@ -1,66 +1,66 @@
import { IDevice, Stability, UseCase } from "../device";
export const techo: IDevice = {
name: "T-Echo",
misc: {
Stability: Stability.Semi,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-gray-700 via-gray-900 to-black"
},
images: {
Front: "/img/hardware/t-echo-lilygo.jpg",
Back: ""
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry"
]
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915]
}
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M"
}
}
]
name: "T-Echo",
misc: {
Stability: Stability.Semi,
SuggestedUse: [UseCase.Portable],
Gradient: "bg-gradient-to-r from-gray-700 via-gray-900 to-black",
},
images: {
Front: "/img/hardware/t-echo-lilygo.jpg",
Back: "",
},
features: {
BLE: true,
WiFi: true,
Modules: [
"cannedMessage",
"externalNotification",
"rangeTest",
"rotaryEncoder",
"storeAndForward",
"telemetry",
],
},
specifications: {
BLEVersion: "4.2",
BLEAntenna: "Integrated",
WiFiVersion: "2.4GHz 802.11 b/g/n WPA/WPA2/WPA2-Enterprise/SPS",
WiFiAntenna: "Integrated",
Chipset: "ESP32",
Driver: "CH9102",
GNSS: "NEO-6M",
FlashSize: 4,
Frequencies: [433, 868, 915, 923],
LoRa: "SX1262",
PSRAM: 8,
RAM: undefined,
},
variants: [
{
name: "TBeam 0.7",
misc: {
Stability: Stability.Unstable,
},
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.0",
specifications: {
Frequencies: [868, 915],
},
},
{
name: "TBeam 1.1",
specifications: {
Driver: "CP210X",
GNSS: "NEO-6M",
},
},
],
};

View file

@ -1,11 +1,11 @@
export interface IRegion {
name: string;
freqStart: number;
freqEnd: number;
dutyCycle: number;
spacing: number;
powerLimit: number;
audioPermitted: boolean;
frequencySwitching: boolean;
wideLora: boolean;
name: string;
freqStart: number;
freqEnd: number;
dutyCycle: number;
spacing: number;
powerLimit: number;
audioPermitted: boolean;
frequencySwitching: boolean;
wideLora: boolean;
}

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const ANZ: IRegion = {
name: "ANZ",
freqStart: 915.0,
freqEnd: 928.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 30,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "ANZ",
freqStart: 915.0,
freqEnd: 928.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 30,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const CN: IRegion = {
name: "CN",
freqStart: 470.0,
freqEnd: 510.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 19,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "CN",
freqStart: 470.0,
freqEnd: 510.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 19,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const EU_433: IRegion = {
name: "EU_433",
freqStart: 433.0,
freqEnd: 434.0,
dutyCycle: 10,
spacing: 0,
powerLimit: 12,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "EU_433",
freqStart: 433.0,
freqEnd: 434.0,
dutyCycle: 10,
spacing: 0,
powerLimit: 12,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const EU_868: IRegion = {
name: "EU_868",
freqStart: 869.4,
freqEnd: 869.65,
dutyCycle: 10,
spacing: 0,
powerLimit: 27,
audioPermitted: false,
frequencySwitching: false,
wideLora: false
name: "EU_868",
freqStart: 869.4,
freqEnd: 869.65,
dutyCycle: 10,
spacing: 0,
powerLimit: 27,
audioPermitted: false,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const IN: IRegion = {
name: "IN",
freqStart: 865.0,
freqEnd: 867.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 30,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "IN",
freqStart: 865.0,
freqEnd: 867.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 30,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const JP: IRegion = {
name: "JP",
freqStart: 920.8,
freqEnd: 927.8,
dutyCycle: 100,
spacing: 0,
powerLimit: 16,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "JP",
freqStart: 920.8,
freqEnd: 927.8,
dutyCycle: 100,
spacing: 0,
powerLimit: 16,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const KR: IRegion = {
name: "KR",
freqStart: 920.0,
freqEnd: 925.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 0,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "KR",
freqStart: 920.0,
freqEnd: 925.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 0,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const LORA_24: IRegion = {
name: "LORA_24",
freqStart: 2400.0,
freqEnd: 2483.5,
dutyCycle: 100,
spacing: 0,
powerLimit: 10,
audioPermitted: true,
frequencySwitching: false,
wideLora: true
name: "LORA_24",
freqStart: 2400.0,
freqEnd: 2483.5,
dutyCycle: 100,
spacing: 0,
powerLimit: 10,
audioPermitted: true,
frequencySwitching: false,
wideLora: true,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const NZ_865: IRegion = {
name: "NZ_865",
freqStart: 864.0,
freqEnd: 868.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 36,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "NZ_865",
freqStart: 864.0,
freqEnd: 868.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 36,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const RU: IRegion = {
name: "RU",
freqStart: 868.7,
freqEnd: 869.2,
dutyCycle: 100,
spacing: 0,
powerLimit: 20,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "RU",
freqStart: 868.7,
freqEnd: 869.2,
dutyCycle: 100,
spacing: 0,
powerLimit: 20,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const TH: IRegion = {
name: "TH",
freqStart: 920.0,
freqEnd: 925.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 16,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "TH",
freqStart: 920.0,
freqEnd: 925.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 16,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const TW: IRegion = {
name: "TW",
freqStart: 920.0,
freqEnd: 925.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 0,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "TW",
freqStart: 920.0,
freqEnd: 925.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 0,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const UA_433: IRegion = {
name: "UA_433",
freqStart: 433.0,
freqEnd: 434.7,
dutyCycle: 10,
spacing: 0,
powerLimit: 10,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "UA_433",
freqStart: 433.0,
freqEnd: 434.7,
dutyCycle: 10,
spacing: 0,
powerLimit: 10,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const UA_868: IRegion = {
name: "UA_868",
freqStart: 868.0,
freqEnd: 868.6,
dutyCycle: 1,
spacing: 0,
powerLimit: 14,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "UA_868",
freqStart: 868.0,
freqEnd: 868.6,
dutyCycle: 1,
spacing: 0,
powerLimit: 14,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const UNSET: IRegion = {
name: "UNSET",
freqStart: 902.0,
freqEnd: 928.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 30,
audioPermitted: true,
frequencySwitching: false,
wideLora: false
name: "UNSET",
freqStart: 902.0,
freqEnd: 928.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 30,
audioPermitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,13 +1,13 @@
import { IRegion } from "../region";
export const US: IRegion = {
name: "US",
freqStart: 902.0,
freqEnd: 928.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 30,
audioPrmitted: true,
frequencySwitching: false,
wideLora: false
name: "US",
freqStart: 902.0,
freqEnd: 928.0,
dutyCycle: 100,
spacing: 0,
powerLimit: 30,
audioPrmitted: true,
frequencySwitching: false,
wideLora: false,
};

View file

@ -1,29 +1,29 @@
import React from "react";
import { Showcase } from "../utils/apiTypes.js";
import { Showcase } from "../utils/apiTypes";
import { useSelectedTags } from "./useSelectedTags";
const filterNetworks = (
showcaseNetworks: Showcase[],
selectedTags: string[]
showcaseNetworks: Showcase[],
selectedTags: string[],
) => {
if (selectedTags.length === 0) {
return showcaseNetworks;
}
return showcaseNetworks.filter((showcaseNetwork) => {
if (showcaseNetwork.tags.length === 0) {
return false;
}
return selectedTags.every((queryTag) =>
showcaseNetwork.tags.find((searchTag) => searchTag.label === queryTag)
);
});
if (selectedTags.length === 0) {
return showcaseNetworks;
}
return showcaseNetworks.filter((showcaseNetwork) => {
if (showcaseNetwork.tags.length === 0) {
return false;
}
return selectedTags.every((queryTag) =>
showcaseNetwork.tags.find((searchTag) => searchTag.label === queryTag),
);
});
};
export const useFilteredNetworks = (networks: Showcase[]) => {
const selectedTags = useSelectedTags();
return React.useMemo(
() => filterNetworks(networks, selectedTags),
[selectedTags]
);
const selectedTags = useSelectedTags();
return React.useMemo(
() => filterNetworks(networks, selectedTags),
[selectedTags],
);
};

View file

@ -5,12 +5,12 @@ import { useLocation } from "@docusaurus/router";
import { readSearchTags } from "../pages/showcase/_components/TagSelect";
export const useSelectedTags = () => {
const location = useLocation();
const [selectedTags, setSelectedTags] = React.useState<string[]>([]);
React.useEffect(() => {
const tags = readSearchTags(location.search);
setSelectedTags(tags);
}, [location]);
const location = useLocation();
const [selectedTags, setSelectedTags] = React.useState<string[]>([]);
React.useEffect(() => {
const tags = readSearchTags(location.search);
setSelectedTags(tags);
}, [location]);
return selectedTags;
return selectedTags;
};

View file

@ -4,454 +4,455 @@ import { FiTwitter } from "react-icons/fi";
import { ChevronRightIcon } from "@heroicons/react/20/solid";
import Layout from "@theme/Layout";
import { Dark, Light } from "/src/components/ColorMode";
import { Dark, Light } from "../../components/ColorMode";
const TwoPointZero = (): JSX.Element => {
const stats = [
{ label: "Active Nodes", value: "A Lot!" },
{ label: "Community Members", value: "4000+" },
{ label: "Firmware Commits", value: "4900+" },
{ label: "Community Donations", value: "$5700+" }
];
const logos = [
{
name: "Vercel",
url: "/2.0/vercel-logotype-dark.svg"
},
{
name: "Cloudflare",
url: "/2.0/CF_logo_horizontal_blktype.svg"
},
{
name: "RAK Wireless",
url: "/2.0/RAK-blue-main.svg"
},
{
name: "Open Collective",
url: "/2.0/opencollectivelogo.svg"
},
{
name: "LILYGO",
url: "/2.0/LILYGO.png"
},
{
name: "Discord",
url: "/2.0/discord.svg"
}
];
return (
<Layout title="Meshtastic 2.0" description="Meshtastic 2.0 Landing Page">
<div>
<main>
{/* Hero section */}
<div className="overflow-hidden pt-8 sm:pt-12 lg:relative lg:py-24">
<div className="mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:grid lg:max-w-7xl lg:grid-cols-2 lg:gap-24 lg:px-8">
<div>
<div>
<Dark>
<img
className="h-11 w-auto"
it
src="/design/logo/svg/Mesh_Logo_White.svg"
alt="Meshtastic Logo"
/>
</Dark>
<Light>
<img
className="h-11 w-auto"
it
src="/design/logo/svg/Mesh_Logo_Black.svg"
alt="Meshtastic Logo"
/>
</Light>
</div>
<div className="mt-20">
<div>
<a href="#" className="inline-flex space-x-4">
<span className="rounded bg-rose-50 px-2.5 py-1 text-sm font-semibold text-rose-500">
What's new
</span>
<span className="inline-flex items-center space-x-1 text-sm font-medium text-rose-500">
<span>Click to find out</span>
<ChevronRightIcon
className="h-5 w-5"
aria-hidden="true"
/>
</span>
</a>
</div>
<div className="mt-6 sm:max-w-xl">
<h1 className="text-4xl font-bold tracking-tight --ifm-heading-color sm:text-5xl">
Meshtastic 2.0 🎉🎉🎉
</h1>
<p className="mt-6 text-xl --ifm-font-color-base">
After 9 months in the making, we present to you the next
milestone for the Meshtastic project.
</p>
</div>
<div className="mt-12 sm:w-full sm:max-w-lg">
<p className="mt-6 mb-4 text-xl --ifm-font-color-base">
As a part of the launch event, we are running a number of
giveaways, so jump in and win some prizes.
</p>
<div className="flex gap-2">
<a
href="#start"
className="flex w-full rounded-md border border-transparent bg-rose-500 px-5 py-3 font-medium text-white shadow hover:bg-rose-600 hover:text-black hover:no-underline focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 sm:px-10"
>
<span className="m-auto">Find Out More</span>
</a>
<a
className="flex w-16 rounded-md border border-transparent bg-[#1DA1F2] shadow hover:bg-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2"
target="_blank"
rel="noreferrer noopener"
href="https://twitter.com/TheMeshtastic/status/1586933393526333441"
>
<FiTwitter className="m-auto text-white" />
</a>
</div>
</div>
</div>
</div>
</div>
const stats = [
{ label: "Active Nodes", value: "A Lot!" },
{ label: "Community Members", value: "4000+" },
{ label: "Firmware Commits", value: "4900+" },
{ label: "Community Donations", value: "$5700+" },
];
const logos = [
{
name: "Vercel",
url: "/2.0/vercel-logotype-dark.svg",
},
{
name: "Cloudflare",
url: "/2.0/CF_logo_horizontal_blktype.svg",
},
{
name: "RAK Wireless",
url: "/2.0/RAK-blue-main.svg",
},
{
name: "Open Collective",
url: "/2.0/opencollectivelogo.svg",
},
{
name: "LILYGO",
url: "/2.0/LILYGO.png",
},
{
name: "Discord",
url: "/2.0/discord.svg",
},
];
return (
<Layout title="Meshtastic 2.0" description="Meshtastic 2.0 Landing Page">
<div>
<main>
{/* Hero section */}
<div className="overflow-hidden pt-8 sm:pt-12 lg:relative lg:py-24">
<div className="mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:grid lg:max-w-7xl lg:grid-cols-2 lg:gap-24 lg:px-8">
<div>
<div>
<Dark>
<img
className="h-11 w-auto"
src="/design/logo/svg/Mesh_Logo_White.svg"
alt="Meshtastic Logo"
/>
</Dark>
<Light>
<img
className="h-11 w-auto"
src="/design/logo/svg/Mesh_Logo_Black.svg"
alt="Meshtastic Logo"
/>
</Light>
</div>
<div className="mt-20">
<div>
<a
href="https://github.com/meshtastic/firmware/releases"
className="inline-flex space-x-4"
>
<span className="rounded bg-rose-50 px-2.5 py-1 text-sm font-semibold text-rose-500">
What's new
</span>
<span className="inline-flex items-center space-x-1 text-sm font-medium text-rose-500">
<span>Click to find out</span>
<ChevronRightIcon
className="h-5 w-5"
aria-hidden="true"
/>
</span>
</a>
</div>
<div className="mt-6 sm:max-w-xl">
<h1 className="text-4xl font-bold tracking-tight --ifm-heading-color sm:text-5xl">
Meshtastic 2.0 🎉🎉🎉
</h1>
<p className="mt-6 text-xl --ifm-font-color-base">
After 9 months in the making, we present to you the next
milestone for the Meshtastic project.
</p>
</div>
<div className="mt-12 sm:w-full sm:max-w-lg">
<p className="mt-6 mb-4 text-xl --ifm-font-color-base">
As a part of the launch event, we are running a number of
giveaways, so jump in and win some prizes.
</p>
<div className="flex gap-2">
<a
href="#start"
className="flex w-full rounded-md border border-transparent bg-rose-500 px-5 py-3 font-medium text-white shadow hover:bg-rose-600 hover:text-black hover:no-underline focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 sm:px-10"
>
<span className="m-auto">Find Out More</span>
</a>
<a
className="flex w-16 rounded-md border border-transparent bg-[#1DA1F2] shadow hover:bg-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2"
target="_blank"
rel="noreferrer noopener"
href="https://twitter.com/TheMeshtastic/status/1586933393526333441"
>
<FiTwitter className="m-auto text-white" />
</a>
</div>
</div>
</div>
</div>
</div>
<div className="sm:mx-auto sm:max-w-3xl sm:px-6">
<div className="py-12 sm:relative sm:mt-12 sm:py-16 lg:absolute lg:inset-y-0 lg:right-0 lg:w-1/2">
<div className="hidden sm:block">
<div className="absolute inset-y-0 left-1/2 w-screen rounded-l-3xl bg-gray-50 lg:left-80 lg:right-0 lg:w-full" />
<svg
className="absolute top-8 right-1/2 -mr-3 lg:left-0 lg:m-0"
width={404}
height={392}
fill="none"
viewBox="0 0 404 392"
>
<defs>
<pattern
id="837c3e70-6c3a-44e6-8854-cc48c737b659"
x={0}
y={0}
width={20}
height={20}
patternUnits="userSpaceOnUse"
>
<rect
x={0}
y={0}
width={4}
height={4}
className="text-gray-200"
fill="currentColor"
/>
</pattern>
</defs>
<rect
width={404}
height={392}
fill="url(#837c3e70-6c3a-44e6-8854-cc48c737b659)"
/>
</svg>
</div>
<div className="relative -mr-40 pl-4 sm:mx-auto sm:max-w-3xl sm:px-0 lg:h-full lg:max-w-none lg:pl-12">
<img
className="w-full rounded-md shadow-xl ring-1 ring-black ring-opacity-5 lg:h-full lg:w-auto lg:max-w-none"
src="/2.0/webUI.png"
alt="Web UI"
/>
<img
className="absolute top-0 m-20 w-full rounded-md shadow-xl ring-1 ring-black ring-opacity-5 lg:h-full lg:w-auto lg:max-w-none"
src="/2.0/android.jpg"
alt="Android"
/>
<img
className="absolute top-0 left-96 m-20 w-full rounded-md shadow-xl ring-1 ring-black ring-opacity-5 lg:h-full lg:w-auto lg:max-w-none"
src="/2.0/ios.png"
alt="IOS"
/>
</div>
</div>
</div>
</div>
<div className="sm:mx-auto sm:max-w-3xl sm:px-6">
<div className="py-12 sm:relative sm:mt-12 sm:py-16 lg:absolute lg:inset-y-0 lg:right-0 lg:w-1/2">
<div className="hidden sm:block">
<div className="absolute inset-y-0 left-1/2 w-screen rounded-l-3xl bg-gray-50 lg:left-80 lg:right-0 lg:w-full" />
<svg
className="absolute top-8 right-1/2 -mr-3 lg:left-0 lg:m-0"
width={404}
height={392}
fill="none"
viewBox="0 0 404 392"
>
<defs>
<pattern
id="837c3e70-6c3a-44e6-8854-cc48c737b659"
x={0}
y={0}
width={20}
height={20}
patternUnits="userSpaceOnUse"
>
<rect
x={0}
y={0}
width={4}
height={4}
className="text-gray-200"
fill="currentColor"
/>
</pattern>
</defs>
<rect
width={404}
height={392}
fill="url(#837c3e70-6c3a-44e6-8854-cc48c737b659)"
/>
</svg>
</div>
<div className="relative -mr-40 pl-4 sm:mx-auto sm:max-w-3xl sm:px-0 lg:h-full lg:max-w-none lg:pl-12">
<img
className="w-full rounded-md shadow-xl ring-1 ring-black ring-opacity-5 lg:h-full lg:w-auto lg:max-w-none"
src="/2.0/webUI.png"
alt="Web UI"
/>
<img
className="absolute top-0 m-20 w-full rounded-md shadow-xl ring-1 ring-black ring-opacity-5 lg:h-full lg:w-auto lg:max-w-none"
src="/2.0/android.jpg"
alt="Android"
/>
<img
className="absolute top-0 left-96 m-20 w-full rounded-md shadow-xl ring-1 ring-black ring-opacity-5 lg:h-full lg:w-auto lg:max-w-none"
src="/2.0/ios.png"
alt="IOS"
/>
</div>
</div>
</div>
</div>
{/* Testimonial/stats section */}
<div className="relative mt-20">
<div className="lg:mx-auto lg:grid lg:max-w-7xl lg:grid-cols-2 lg:items-start lg:gap-24 lg:px-8">
<div className="relative sm:py-16 lg:py-0">
<div
aria-hidden="true"
className="hidden sm:block lg:absolute lg:inset-y-0 lg:right-0 lg:w-screen"
>
<div className="absolute inset-y-0 right-1/2 w-full rounded-r-3xl bg-gray-50 lg:right-72" />
<svg
className="absolute top-8 left-1/2 -ml-3 lg:-right-8 lg:left-auto lg:top-12"
width={404}
height={392}
fill="none"
viewBox="0 0 404 392"
>
<defs>
<pattern
id="02f20b47-fd69-4224-a62a-4c9de5c763f7"
x={0}
y={0}
width={20}
height={20}
patternUnits="userSpaceOnUse"
>
<rect
x={0}
y={0}
width={4}
height={4}
className="text-gray-200"
fill="currentColor"
/>
</pattern>
</defs>
<rect
width={404}
height={392}
fill="url(#02f20b47-fd69-4224-a62a-4c9de5c763f7)"
/>
</svg>
</div>
<div className="relative mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:max-w-none lg:px-0 lg:py-20">
{/* Testimonial card*/}
<div className="relative overflow-hidden rounded-2xl pt-64 pb-10 shadow-xl">
<img
className="absolute inset-0 h-full w-full object-cover"
src="/2.0/background.png"
alt=""
/>
<div className="absolute inset-0 bg-green-500 mix-blend-multiply" />
<div className="absolute inset-0 bg-gradient-to-t from-green-600 via-green-600 opacity-90" />
<div className="relative px-8">
<div>
<img
className="h-8 text-white"
src="/2.0/typelogo.svg"
alt="Meshtastic"
/>
</div>
<blockquote className="mt-8">
<div className="relative text-lg font-medium text-white md:flex-grow">
<svg
className="absolute top-0 left-0 h-8 w-8 -translate-x-3 -translate-y-2 transform text-rose-400"
fill="currentColor"
viewBox="0 0 32 32"
aria-hidden="true"
>
<path d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z" />
</svg>
<p className="relative">
Meshtastic is the neatest open source project I've
ever seen!
</p>
</div>
{/* Testimonial/stats section */}
<div className="relative mt-20">
<div className="lg:mx-auto lg:grid lg:max-w-7xl lg:grid-cols-2 lg:items-start lg:gap-24 lg:px-8">
<div className="relative sm:py-16 lg:py-0">
<div
aria-hidden="true"
className="hidden sm:block lg:absolute lg:inset-y-0 lg:right-0 lg:w-screen"
>
<div className="absolute inset-y-0 right-1/2 w-full rounded-r-3xl bg-gray-50 lg:right-72" />
<svg
className="absolute top-8 left-1/2 -ml-3 lg:-right-8 lg:left-auto lg:top-12"
width={404}
height={392}
fill="none"
viewBox="0 0 404 392"
>
<defs>
<pattern
id="02f20b47-fd69-4224-a62a-4c9de5c763f7"
x={0}
y={0}
width={20}
height={20}
patternUnits="userSpaceOnUse"
>
<rect
x={0}
y={0}
width={4}
height={4}
className="text-gray-200"
fill="currentColor"
/>
</pattern>
</defs>
<rect
width={404}
height={392}
fill="url(#02f20b47-fd69-4224-a62a-4c9de5c763f7)"
/>
</svg>
</div>
<div className="relative mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:max-w-none lg:px-0 lg:py-20">
{/* Testimonial card*/}
<div className="relative overflow-hidden rounded-2xl pt-64 pb-10 shadow-xl">
<img
className="absolute inset-0 h-full w-full object-cover"
src="/2.0/background.png"
alt=""
/>
<div className="absolute inset-0 bg-green-500 mix-blend-multiply" />
<div className="absolute inset-0 bg-gradient-to-t from-green-600 via-green-600 opacity-90" />
<div className="relative px-8">
<div>
<img
className="h-8 text-white"
src="/2.0/typelogo.svg"
alt="Meshtastic"
/>
</div>
<blockquote className="mt-8">
<div className="relative text-lg font-medium text-white md:flex-grow">
<svg
className="absolute top-0 left-0 h-8 w-8 -translate-x-3 -translate-y-2 transform text-rose-400"
fill="currentColor"
viewBox="0 0 32 32"
aria-hidden="true"
>
<path d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z" />
</svg>
<p className="relative">
Meshtastic is the neatest open source project I've
ever seen!
</p>
</div>
<footer className="mt-4">
<p className="text-base font-semibold text-rose-200">
Elvis Presley
</p>
</footer>
</blockquote>
</div>
</div>
</div>
</div>
<footer className="mt-4">
<p className="text-base font-semibold text-rose-200">
Elvis Presley
</p>
</footer>
</blockquote>
</div>
</div>
</div>
</div>
<div
id="start"
className="relative mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:px-0"
>
{/* Content area */}
<div className="pt-12 sm:pt-16 lg:pt-20">
<h2 className="text-3xl font-bold tracking-tight --ifm-heading-color sm:text-4xl">
A brief overview of all the changes and improvements
</h2>
<div className="mt-6 space-y-6 --ifm-font-color-base">
<h2>Monumental stuff!</h2>
<p className="leading-7 --ifm-font-color-base">
<li>
Completely new LoRA band plan with faster messaging
</li>
<li>Smarter and more reliable mesh routing</li>
<li>
Unlimited nodes* (80 Connected at a time, oldest node
will be removed when a new node joins the mesh)
</li>
<li>
New messaging additions: waypoints, reactions
(tap-backs), and telemetry
</li>
<li>
Improvements for Canned Messages module and CardKB
messaging for stand alone communicator devices
</li>
<li>Sensor, Screen, and Input device auto-detection</li>
<li>New devices supported (6 new targets!)</li>
<li>
Added over the air bluetooth updates for NRF devices
(RAK-4631)
</li>
<li>Ethernet support via RAK-13800</li>
<li>
Compass improvements for larger screens and
customizations
</li>
</p>
<h2>Nerd stuff!</h2>
<p className="leading-7 --ifm-font-color-base">
<li>New filesystem for ESP32 (LittleFS)</li>
<li>
Upgraded Arduino framework for both NRF52 and ESP32
</li>
<li>New bluetooth stack for ESP32 (NimBLE)</li>
<li>Unified GPS stack now using NMEA</li>
<li>Support for more I2C sensors</li>
<li>Support for ATECCA608B Cryptographic Coprocessor</li>
<li>More Serial module I/O modes</li>
<li>JSON messages over MQTT</li>
<li>
Device codebase refactored and optimized in many areas
</li>
<li>
Completely restructured protobufs and configuration
</li>
</p>
</div>
</div>
<div
id="start"
className="relative mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:px-0"
>
{/* Content area */}
<div className="pt-12 sm:pt-16 lg:pt-20">
<h2 className="text-3xl font-bold tracking-tight --ifm-heading-color sm:text-4xl">
A brief overview of all the changes and improvements
</h2>
<div className="mt-6 space-y-6 --ifm-font-color-base">
<h2>Monumental stuff!</h2>
<p className="leading-7 --ifm-font-color-base">
<li>
Completely new LoRA band plan with faster messaging
</li>
<li>Smarter and more reliable mesh routing</li>
<li>
Unlimited nodes* (80 Connected at a time, oldest node
will be removed when a new node joins the mesh)
</li>
<li>
New messaging additions: waypoints, reactions
(tap-backs), and telemetry
</li>
<li>
Improvements for Canned Messages module and CardKB
messaging for stand alone communicator devices
</li>
<li>Sensor, Screen, and Input device auto-detection</li>
<li>New devices supported (6 new targets!)</li>
<li>
Added over the air bluetooth updates for NRF devices
(RAK-4631)
</li>
<li>Ethernet support via RAK-13800</li>
<li>
Compass improvements for larger screens and
customizations
</li>
</p>
<h2>Nerd stuff!</h2>
<p className="leading-7 --ifm-font-color-base">
<li>New filesystem for ESP32 (LittleFS)</li>
<li>
Upgraded Arduino framework for both NRF52 and ESP32
</li>
<li>New bluetooth stack for ESP32 (NimBLE)</li>
<li>Unified GPS stack now using NMEA</li>
<li>Support for more I2C sensors</li>
<li>Support for ATECCA608B Cryptographic Coprocessor</li>
<li>More Serial module I/O modes</li>
<li>JSON messages over MQTT</li>
<li>
Device codebase refactored and optimized in many areas
</li>
<li>
Completely restructured protobufs and configuration
</li>
</p>
</div>
</div>
{/* Stats section */}
<div className="mt-10">
<dl className="grid grid-cols-2 gap-x-4 gap-y-8">
{stats.map((stat) => (
<div
key={stat.label}
className="border-t-2 border-gray-100 pt-6"
>
<dt className="font-medium --ifm-font-color-base">
{stat.label}
</dt>
<dd className="text-3xl font-bold tracking-tight --ifm-heading-color">
{stat.value}
</dd>
</div>
))}
</dl>
</div>
</div>
</div>
</div>
{/* Stats section */}
<div className="mt-10">
<dl className="grid grid-cols-2 gap-x-4 gap-y-8">
{stats.map((stat) => (
<div
key={stat.label}
className="border-t-2 border-gray-100 pt-6"
>
<dt className="font-medium --ifm-font-color-base">
{stat.label}
</dt>
<dd className="text-3xl font-bold tracking-tight --ifm-heading-color">
{stat.value}
</dd>
</div>
))}
</dl>
</div>
</div>
</div>
</div>
{/* Logo cloud section */}
<div className="mt-32">
<div className="mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="lg:grid lg:grid-cols-2 lg:items-center lg:gap-24">
<div>
<h2 className="text-3xl font-bold tracking-tight --ifm-heading-color sm:text-4xl">
All made possible by the amazing companies that support us.
</h2>
<p className="mt-6 max-w-3xl text-lg leading-7 --ifm-font-color-base">
Running a project of this scale is no easy feat, without the
generosity of many of our vendors and providers, none of
this would be possible.
</p>
</div>
<div className="mt-12 grid grid-cols-2 gap-0.5 md:grid-cols-3 lg:mt-0 lg:grid-cols-2">
{logos.map((logo) => (
<div
key={logo.name}
className="col-span-1 flex justify-center bg-gray-50 py-8 px-8"
>
<img
className="max-h-12"
src={logo.url}
alt={logo.name}
/>
</div>
))}
</div>
</div>
</div>
</div>
{/* Logo cloud section */}
<div className="mt-32">
<div className="mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="lg:grid lg:grid-cols-2 lg:items-center lg:gap-24">
<div>
<h2 className="text-3xl font-bold tracking-tight --ifm-heading-color sm:text-4xl">
All made possible by the amazing companies that support us.
</h2>
<p className="mt-6 max-w-3xl text-lg leading-7 --ifm-font-color-base">
Running a project of this scale is no easy feat, without the
generosity of many of our vendors and providers, none of
this would be possible.
</p>
</div>
<div className="mt-12 grid grid-cols-2 gap-0.5 md:grid-cols-3 lg:mt-0 lg:grid-cols-2">
{logos.map((logo) => (
<div
key={logo.name}
className="col-span-1 flex justify-center bg-gray-50 py-8 px-8"
>
<img
className="max-h-12"
src={logo.url}
alt={logo.name}
/>
</div>
))}
</div>
</div>
</div>
</div>
{/* CTA section */}
<div className="relative mt-24 sm:mt-32 sm:py-16">
<div aria-hidden="true" className="hidden sm:block">
<div className="absolute inset-y-0 left-0 w-1/2 rounded-r-3xl bg-gray-50" />
<svg
className="absolute top-8 left-1/2 -ml-3"
width={404}
height={392}
fill="none"
viewBox="0 0 404 392"
>
<defs>
<pattern
id="8228f071-bcee-4ec8-905a-2a059a2cc4fb"
x={0}
y={0}
width={20}
height={20}
patternUnits="userSpaceOnUse"
>
<rect
x={0}
y={0}
width={4}
height={4}
className="text-gray-200"
fill="currentColor"
/>
</pattern>
</defs>
<rect
width={404}
height={392}
fill="url(#8228f071-bcee-4ec8-905a-2a059a2cc4fb)"
/>
</svg>
</div>
<div className="mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="relative overflow-hidden rounded-2xl bg-green-500 px-6 py-10 shadow-xl sm:px-12 sm:py-20">
<div
aria-hidden="true"
className="absolute inset-0 -mt-72 sm:-mt-32 md:mt-0"
>
<svg
className="absolute inset-0 h-full w-full"
preserveAspectRatio="xMidYMid slice"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 1463 360"
>
<path
className="text-green-400 text-opacity-40"
fill="currentColor"
d="M-82.673 72l1761.849 472.086-134.327 501.315-1761.85-472.086z"
/>
<path
className="text-green-600 text-opacity-40"
fill="currentColor"
d="M-217.088 544.086L1544.761 72l134.327 501.316-1761.849 472.086z"
/>
</svg>
</div>
<div className="flex flex-col gap-12">
<div className="sm:text-center">
<h2 className="text-3xl font-bold tracking-tight text-white sm:text-4xl">
Congratulations to the winners!
</h2>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</Layout>
);
{/* CTA section */}
<div className="relative mt-24 sm:mt-32 sm:py-16">
<div aria-hidden="true" className="hidden sm:block">
<div className="absolute inset-y-0 left-0 w-1/2 rounded-r-3xl bg-gray-50" />
<svg
className="absolute top-8 left-1/2 -ml-3"
width={404}
height={392}
fill="none"
viewBox="0 0 404 392"
>
<defs>
<pattern
id="8228f071-bcee-4ec8-905a-2a059a2cc4fb"
x={0}
y={0}
width={20}
height={20}
patternUnits="userSpaceOnUse"
>
<rect
x={0}
y={0}
width={4}
height={4}
className="text-gray-200"
fill="currentColor"
/>
</pattern>
</defs>
<rect
width={404}
height={392}
fill="url(#8228f071-bcee-4ec8-905a-2a059a2cc4fb)"
/>
</svg>
</div>
<div className="mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:max-w-7xl lg:px-8">
<div className="relative overflow-hidden rounded-2xl bg-green-500 px-6 py-10 shadow-xl sm:px-12 sm:py-20">
<div
aria-hidden="true"
className="absolute inset-0 -mt-72 sm:-mt-32 md:mt-0"
>
<svg
className="absolute inset-0 h-full w-full"
preserveAspectRatio="xMidYMid slice"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 1463 360"
>
<path
className="text-green-400 text-opacity-40"
fill="currentColor"
d="M-82.673 72l1761.849 472.086-134.327 501.315-1761.85-472.086z"
/>
<path
className="text-green-600 text-opacity-40"
fill="currentColor"
d="M-217.088 544.086L1544.761 72l134.327 501.316-1761.849 472.086z"
/>
</svg>
</div>
<div className="flex flex-col gap-12">
<div className="sm:text-center">
<h2 className="text-3xl font-bold tracking-tight text-white sm:text-4xl">
Congratulations to the winners!
</h2>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</Layout>
);
};
export default TwoPointZero;

View file

@ -1,44 +1,44 @@
import React from "react";
export interface avatarProps {
imgUrl: string;
name?: string;
userName?: string;
description?: string;
imgUrl: string;
name?: string;
userName?: string;
description?: string;
}
export interface avatarLayoutProps {
list: list[];
list: list[];
}
export const Avatar = ({
imgUrl,
name,
description
imgUrl,
name,
description,
}: avatarProps): JSX.Element => {
return (
<div className="card m-4 border-2 border-secondary">
<div className="card__body">
<div className="avatar">
<img className="avatar__photo avatar__photo--sm" src={imgUrl} />
<div className="avatar__intro">
<div className="avatar__name">{name}</div>
<small className="avatar__subtitle">{description}</small>
</div>
</div>
</div>
</div>
);
return (
<div className="card m-4 border-2 border-secondary">
<div className="card__body">
<div className="avatar">
<img className="avatar__photo avatar__photo--sm" src={imgUrl} />
<div className="avatar__intro">
<div className="avatar__name">{name}</div>
<small className="avatar__subtitle">{description}</small>
</div>
</div>
</div>
</div>
);
};
export const AvatarLayout = ({ list }: avatarLayoutProps): JSX.Element => {
return (
<div className="container">
<div className="flex flex-wrap justify-center bg-primary">
{list.map(() => {
return <Avatar />;
})}
</div>
</div>
);
return (
<div className="container">
<div className="flex flex-wrap justify-center bg-primary">
{list.map(() => {
return <Avatar />;
})}
</div>
</div>
);
};

View file

@ -5,161 +5,161 @@ import Link from "@docusaurus/Link";
import { AvatarLayout } from "./_components/Avatar";
const Credits = (): JSX.Element => {
const partnerLogos = [
{
name: "Vercel",
url: "/2.0/vercel-logotype-dark.svg"
},
{
name: "Cloudflare",
url: "/2.0/CF_logo_horizontal_blktype.svg"
},
{
name: "RAK Wireless",
url: "/2.0/RAK-blue-main.svg"
},
{
name: "Open Collective",
url: "/2.0/opencollectivelogo.svg"
},
{
name: "LILYGO",
url: "/2.0/LILYGO.png"
},
{
name: "Discord",
url: "/2.0/discord.svg"
}
];
return (
<Layout
title="Credits"
description="Meshtastic is made possible by the following people & organizations."
>
<main className="relative mt-20">
<div className="container mx-auto p-6 leading-normal space-y-4">
<h1>Credits</h1>
<p>
Meshtastic is community driven. Thousands of hours have been donated
by volunteers who want to develop this amazing project. Whether
you've submitted a pull request or triaged a bug in our
Discord/Forum. You've made Meshtastic possible. Thank you for your
contributions.
</p>
<p>
We would also like to recognize those who have donated financially
to the project. As Meshtastic has grown, we've aquired some ongoing
costs to keep the project running. Thank you for your generous
donations.
</p>
</div>
<div className="container mx-auto p-6 leading-normal space-y-4">
<h2>Fiscal Sponsors</h2>
<p>
We have partnered with both the{" "}
<a
className="underline"
href="https://opencollective.com"
target="_blank"
>
Open Collective
</a>{" "}
and the{" "}
<a
className="underline"
href="https://www.oscollective.org"
target="_blank"
>
Open Source Collective
</a>{" "}
to help us with a fiscal management framework and banking needs.
They help support over three thousand open source projects including
the PHP Foundation, F-Droid, Sonarr, LinuxServer and DarkReader. We
are in good hands and good company.
</p>
<p>
As with everything we do here, Open Collective provides a fully
transparent framework for our budget and expenses. You can see what
were bringing in, who is spending money and where that money is
going{" "}
<a
className="underline"
href="https://opencollective.com/meshtastic"
target="_blank"
>
here
</a>
.
</p>
<p>
In addition to our partnership with Open Collective and Open Source
Collective, we have also been approved into the{" "}
<a
className="underline"
href="https://github.com/sponsors"
target="_blank"
>
GitHub Sponsors
</a>{" "}
program where we can set fundraising goals with GitHub.
</p>
<p>
All donations made through GitHub will be deposited to our account
with the Open Source Collective and managed by the Open Collective.
This means we have a single place to monitor and maintain
transparency of our finances.
</p>
<p>If you are able, please contribute to this amazing project.</p>
<div className="indexCtasBody">
<Link
className={"button button--outline button--lg cta--button"}
to={"https://opencollective.com/meshtastic/donate"}
>
Sponsor Meshtastic
</Link>
</div>
<h3>
Open Collective Donations
{/*Open Collective Donations*/}
<AvatarLayout list={[]} />
</h3>
<h3>
GitHub Sponsor Donations
{/*GitHub Sponsor Donations*/}
<AvatarLayout list={[]} />
</h3>
</div>
<div className="container mx-auto p-6 leading-normal space-y-4">
<h2>Partnerships</h2>
<div className="mt-12 grid grid-cols-2 gap-0.5 md:grid-cols-3 lg:mt-0 lg:grid-cols-2">
{partnerLogos.map((logo) => (
<div
key={logo.name}
className="col-span-1 flex justify-center bg-gray-50 py-8 px-8"
>
<img className="max-h-12" src={logo.url} alt={logo.name} />
</div>
))}
</div>
</div>
<div className="container mx-auto p-6 leading-normal space-y-4">
<h2>Contributors</h2>
<p>
Literally thousands of hours have gone into creating, maintaining,
and improving Meshtastic. Without our contributors none of this
would be possible. Thank you for donating the time for each and
every commit, issue, and pull request.
</p>
{/*GitHub Organization Contributors*/}
<AvatarLayout list={[]} />
</div>
{/*Admin Bios*/}
<div className="container mx-auto p-6 leading-normal space-y-4">
<AvatarLayout list={[]} />
</div>
</main>
</Layout>
);
const partnerLogos = [
{
name: "Vercel",
url: "/2.0/vercel-logotype-dark.svg",
},
{
name: "Cloudflare",
url: "/2.0/CF_logo_horizontal_blktype.svg",
},
{
name: "RAK Wireless",
url: "/2.0/RAK-blue-main.svg",
},
{
name: "Open Collective",
url: "/2.0/opencollectivelogo.svg",
},
{
name: "LILYGO",
url: "/2.0/LILYGO.png",
},
{
name: "Discord",
url: "/2.0/discord.svg",
},
];
return (
<Layout
title="Credits"
description="Meshtastic is made possible by the following people & organizations."
>
<main className="relative mt-20">
<div className="container mx-auto p-6 leading-normal space-y-4">
<h1>Credits</h1>
<p>
Meshtastic is community driven. Thousands of hours have been donated
by volunteers who want to develop this amazing project. Whether
you've submitted a pull request or triaged a bug in our
Discord/Forum. You've made Meshtastic possible. Thank you for your
contributions.
</p>
<p>
We would also like to recognize those who have donated financially
to the project. As Meshtastic has grown, we've aquired some ongoing
costs to keep the project running. Thank you for your generous
donations.
</p>
</div>
<div className="container mx-auto p-6 leading-normal space-y-4">
<h2>Fiscal Sponsors</h2>
<p>
We have partnered with both the{" "}
<a
className="underline"
href="https://opencollective.com"
target="_blank"
>
Open Collective
</a>{" "}
and the{" "}
<a
className="underline"
href="https://www.oscollective.org"
target="_blank"
>
Open Source Collective
</a>{" "}
to help us with a fiscal management framework and banking needs.
They help support over three thousand open source projects including
the PHP Foundation, F-Droid, Sonarr, LinuxServer and DarkReader. We
are in good hands and good company.
</p>
<p>
As with everything we do here, Open Collective provides a fully
transparent framework for our budget and expenses. You can see what
were bringing in, who is spending money and where that money is
going{" "}
<a
className="underline"
href="https://opencollective.com/meshtastic"
target="_blank"
>
here
</a>
.
</p>
<p>
In addition to our partnership with Open Collective and Open Source
Collective, we have also been approved into the{" "}
<a
className="underline"
href="https://github.com/sponsors"
target="_blank"
>
GitHub Sponsors
</a>{" "}
program where we can set fundraising goals with GitHub.
</p>
<p>
All donations made through GitHub will be deposited to our account
with the Open Source Collective and managed by the Open Collective.
This means we have a single place to monitor and maintain
transparency of our finances.
</p>
<p>If you are able, please contribute to this amazing project.</p>
<div className="indexCtasBody">
<Link
className={"button button--outline button--lg cta--button"}
to={"https://opencollective.com/meshtastic/donate"}
>
Sponsor Meshtastic
</Link>
</div>
<h3>
Open Collective Donations
{/*Open Collective Donations*/}
<AvatarLayout list={[]} />
</h3>
<h3>
GitHub Sponsor Donations
{/*GitHub Sponsor Donations*/}
<AvatarLayout list={[]} />
</h3>
</div>
<div className="container mx-auto p-6 leading-normal space-y-4">
<h2>Partnerships</h2>
<div className="mt-12 grid grid-cols-2 gap-0.5 md:grid-cols-3 lg:mt-0 lg:grid-cols-2">
{partnerLogos.map((logo) => (
<div
key={logo.name}
className="col-span-1 flex justify-center bg-gray-50 py-8 px-8"
>
<img className="max-h-12" src={logo.url} alt={logo.name} />
</div>
))}
</div>
</div>
<div className="container mx-auto p-6 leading-normal space-y-4">
<h2>Contributors</h2>
<p>
Literally thousands of hours have gone into creating, maintaining,
and improving Meshtastic. Without our contributors none of this
would be possible. Thank you for donating the time for each and
every commit, issue, and pull request.
</p>
{/*GitHub Organization Contributors*/}
<AvatarLayout list={[]} />
</div>
{/*Admin Bios*/}
<div className="container mx-auto p-6 leading-normal space-y-4">
<AvatarLayout list={[]} />
</div>
</main>
</Layout>
);
};
export default Credits;

View file

@ -1,146 +1,146 @@
import React from "react";
export interface downloadCardProps {
client: string;
imgUrl: string;
url: string;
imgUrl2: string;
url2: string;
notes: string;
buttonText: string;
client: string;
imgUrl: string;
url: string;
imgUrl2: string;
url2: string;
notes: string;
buttonText: string;
}
export const DownloadCard = ({
client,
imgUrl,
url,
imgUrl2,
url2,
notes,
buttonText
client,
imgUrl,
url,
imgUrl2,
url2,
notes,
buttonText,
}: downloadCardProps): JSX.Element => {
return (
<div className="card">
<div
className="card__header"
style={{ display: "flex", justifyContent: "space-between" }}
>
<h3>{client}</h3>
</div>
<div
className="card__body"
style={{ display: "flex", justifyContent: "center" }}
>
{buttonText ? (
<a href={url} className="button button--secondary button--block">
{buttonText}
</a>
) : (
<div>
<a href={url}>
<img alt="img1" style={{ height: "4rem" }} src={imgUrl}></img>
</a>
<a href={url2}>
<img alt="img2" style={{ height: "4rem" }} src={imgUrl2}></img>
</a>
</div>
)}
</div>
<div className="card__footer">{notes ? notes : null}</div>
</div>
);
return (
<div className="card">
<div
className="card__header"
style={{ display: "flex", justifyContent: "space-between" }}
>
<h3>{client}</h3>
</div>
<div
className="card__body"
style={{ display: "flex", justifyContent: "center" }}
>
{buttonText ? (
<a href={url} className="button button--secondary button--block">
{buttonText}
</a>
) : (
<div>
<a href={url}>
<img alt="img1" style={{ height: "4rem" }} src={imgUrl} />
</a>
<a href={url2}>
<img alt="img2" style={{ height: "4rem" }} src={imgUrl2} />
</a>
</div>
)}
</div>
<div className="card__footer">{notes ? notes : null}</div>
</div>
);
};
export const PlaceholderCard = (): JSX.Element => {
return (
<div
className="card"
style={{
width: "100%",
animation: "pulse 2s infinite",
transform: "scale(1)",
display: "flex",
gap: "1rem",
padding: "1rem"
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "1rem"
}}
>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2rem",
width: "8rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
marginTop: "1rem",
height: "1rem",
width: "8rem"
}}
/>
</div>
<div
className="card__body"
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "3rem",
display: "flex",
jusifyContent: "center",
alignItems: "center"
}}
/>
<a className="button disabled button--primary button--block">&nbsp;</a>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "8rem",
height: "2rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "11rem",
height: "1rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "9rem",
height: "1rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "13rem",
height: "1rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "11rem",
height: "1rem"
}}
/>
</div>
);
return (
<div
className="card"
style={{
width: "100%",
animation: "pulse 2s infinite",
transform: "scale(1)",
display: "flex",
gap: "1rem",
padding: "1rem",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "1rem",
}}
>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2rem",
width: "8rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
marginTop: "1rem",
height: "1rem",
width: "8rem",
}}
/>
</div>
<div
className="card__body"
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "3rem",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
/>
<a className="button disabled button--primary button--block">&nbsp;</a>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "8rem",
height: "2rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "11rem",
height: "1rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "9rem",
height: "1rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "13rem",
height: "1rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "11rem",
height: "1rem",
}}
/>
</div>
);
};

View file

@ -1,151 +1,151 @@
import React from "react";
import { DeviceFirmwareResource } from "../../../utils/apiTypes.js";
import { DeviceFirmwareResource } from "../../../utils/apiTypes";
export interface releaseCardProps {
variant: string;
description: string;
release?: DeviceFirmwareResource[];
variant: string;
description: string;
release?: DeviceFirmwareResource[];
}
export const FirmwareCard = ({
variant,
description,
release
variant,
description,
release,
}: releaseCardProps): JSX.Element => {
return (
<div className="card m-4 border-2 border-secondary">
<div
className="card__header"
style={{ display: "flex", justifyContent: "space-between" }}
>
<h3>{variant}</h3>
{release?.length && (
<a href={release[0].page_url}>{release[0].title}</a>
)}
</div>
<div className="card__body">
<p>{description}</p>
</div>
<div className="card__footer mt-auto">
<div className="margin-top--sm">
<details>
<summary>Older Versions</summary>
{release.slice(1, 6).map((release) => {
return (
<div key={release.id}>
<a href={release.zip_url}>{release.title}</a>
</div>
);
})}
</details>
</div>
{release?.length ? (
<>
<a
href={release[0].zip_url}
className="button button--secondary button--block margin-top--sm"
>
Download {variant}
</a>
</>
) : (
<button disabled className="button button--secondary button--block">
Loading...
</button>
)}
</div>
</div>
);
return (
<div className="card m-4 border-2 border-secondary">
<div
className="card__header"
style={{ display: "flex", justifyContent: "space-between" }}
>
<h3>{variant}</h3>
{release?.length && (
<a href={release[0].page_url}>{release[0].title}</a>
)}
</div>
<div className="card__body">
<p>{description}</p>
</div>
<div className="card__footer mt-auto">
<div className="margin-top--sm">
<details>
<summary>Older Versions</summary>
{release.slice(1, 6).map((release) => {
return (
<div key={release.id}>
<a href={release.zip_url}>{release.title}</a>
</div>
);
})}
</details>
</div>
{release?.length ? (
<>
<a
href={release[0].zip_url}
className="button button--secondary button--block margin-top--sm"
>
Download {variant}
</a>
</>
) : (
<button disabled className="button button--secondary button--block">
Loading...
</button>
)}
</div>
</div>
);
};
export const PlaceholderFirmwareCard = (): JSX.Element => {
return (
<div
className="card"
style={{
width: "100%",
animation: "pulse 2s infinite",
transform: "scale(1)",
display: "flex",
gap: "1rem",
padding: "1rem"
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "1rem"
}}
>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2rem",
width: "8rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
marginTop: "1rem",
height: "1rem",
width: "8rem"
}}
/>
</div>
<div
className="card__body"
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "3rem"
}}
/>
<a className="button disabled button--primary button--block">&nbsp;</a>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "8rem",
height: "2rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "11rem",
height: "1rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "9rem",
height: "1rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "13rem",
height: "1rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "11rem",
height: "1rem"
}}
/>
</div>
);
return (
<div
className="card"
style={{
width: "100%",
animation: "pulse 2s infinite",
transform: "scale(1)",
display: "flex",
gap: "1rem",
padding: "1rem",
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "1rem",
}}
>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2rem",
width: "8rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
marginTop: "1rem",
height: "1rem",
width: "8rem",
}}
/>
</div>
<div
className="card__body"
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "3rem",
}}
/>
<a className="button disabled button--primary button--block">&nbsp;</a>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "8rem",
height: "2rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "11rem",
height: "1rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "9rem",
height: "1rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "13rem",
height: "1rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
width: "11rem",
height: "1rem",
}}
/>
</div>
);
};

View file

@ -1,18 +1,18 @@
import React from "react";
export const HeaderText = ({ type, text, link }): JSX.Element => {
const Header = type;
const Header = type;
return (
<Header className="anchor anchorWithHideOnScrollNavbar_node_modules-@docusaurus-theme-classic-lib-next-theme-Heading-styles-module">
{text}
{link && (
<a
className="hash-link"
href={`#${link}`}
title="Direct link to heading"
/>
)}
</Header>
);
return (
<Header className="anchor anchorWithHideOnScrollNavbar_node_modules-@docusaurus-theme-classic-lib-next-theme-Heading-styles-module">
{text}
{link && (
<a
className="hash-link"
href={`#${link}`}
title="Direct link to heading"
/>
)}
</Header>
);
};

View file

@ -4,236 +4,236 @@ import { FaAndroid, FaApple } from "react-icons/fa";
import useSWR from "swr";
import {
ArrowTopRightOnSquareIcon,
BoltIcon,
ComputerDesktopIcon,
CpuChipIcon,
GlobeAltIcon
ArrowTopRightOnSquareIcon,
BoltIcon,
ComputerDesktopIcon,
CpuChipIcon,
GlobeAltIcon,
} from "@heroicons/react/24/solid";
import Layout from "@theme/Layout";
import { FirmwareReleases } from "../../utils/apiTypes.js";
import { FirmwareReleases } from "../../utils/apiTypes";
import { fetcher } from "../../utils/swr";
import {
FirmwareCard,
PlaceholderFirmwareCard
FirmwareCard,
PlaceholderFirmwareCard,
} from "./_components/FirmwareCard";
const Firmware = (): JSX.Element => {
const { data, error } = useSWR<FirmwareReleases>(
"https://api.meshtastic.org/github/firmware/list",
fetcher
);
const { data, error } = useSWR<FirmwareReleases>(
"https://api.meshtastic.org/github/firmware/list",
fetcher,
);
return (
<Layout
title="Downloads"
description="Downloads for the Meshtastic project"
>
<div className="container mt-8 flex flex-col gap-3">
<h1 className="m-2">Flasher</h1>
<div className="flex w-full overflow-hidden rounded-xl">
<div className="flex w-1/5 bg-gradient-to-r from-green-500 to-primary">
<BoltIcon className="m-auto h-20" />
</div>
<div className="flex w-full flex-col bg-primary xl:flex-row">
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>Meshtastic Flasher</h3>
</div>
<div className="card__body">
<p>Desktop application to flash fimware to your devices.</p>
</div>
<div className="card__footer mt-auto">
<a
href="https://github.com/meshtastic/Meshtastic-gui-installer/releases/latest"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
Download
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
</div>
</div>
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>ESP32 Web Flasher</h3>
</div>
<div className="card__body">
<p>
Web based installer for easy flashing with Chrome and Edge
Browser. Works with T-Beam, T-Lora, Nano-G1 and similar
boards.
</p>
</div>
<div className="card__footer mt-auto">
<a
href="https://flasher.meshtastic.org/"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
Go to Flasher
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
</div>
</div>
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>nRF52 Drag & Drop</h3>
</div>
<div className="card__body">
<p>
Devices such as T-Echo and RAK4631 are flashed via filesystem.
</p>
</div>
<div className="card__footer mt-auto">
<a
href="/docs/getting-started/flashing-firmware/nrf52/drag-n-drop"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
View Instructions
</a>
</div>
</div>
</div>
</div>
{/* */}
<h1 className="m-2">Apps</h1>
<div className="flex w-full overflow-hidden rounded-xl">
<div className="flex w-1/5 bg-gradient-to-r from-rose-500 to-primary">
<ComputerDesktopIcon className="m-auto h-20" />
</div>
<div className="flex w-full columns-3 flex-col bg-primary lg:flex-row">
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>Apple</h3>
</div>
<div className="card__body flex items-center">
<div className="m-auto">
<FaApple className="h-20 w-20" />
</div>
</div>
<div className="card__body">
Available on MacOS & iOS. Requires MacOS Ventura or iOS 16+.
</div>
<div className="card__footer mt-auto">
<a
target="_blank"
rel="noopener noreferrer"
href="https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1586432531"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
App Store
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
</div>
</div>
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>Android</h3>
</div>
<div className="card__body flex items-center">
<div className="m-auto">
<FaAndroid className="h-20 w-20" />
</div>
</div>
<div className="card__body">Sideloading also available.</div>
<div className="card__footer mt-auto">
<a
target="_blank"
rel="noopener noreferrer"
href="https://meshtastic.org/docs/software/android/installation"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
F-Droid
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
<a
target="_blank"
rel="noopener noreferrer"
href="https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source=downloads-page"
className="mt-4 flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
Play Store
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
</div>
</div>
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>Web</h3>
</div>
<div className="card__body flex items-center">
<div className="m-auto">
<GlobeAltIcon className="h-20 w-20" />
</div>
</div>
<div className="card__body">
Requires Chromium based browsers.
</div>
<div className="card__footer mt-auto">
<a
target="_blank"
rel="noopener noreferrer"
href="https://client.meshtastic.org"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
client.meshtastic.org
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
</div>
</div>
</div>
</div>
{/* */}
<h1 className="m-2">Firmware</h1>
<div className="flex w-full overflow-hidden rounded-xl">
<div className="flex w-1/5 bg-gradient-to-r from-orange-500 to-primary">
<CpuChipIcon className="m-auto h-20" />
</div>
<div className="flex w-full flex-col bg-primary lg:flex-row">
{data && !error ? (
<>
<FirmwareCard
variant="Stable"
description="Tested feature set. For those who want stability."
release={data.releases.stable}
/>
<FirmwareCard
variant="Alpha"
description="Upcoming changes for testing. For those who want new features."
release={data.releases.alpha}
/>
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>Bleeding</h3>
</div>
<div className="card__body">
<p>
Latest successful CI build. For those who want to break
things.
</p>
</div>
<div className="card__footer mt-auto">
<a
href="https://nightly.link/meshtastic/firmware/workflows/main/master/built.zip"
className="button button--secondary button--block"
>
Download Bleeding
</a>
</div>
</div>
</>
) : (
<>
<PlaceholderFirmwareCard />
<PlaceholderFirmwareCard />
<PlaceholderFirmwareCard />
</>
)}
</div>
</div>
</div>
</Layout>
);
return (
<Layout
title="Downloads"
description="Downloads for the Meshtastic project"
>
<div className="container mt-8 flex flex-col gap-3">
<h1 className="m-2">Flasher</h1>
<div className="flex w-full overflow-hidden rounded-xl">
<div className="flex w-1/5 bg-gradient-to-r from-green-500 to-primary">
<BoltIcon className="m-auto h-20" />
</div>
<div className="flex w-full flex-col bg-primary xl:flex-row">
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>Meshtastic Flasher</h3>
</div>
<div className="card__body">
<p>Desktop application to flash fimware to your devices.</p>
</div>
<div className="card__footer mt-auto">
<a
href="https://github.com/meshtastic/Meshtastic-gui-installer/releases/latest"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
Download
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
</div>
</div>
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>ESP32 Web Flasher</h3>
</div>
<div className="card__body">
<p>
Web based installer for easy flashing with Chrome and Edge
Browser. Works with T-Beam, T-Lora, Nano-G1 and similar
boards.
</p>
</div>
<div className="card__footer mt-auto">
<a
href="https://flasher.meshtastic.org/"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
Go to Flasher
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
</div>
</div>
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>nRF52 Drag & Drop</h3>
</div>
<div className="card__body">
<p>
Devices such as T-Echo and RAK4631 are flashed via filesystem.
</p>
</div>
<div className="card__footer mt-auto">
<a
href="/docs/getting-started/flashing-firmware/nrf52/drag-n-drop"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
View Instructions
</a>
</div>
</div>
</div>
</div>
{/* */}
<h1 className="m-2">Apps</h1>
<div className="flex w-full overflow-hidden rounded-xl">
<div className="flex w-1/5 bg-gradient-to-r from-rose-500 to-primary">
<ComputerDesktopIcon className="m-auto h-20" />
</div>
<div className="flex w-full columns-3 flex-col bg-primary lg:flex-row">
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>Apple</h3>
</div>
<div className="card__body flex items-center">
<div className="m-auto">
<FaApple className="h-20 w-20" />
</div>
</div>
<div className="card__body">
Available on MacOS & iOS. Requires MacOS Ventura or iOS 16+.
</div>
<div className="card__footer mt-auto">
<a
target="_blank"
rel="noopener noreferrer"
href="https://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=1586432531"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
App Store
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
</div>
</div>
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>Android</h3>
</div>
<div className="card__body flex items-center">
<div className="m-auto">
<FaAndroid className="h-20 w-20" />
</div>
</div>
<div className="card__body">Sideloading also available.</div>
<div className="card__footer mt-auto">
<a
target="_blank"
rel="noopener noreferrer"
href="https://meshtastic.org/docs/software/android/installation"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
F-Droid
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
<a
target="_blank"
rel="noopener noreferrer"
href="https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source=downloads-page"
className="mt-4 flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
Play Store
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
</div>
</div>
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>Web</h3>
</div>
<div className="card__body flex items-center">
<div className="m-auto">
<GlobeAltIcon className="h-20 w-20" />
</div>
</div>
<div className="card__body">
Requires Chromium based browsers.
</div>
<div className="card__footer mt-auto">
<a
target="_blank"
rel="noopener noreferrer"
href="https://client.meshtastic.org"
className="m-auto flex rounded-lg border-4 border-transparent bg-accent p-1 font-semibold text-black shadow-md hover:text-black hover:brightness-110 active:border-green-200"
>
client.meshtastic.org
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
</a>
</div>
</div>
</div>
</div>
{/* */}
<h1 className="m-2">Firmware</h1>
<div className="flex w-full overflow-hidden rounded-xl">
<div className="flex w-1/5 bg-gradient-to-r from-orange-500 to-primary">
<CpuChipIcon className="m-auto h-20" />
</div>
<div className="flex w-full flex-col bg-primary lg:flex-row">
{data && !error ? (
<>
<FirmwareCard
variant="Stable"
description="Tested feature set. For those who want stability."
release={data.releases.stable}
/>
<FirmwareCard
variant="Alpha"
description="Upcoming changes for testing. For those who want new features."
release={data.releases.alpha}
/>
<div className="card m-4 border-2 border-secondary">
<div className="card__header">
<h3>Bleeding</h3>
</div>
<div className="card__body">
<p>
Latest successful CI build. For those who want to break
things.
</p>
</div>
<div className="card__footer mt-auto">
<a
href="https://nightly.link/meshtastic/firmware/workflows/main/master/built.zip"
className="button button--secondary button--block"
>
Download Bleeding
</a>
</div>
</div>
</>
) : (
<>
<PlaceholderFirmwareCard />
<PlaceholderFirmwareCard />
<PlaceholderFirmwareCard />
</>
)}
</div>
</div>
</div>
</Layout>
);
};
export default Firmware;

View file

@ -2,8 +2,8 @@ import React, { useState } from "react";
import { FiPlus } from "react-icons/fi";
import { HardwareModal } from "@site/src/components/hardware/HardwareModal";
import { IDevice } from "@site/src/data/device";
import { HardwareModal } from "../../components/hardware/HardwareModal";
import { IDevice } from "../../data/device";
import { HardwareCard } from "../../components/hardware/HardwareCard";
import { PageLayout } from "../../components/PageLayout";
@ -16,79 +16,79 @@ import { tbeam } from "../../data/devices/tbeam";
import { techo } from "../../data/devices/techo";
const Hardware = (): JSX.Element => {
const hardware = [tbeam, hydra, rak19003, rak19001, nano_g1, heltec, techo];
const [modalData, setModalData] = useState<IDevice>();
const hardware = [tbeam, hydra, rak19003, rak19001, nano_g1, heltec, techo];
const [modalData, setModalData] = useState<IDevice>();
return (
<PageLayout title="Hardware" description="Supported hardware">
<div className="border-b border-tertiary p-4">
<div className="sm:flex sm:items-baseline">
<h3 className="text-lg font-medium leading-6 text-gray-900">
Issues
</h3>
<div className="mt-4 sm:mt-0 sm:ml-10">
<nav className="-mb-px flex space-x-8">
<a
href="#"
className="border-indigo-500 text-indigo-600"
aria-current={"page"}
>
Devices
</a>
<a
href="#"
className="hover:border-gray-300', 'whitespace-nowrap border-b-2 border-transparent
return (
<PageLayout title="Hardware" description="Supported hardware">
<div className="border-b border-tertiary p-4">
<div className="sm:flex sm:items-baseline">
<h3 className="text-lg font-medium leading-6 text-gray-900">
Issues
</h3>
<div className="mt-4 sm:mt-0 sm:ml-10">
<nav className="-mb-px flex space-x-8">
<a
href="#"
className="border-indigo-500 text-indigo-600"
aria-current={"page"}
>
Devices
</a>
<a
href="#"
className="hover:border-gray-300', 'whitespace-nowrap border-b-2 border-transparent
px-1 pb-4 text-sm font-medium text-gray-500 hover:text-gray-700"
>
Antennas
</a>
</nav>
</div>
</div>
</div>
<div className="mx-auto max-w-7xl py-8 px-4 sm:px-6 lg:px-8">
<ul
role="list"
className="grid grid-cols-2 gap-x-2 gap-y-4 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:grid-cols-5 xl:gap-x-4"
>
{hardware.map((device, index) => (
<HardwareCard
key={index}
device={device}
setDevice={(): void => {
setModalData(device);
}}
/>
))}
<li className="group relative">
<a
href="https://github.com/meshtastic/firmware/issues/new?assignees=&labels=enhancement%2Ctriage&template=New+Board.yml&title=%5BBoard%5D%3A+"
className="flex aspect-[4/3] rounded-lg border-2 border-dashed border-mute group-hover:border-tertiaryInv"
target="_blank"
rel="noreferrer"
>
<FiPlus className="m-auto h-12 w-12 text-mute group-hover:text-tertiaryInv" />
</a>
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-primaryInv">
New Board
</p>
<p className="pointer-events-none block text-sm font-medium text-mute">
Want to support a board?
</p>
</li>
</ul>
</div>
{modalData && (
<HardwareModal
open={!!modalData}
close={() => {
setModalData(undefined);
}}
device={modalData}
/>
)}
</PageLayout>
);
>
Antennas
</a>
</nav>
</div>
</div>
</div>
<div className="mx-auto max-w-7xl py-8 px-4 sm:px-6 lg:px-8">
<ul
role="list"
className="grid grid-cols-2 gap-x-2 gap-y-4 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:grid-cols-5 xl:gap-x-4"
>
{hardware.map((device, index) => (
<HardwareCard
key={index}
device={device}
setDevice={(): void => {
setModalData(device);
}}
/>
))}
<li className="group relative">
<a
href="https://github.com/meshtastic/firmware/issues/new?assignees=&labels=enhancement%2Ctriage&template=New+Board.yml&title=%5BBoard%5D%3A+"
className="flex aspect-[4/3] rounded-lg border-2 border-dashed border-mute group-hover:border-tertiaryInv"
target="_blank"
rel="noreferrer"
>
<FiPlus className="m-auto h-12 w-12 text-mute group-hover:text-tertiaryInv" />
</a>
<p className="pointer-events-none mt-2 block truncate text-sm font-medium text-primaryInv">
New Board
</p>
<p className="pointer-events-none block text-sm font-medium text-mute">
Want to support a board?
</p>
</li>
</ul>
</div>
{modalData && (
<HardwareModal
open={!!modalData}
close={() => {
setModalData(undefined);
}}
device={modalData}
/>
)}
</PageLayout>
);
};
export default Hardware;

View file

@ -13,299 +13,299 @@ import Layout from "@theme/Layout";
import { SocialCard, SocialCardProps } from "../components/homepage/SocialCard";
const features = [
{
title: "Radio Mesh Text Messaging",
imageUrl: "img/homepage/messages.svg",
description: (
<>
Off-grid messaging using inexpensive hardware to create your personal
mesh. Radios forward messages to the next to flood the network.
Communicate kilometers/miles between nodes. Internet-connected relay
nodes enable the conversation to move online too.
</>
)
},
{
title: "Encryption",
imageUrl: "img/homepage/encryption.svg",
description: (
<>
Messages are AES256 encrypted. Only radios supplied with your channel
settings (which includes the key) should be able to read your messages.
Using multichannel settings you can send encrypted messages on one
channel and still participate in a default Meshtastic mesh.
</>
)
},
{
title: "Conserve Battery",
imageUrl: "img/homepage/battery.svg",
description: (
<>
Go for days on end and on a single battery or extend it infinitely with
a solar cell. Power management ensures the device will last the duration
of your use.
</>
)
},
{
title: "Extensible",
imageUrl: "img/homepage/extendable.svg",
description: (
<>
Create a highly scalable mesh with hardware on a multitude of platforms
to fit your unique requirements: Create an environment monitoring mesh
and produce real-time heatmaps, or maybe decentralized, encrypted
messaging network, your imagination is the limit.
</>
)
},
{
title: "Platform Agnostic",
imageUrl: "img/homepage/platforms.svg",
description: (
<>
Meshtastic clients are built or being built for all major desktop and
mobile platforms. Linux, Windows, Mac, Android, and iOS are all
supported or well on their way to being supported.
</>
)
},
{
title: "Open Source",
imageUrl: "img/homepage/opensource.svg",
description: (
<>
All Meshtastic software is open source. If you want an improvement,
submit a pull request or file an issue on Github. Happy coding!
</>
)
}
{
title: "Radio Mesh Text Messaging",
imageUrl: "img/homepage/messages.svg",
description: (
<>
Off-grid messaging using inexpensive hardware to create your personal
mesh. Radios forward messages to the next to flood the network.
Communicate kilometers/miles between nodes. Internet-connected relay
nodes enable the conversation to move online too.
</>
),
},
{
title: "Encryption",
imageUrl: "img/homepage/encryption.svg",
description: (
<>
Messages are AES256 encrypted. Only radios supplied with your channel
settings (which includes the key) should be able to read your messages.
Using multichannel settings you can send encrypted messages on one
channel and still participate in a default Meshtastic mesh.
</>
),
},
{
title: "Conserve Battery",
imageUrl: "img/homepage/battery.svg",
description: (
<>
Go for days on end and on a single battery or extend it infinitely with
a solar cell. Power management ensures the device will last the duration
of your use.
</>
),
},
{
title: "Extensible",
imageUrl: "img/homepage/extendable.svg",
description: (
<>
Create a highly scalable mesh with hardware on a multitude of platforms
to fit your unique requirements: Create an environment monitoring mesh
and produce real-time heatmaps, or maybe decentralized, encrypted
messaging network, your imagination is the limit.
</>
),
},
{
title: "Platform Agnostic",
imageUrl: "img/homepage/platforms.svg",
description: (
<>
Meshtastic clients are built or being built for all major desktop and
mobile platforms. Linux, Windows, Mac, Android, and iOS are all
supported or well on their way to being supported.
</>
),
},
{
title: "Open Source",
imageUrl: "img/homepage/opensource.svg",
description: (
<>
All Meshtastic software is open source. If you want an improvement,
submit a pull request or file an issue on Github. Happy coding!
</>
),
},
];
const SocialCards: SocialCardProps[] = [
{
color: "bg-[#5865F2]",
link: "https://discord.com/invite/ktMAKGBnBs",
children: (
<img
alt="discord"
className="m-auto h-10"
src="/img/homepage/Discord-Logo-White.svg"
/>
)
},
{
color: "bg-[#ffffff]",
link: "https://twitter.com/TheMeshtastic",
children: (
<img
alt="twitter"
className="m-auto h-10"
src="/img/homepage/Twitter-logo.svg"
/>
)
},
{
color: "bg-[#FF0000]",
link: "https://www.youtube.com/meshtastic",
children: (
<img
alt="youtube"
className="m-auto h-16"
src="/img/homepage/YouTube-Logo-White.svg"
/>
)
},
{
color: "bg-[#ffffff]",
link: "https://meshtastic.discourse.group",
children: (
<img
alt="discourse"
className="m-auto h-12"
src="/img/homepage/Discourse-Logo-White.svg"
/>
)
},
{
color: "bg-[#FF4500]",
link: "https://reddit.com/r/meshtastic",
children: (
<img
alt="reddit"
className="m-auto h-20"
src="/img/homepage/Reddit-Logo-White.svg"
/>
)
},
{
color: "bg-[#ffffff]",
link: "https://github.com/meshtastic",
children: (
<img
alt="github"
className="m-auto w-12"
src="/img/homepage/GitHub-Logo-White.svg"
/>
)
}
{
color: "bg-[#5865F2]",
link: "https://discord.com/invite/ktMAKGBnBs",
children: (
<img
alt="discord"
className="m-auto h-10"
src="/img/homepage/Discord-Logo-White.svg"
/>
),
},
{
color: "bg-[#ffffff]",
link: "https://twitter.com/TheMeshtastic",
children: (
<img
alt="twitter"
className="m-auto h-10"
src="/img/homepage/Twitter-logo.svg"
/>
),
},
{
color: "bg-[#FF0000]",
link: "https://www.youtube.com/meshtastic",
children: (
<img
alt="youtube"
className="m-auto h-16"
src="/img/homepage/YouTube-Logo-White.svg"
/>
),
},
{
color: "bg-[#ffffff]",
link: "https://meshtastic.discourse.group",
children: (
<img
alt="discourse"
className="m-auto h-12"
src="/img/homepage/Discourse-Logo-White.svg"
/>
),
},
{
color: "bg-[#FF4500]",
link: "https://reddit.com/r/meshtastic",
children: (
<img
alt="reddit"
className="m-auto h-20"
src="/img/homepage/Reddit-Logo-White.svg"
/>
),
},
{
color: "bg-[#ffffff]",
link: "https://github.com/meshtastic",
children: (
<img
alt="github"
className="m-auto w-12"
src="/img/homepage/GitHub-Logo-White.svg"
/>
),
},
];
function Home() {
const context = useDocusaurusContext();
const { siteConfig } = context;
return (
<Layout>
<Head>
<meta property="og:title" content="Meshtastic" />
<meta
property="og:image"
content={useBaseUrl("design/web/social-preview-1200x630.png")}
/>
<meta
property="og:description"
content="An open source, off-grid, decentralized, mesh network built to run on affordable, low-power devices"
/>
<meta property="og:url" content="https://meshtastic.org/" />
<meta name="twitter:card" content="summary_large_image" />
</Head>
<header style={{ textAlign: "center" }} className="hero hero--primary">
<div className="container">
<h1 className="hero__title">
<img
style={{ paddingTop: "2rem", paddingBottom: "2rem" }}
alt="Meshtastic Logo"
className="header__logo"
src={useBaseUrl("design/typelogo/typelogo.svg")}
/>
</h1>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className="indexCtas">
<Link className="button button--lg" to="/docs/introduction">
Learn More
</Link>
<Link className="button button--lg" to="/docs/getting-started">
Get Started
</Link>
</div>
</div>
</header>
<main className="flex flex-col gap-4">
<Carousel autoPlay infiniteLoop showStatus={false} showThumbs={false}>
{features.map((feature, index) => (
<div key={index} className="flex p-12">
<div className="w-1/2">
<img
className="my-auto h-40"
src={feature.imageUrl}
alt={feature.title}
/>
</div>
<div className="my-auto w-1/2">
<h3 className="text-xl font-medium">{feature.title}</h3>
<p>{feature.description}</p>
</div>
</div>
))}
</Carousel>
const context = useDocusaurusContext();
const { siteConfig } = context;
return (
<Layout>
<Head>
<meta property="og:title" content="Meshtastic" />
<meta
property="og:image"
content={useBaseUrl("design/web/social-preview-1200x630.png")}
/>
<meta
property="og:description"
content="An open source, off-grid, decentralized, mesh network built to run on affordable, low-power devices"
/>
<meta property="og:url" content="https://meshtastic.org/" />
<meta name="twitter:card" content="summary_large_image" />
</Head>
<header style={{ textAlign: "center" }} className="hero hero--primary">
<div className="container">
<h1 className="hero__title">
<img
style={{ paddingTop: "2rem", paddingBottom: "2rem" }}
alt="Meshtastic Logo"
className="header__logo"
src={useBaseUrl("design/typelogo/typelogo.svg")}
/>
</h1>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className="indexCtas">
<Link className="button button--lg" to="/docs/introduction">
Learn More
</Link>
<Link className="button button--lg" to="/docs/getting-started">
Get Started
</Link>
</div>
</div>
</header>
<main className="flex flex-col gap-4">
<Carousel autoPlay infiniteLoop showStatus={false} showThumbs={false}>
{features.map((feature) => (
<div key={feature.title} className="flex p-12">
<div className="w-1/2">
<img
className="my-auto h-40"
src={feature.imageUrl}
alt={feature.title}
/>
</div>
<div className="my-auto w-1/2">
<h3 className="text-xl font-medium">{feature.title}</h3>
<p>{feature.description}</p>
</div>
</div>
))}
</Carousel>
<div className="bg-primaryDark mx-auto flex w-full lg:w-auto flex-col gap-4 p-4 shadow-inner">
<h3 className="text-xl font-bold">Connect with us.</h3>
<div className="flex w-full overflow-x-auto">
{SocialCards.map((card, index) => (
<SocialCard key={index} color={card.color} link={card.link}>
{card.children}
</SocialCard>
))}
</div>
</div>
<div className="bg-primaryDark mx-auto flex w-full lg:w-auto flex-col gap-4 p-4 shadow-inner">
<h3 className="text-xl font-bold">Connect with us.</h3>
<div className="flex w-full overflow-x-auto">
{SocialCards.map((card) => (
<SocialCard key={card.link} color={card.color} link={card.link}>
{card.children}
</SocialCard>
))}
</div>
</div>
<div className="container mx-auto flex w-auto flex-col">
<h2 className="mb-2 text-xl font-medium">
Getting started with Meshtastic is as easy as 1, 2, 3!
</h2>
<ul
className="mx-auto"
style={{
position: "relative",
display: "grid",
gap: "1.5rem",
gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
paddingLeft: "0"
}}
>
<div className="card">
<div
className="card__header"
style={{ display: "flex", justifyContent: "space-between" }}
>
<h3>1. Purchase Supported Hardware</h3>
</div>
<div
className="card__body"
style={{ display: "flex", justifyContent: "center" }}
>
<p>
Hardware you will want to consider:
<ul>
<li>Radio</li>
<li>Battery</li>
<li>Case</li>
<li>
Antenna (most devices include an antenna, but the quality
can be a bit of a mixed bag from some suppliers on stock
antennas)
</li>
</ul>
</p>
</div>
</div>
<div className="card">
<div
className="card__header"
style={{ display: "flex", justifyContent: "space-between" }}
>
<h3>2. Flash & Configure Node</h3>
</div>
<div
className="card__body"
style={{ display: "flex", justifyContent: "center" }}
>
<p>
The Meshtastic Flasher application can assist you in flashing
the firmware and configuring settings.
</p>
</div>
</div>
<div className="card">
<div
className="card__header"
style={{ display: "flex", justifyContent: "space-between" }}
>
<h3>3. Connect to Node</h3>
</div>
<div
className="card__body"
style={{ display: "flex", justifyContent: "center" }}
>
<p>
Applications are available for the following systems:
<ul>
<li>Android</li>
<li>iOS</li>
<li>Mac</li>
<li>Web Browser</li>
</ul>
</p>
</div>
</div>
</ul>
</div>
<br />
</main>
</Layout>
);
<div className="container mx-auto flex w-auto flex-col">
<h2 className="mb-2 text-xl font-medium">
Getting started with Meshtastic is as easy as 1, 2, 3!
</h2>
<ul
className="mx-auto"
style={{
position: "relative",
display: "grid",
gap: "1.5rem",
gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
paddingLeft: "0",
}}
>
<div className="card">
<div
className="card__header"
style={{ display: "flex", justifyContent: "space-between" }}
>
<h3>1. Purchase Supported Hardware</h3>
</div>
<div
className="card__body"
style={{ display: "flex", justifyContent: "center" }}
>
<p>
Hardware you will want to consider:
<ul>
<li>Radio</li>
<li>Battery</li>
<li>Case</li>
<li>
Antenna (most devices include an antenna, but the quality
can be a bit of a mixed bag from some suppliers on stock
antennas)
</li>
</ul>
</p>
</div>
</div>
<div className="card">
<div
className="card__header"
style={{ display: "flex", justifyContent: "space-between" }}
>
<h3>2. Flash & Configure Node</h3>
</div>
<div
className="card__body"
style={{ display: "flex", justifyContent: "center" }}
>
<p>
The Meshtastic Flasher application can assist you in flashing
the firmware and configuring settings.
</p>
</div>
</div>
<div className="card">
<div
className="card__header"
style={{ display: "flex", justifyContent: "space-between" }}
>
<h3>3. Connect to Node</h3>
</div>
<div
className="card__body"
style={{ display: "flex", justifyContent: "center" }}
>
<p>
Applications are available for the following systems:
<ul>
<li>Android</li>
<li>iOS</li>
<li>Mac</li>
<li>Web Browser</li>
</ul>
</p>
</div>
</div>
</ul>
</div>
<br />
</main>
</Layout>
);
}
export default Home;

View file

@ -5,106 +5,106 @@ import { mapUrl } from "../../../utils/map";
import { CardTags } from "./CardTags";
export interface CardProps {
network: Showcase;
network: Showcase;
}
export const Card = React.memo(({ network }: CardProps) => (
<div className="card">
<div className="card__image">
<div style={{ height: "140px" }}>
<img img={mapUrl(network.nodes ?? [])} alt={network.title} />
</div>
</div>
<div className="card__body">
<h4>{network.title}</h4>
<small>{network.summary}</small>
</div>
<div className="card__footer">
<a
href={`?id=${network.id}`}
className="button button--primary button--block"
style={{ marginBottom: "0.5rem" }}
>
Read more
</a>
<CardTags tags={network.tags} />
</div>
</div>
<div className="card">
<div className="card__image">
<div style={{ height: "140px" }}>
<img img={mapUrl(network.nodes ?? [])} alt={network.title} />
</div>
</div>
<div className="card__body">
<h4>{network.title}</h4>
<small>{network.summary}</small>
</div>
<div className="card__footer">
<a
href={`?id=${network.id}`}
className="button button--primary button--block"
style={{ marginBottom: "0.5rem" }}
>
Read more
</a>
<CardTags tags={network.tags} />
</div>
</div>
));
export const PlaceholderCard = (): JSX.Element => (
<div
className="card"
style={{
animation: "pulse 2s infinite",
transform: "scale(1)"
}}
>
<div className="card__image">
<div
style={{
height: "140px"
}}
/>
</div>
<div className="card__body">
<div
style={{
width: "30%",
height: "2rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
marginBottom: "1rem"
}}
/>
<div
style={{
width: "100%",
height: "1rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
marginBottom: "0.5rem"
}}
/>
<div
style={{
width: "100%",
height: "1rem",
borderRadius: "0.4rem",
backgroundColor: "gray"
}}
/>
</div>
<div className="card__footer">
<a
className="button disabled button--primary button--block"
style={{ marginBottom: "0.5rem" }}
>
&nbsp;
</a>
<div
style={{
display: "flex",
gap: "0.5rem"
}}
>
<div
style={{
width: "4rem",
height: "1.5rem",
borderRadius: "0.4rem",
backgroundColor: "gray"
}}
/>
<div
style={{
width: "4rem",
height: "1.5rem",
borderRadius: "0.4rem",
backgroundColor: "gray"
}}
/>
</div>
</div>
</div>
<div
className="card"
style={{
animation: "pulse 2s infinite",
transform: "scale(1)",
}}
>
<div className="card__image">
<div
style={{
height: "140px",
}}
/>
</div>
<div className="card__body">
<div
style={{
width: "30%",
height: "2rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
marginBottom: "1rem",
}}
/>
<div
style={{
width: "100%",
height: "1rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
marginBottom: "0.5rem",
}}
/>
<div
style={{
width: "100%",
height: "1rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
</div>
<div className="card__footer">
<a
className="button disabled button--primary button--block"
style={{ marginBottom: "0.5rem" }}
>
&nbsp;
</a>
<div
style={{
display: "flex",
gap: "0.5rem",
}}
>
<div
style={{
width: "4rem",
height: "1.5rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
<div
style={{
width: "4rem",
height: "1.5rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
</div>
</div>
</div>
);

View file

@ -3,27 +3,27 @@ import React from "react";
import { ShowcaseTag } from "../../../utils/apiTypes";
export interface CardTagsProps {
tags: ShowcaseTag[];
tags: ShowcaseTag[];
}
export const CardTags = ({ tags }: CardTagsProps) => {
return (
<div>
{tags.map(({ color, label }, index) => {
return (
<span
className="badge"
key={index}
style={{
backgroundColor: color,
marginRight: "0.3rem",
userSelect: "none"
}}
>
{label}
</span>
);
})}
</div>
);
return (
<div>
{tags.map(({ color, label }, index) => {
return (
<span
className="badge"
key={index}
style={{
backgroundColor: color,
marginRight: "0.3rem",
userSelect: "none",
}}
>
{label}
</span>
);
})}
</div>
);
};

View file

@ -4,87 +4,87 @@ import { FiHeart } from "react-icons/fi";
import useSWR from "swr";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { fetcher } from "@site/src/utils/swr";
import { fetcher } from "../../../utils/swr";
import { ShowcaseTag } from "../../../utils/apiTypes";
// import { TagList, Tags } from '../../../utils/showcase';
import { PlaceholderTagSelect, TagSelect } from "./TagSelect";
export const Filters = (): JSX.Element => {
const { siteConfig } = useDocusaurusContext();
const { siteConfig } = useDocusaurusContext();
const { data, error } = useSWR<ShowcaseTag[]>(
`${siteConfig.customFields.API_URL}/showcase/tags`,
fetcher
);
const { data, error } = useSWR<ShowcaseTag[]>(
`${siteConfig.customFields.API_URL}/showcase/tags`,
fetcher,
);
return (
<section className="margin-top--l margin-bottom--lg container">
{data && !error ? (
<ul
style={{
padding: "0",
display: "flex",
alignItems: "center",
flexWrap: "wrap"
}}
>
{data.map((tag, i) => {
const { label, color } = tag;
const id = `showcase_checkbox_id_${tag};`;
return (
<section className="margin-top--l margin-bottom--lg container">
{data && !error ? (
<ul
style={{
padding: "0",
display: "flex",
alignItems: "center",
flexWrap: "wrap",
}}
>
{data.map((tag, i) => {
const { label, color } = tag;
const id = `showcase_checkbox_id_${tag};`;
return (
<div
key={i}
style={{
boxSizing: "border-box",
position: "relative",
display: "inline-flex",
alignItems: "center",
height: "2rem",
marginTop: "0.5rem",
marginRight: "0.5rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
verticalAlign: "middle",
userSelect: "none"
}}
>
<TagSelect
tag={tag}
id={id}
label={label}
icon={
tag.label === "Favorite" ? (
<span
style={{
display: "flex",
marginLeft: "0.5rem",
color: "rgb(190 24 93)"
}}
>
<FiHeart />
</span>
) : (
<span
style={{
backgroundColor: color,
width: 10,
height: 10,
borderRadius: "50%",
marginLeft: 8
}}
/>
)
}
/>
</div>
);
})}
</ul>
) : (
<PlaceholderTagSelect />
)}
</section>
);
return (
<div
key={i}
style={{
boxSizing: "border-box",
position: "relative",
display: "inline-flex",
alignItems: "center",
height: "2rem",
marginTop: "0.5rem",
marginRight: "0.5rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
verticalAlign: "middle",
userSelect: "none",
}}
>
<TagSelect
tag={tag}
id={id}
label={label}
icon={
tag.label === "Favorite" ? (
<span
style={{
display: "flex",
marginLeft: "0.5rem",
color: "rgb(190 24 93)",
}}
>
<FiHeart />
</span>
) : (
<span
style={{
backgroundColor: color,
width: 10,
height: 10,
borderRadius: "50%",
marginLeft: 8,
}}
/>
)
}
/>
</div>
);
})}
</ul>
) : (
<PlaceholderTagSelect />
)}
</section>
);
};

View file

@ -3,317 +3,316 @@ import React from "react";
import useSWR from "swr";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { Showcase } from "@site/src/utils/apiTypes";
import { User } from "@site/src/utils/github";
import { fetcher } from "@site/src/utils/swr";
import { Showcase } from "../../../utils/apiTypes";
import { fetcher } from "../../../utils/swr";
interface NetworkProps {
id: string;
id: string;
}
export const Network = ({ id }: NetworkProps): JSX.Element => {
const { siteConfig } = useDocusaurusContext();
const { siteConfig } = useDocusaurusContext();
const { data, error } = useSWR<Showcase>(
`${siteConfig.customFields.API_URL}/showcase/${id}`,
fetcher
);
const { data, error } = useSWR<Showcase>(
`${siteConfig.customFields.API_URL}/showcase/${id}`,
fetcher,
);
const githubData = useSWR<User>(
`https://api.github.com/users/${data?.author?.githubUsername}`,
fetcher
).data;
const githubData = useSWR(
`https://api.github.com/users/${data?.author?.githubUsername}`,
fetcher,
).data;
return (
<div>
{data && !error ? (
<div className="container">
<h1>{data.title}</h1>
<p>{data.summary}</p>
{githubData && (
<div className="avatar">
<img
src={githubData.avatar_url}
alt={githubData.name}
className="avatar__photo"
/>
<div className="avatar__intro">
<div className="avatar__name">{githubData.name}</div>
<div className="avatar__subtitle">{githubData.bio}</div>
</div>
</div>
)}
<div className="markdown">{data.body}</div>
return (
<div>
{data && !error ? (
<div className="container">
<h1>{data.title}</h1>
<p>{data.summary}</p>
{githubData && (
<div className="avatar">
<img
src={githubData.avatar_url}
alt={githubData.name}
className="avatar__photo"
/>
<div className="avatar__intro">
<div className="avatar__name">{githubData.name}</div>
<div className="avatar__subtitle">{githubData.bio}</div>
</div>
</div>
)}
<div className="markdown">{data.body}</div>
<div
className="card"
style={{
marginLeft: "auto",
marginRight: "auto",
maxWidth: "900px"
}}
>
<div
className="card__header"
style={{
margin: "8px"
}}
>
<h2>Bill of Materials</h2>
</div>
<div className="card__body">
{data.materials?.map((material, index) => (
<div
key={index}
style={{
borderTop: "2px solid gray",
display: "flex"
}}
>
<div
style={{
width: "4rem",
display: "flex"
}}
>
<img
src={material.image}
height="auto"
width="100%"
style={{
margin: "auto",
padding: "4px",
display: "block",
maxWidth: "60px",
maxHeight: "60px",
width: "auto",
height: "auto"
}}
/>
</div>
<div className="avatar__intro">
<div className="avatar__name">{material.name}</div>
<small className="avatar__subtitle">
{material.details}
</small>
</div>
<a
target="_blank"
rel="noreferrer"
href={material.url}
className="button button--outline button--secondary"
style={{
marginTop: "auto",
marginBottom: "auto"
}}
>
View
</a>
</div>
))}
</div>
</div>
</div>
) : (
<div>
{error && <div>{JSON.stringify(error)}</div>}
{!data && <PlaceholderNetwork />}
</div>
)}
</div>
);
<div
className="card"
style={{
marginLeft: "auto",
marginRight: "auto",
maxWidth: "900px",
}}
>
<div
className="card__header"
style={{
margin: "8px",
}}
>
<h2>Bill of Materials</h2>
</div>
<div className="card__body">
{data.materials?.map((material, index) => (
<div
key={index}
style={{
borderTop: "2px solid gray",
display: "flex",
}}
>
<div
style={{
width: "4rem",
display: "flex",
}}
>
<img
src={material.image}
height="auto"
width="100%"
style={{
margin: "auto",
padding: "4px",
display: "block",
maxWidth: "60px",
maxHeight: "60px",
width: "auto",
height: "auto",
}}
/>
</div>
<div className="avatar__intro">
<div className="avatar__name">{material.name}</div>
<small className="avatar__subtitle">
{material.details}
</small>
</div>
<a
target="_blank"
rel="noreferrer"
href={material.url}
className="button button--outline button--secondary"
style={{
marginTop: "auto",
marginBottom: "auto",
}}
>
View
</a>
</div>
))}
</div>
</div>
</div>
) : (
<div>
{error && <div>{JSON.stringify(error)}</div>}
{!data && <PlaceholderNetwork />}
</div>
)}
</div>
);
};
export const PlaceholderNetwork = (): JSX.Element => {
return (
<div
className="container"
style={{
display: "flex",
flexDirection: window.innerWidth > 768 ? "row" : "column",
gap: "2rem"
}}
>
<div
style={{
width: window.innerWidth > 768 ? "60%" : "100%"
}}
>
<div
className="card"
style={{
width: "100%",
animation: "pulse 2s infinite",
transform: "scale(1)",
display: "flex",
flexDirection: "column",
gap: "2rem",
padding: "2rem"
}}
>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "4rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "12rem"
}}
/>
<div style={{ display: "flex", gap: "1rem" }}>
<div
style={{
borderRadius: "999px",
backgroundColor: "gray",
height: "4rem",
width: "4rem",
minWidth: "4rem"
}}
/>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "1rem",
width: "100%"
}}
>
<div
style={{
width: "100%",
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "1rem"
}}
/>
<div
style={{
width: "100%",
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2rem"
}}
/>
</div>
</div>
</div>
</div>
<div
style={{
width: window.innerWidth > 768 ? "40%" : "100%"
}}
>
<div
className="card"
style={{
width: "100%",
animation: "pulse 2s infinite",
transform: "scale(1)",
display: "flex",
flexDirection: "column",
gap: "2rem",
padding: "2rem"
}}
>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "12rem"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2rem"
}}
/>
<div style={{ display: "flex", gap: "0.5rem" }}>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray"
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray"
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray"
}}
/>
</div>
<div
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
<div style={{ display: "flex", gap: "1rem" }}>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "20%"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "60%"
}}
/>
<a
className="button disabled button--primary button--block"
style={{ width: "20%" }}
>
&nbsp;
</a>
</div>
<div style={{ display: "flex", gap: "1rem" }}>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "20%"
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "60%"
}}
/>
<a
className="button disabled button--primary button--block"
style={{ width: "20%" }}
>
&nbsp;
</a>
</div>
</div>
</div>
</div>
</div>
);
return (
<div
className="container"
style={{
display: "flex",
flexDirection: window.innerWidth > 768 ? "row" : "column",
gap: "2rem",
}}
>
<div
style={{
width: window.innerWidth > 768 ? "60%" : "100%",
}}
>
<div
className="card"
style={{
width: "100%",
animation: "pulse 2s infinite",
transform: "scale(1)",
display: "flex",
flexDirection: "column",
gap: "2rem",
padding: "2rem",
}}
>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "4rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "12rem",
}}
/>
<div style={{ display: "flex", gap: "1rem" }}>
<div
style={{
borderRadius: "999px",
backgroundColor: "gray",
height: "4rem",
width: "4rem",
minWidth: "4rem",
}}
/>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "1rem",
width: "100%",
}}
>
<div
style={{
width: "100%",
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "1rem",
}}
/>
<div
style={{
width: "100%",
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2rem",
}}
/>
</div>
</div>
</div>
</div>
<div
style={{
width: window.innerWidth > 768 ? "40%" : "100%",
}}
>
<div
className="card"
style={{
width: "100%",
animation: "pulse 2s infinite",
transform: "scale(1)",
display: "flex",
flexDirection: "column",
gap: "2rem",
padding: "2rem",
}}
>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "12rem",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2rem",
}}
/>
<div style={{ display: "flex", gap: "0.5rem" }}>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
}}
/>
</div>
<div
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
<div style={{ display: "flex", gap: "1rem" }}>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "20%",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "60%",
}}
/>
<a
className="button disabled button--primary button--block"
style={{ width: "20%" }}
>
&nbsp;
</a>
</div>
<div style={{ display: "flex", gap: "1rem" }}>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "20%",
}}
/>
<div
style={{
borderRadius: "0.4rem",
backgroundColor: "gray",
height: "2.5rem",
width: "60%",
}}
/>
<a
className="button disabled button--primary button--block"
style={{ width: "20%" }}
>
&nbsp;
</a>
</div>
</div>
</div>
</div>
</div>
);
};

View file

@ -4,64 +4,64 @@ import { Showcase } from "../../../utils/apiTypes";
import { Card, PlaceholderCard } from "./Card";
interface NetworkSectionProps {
title: string;
icon?: JSX.Element;
iconColor?: string;
networks?: Showcase[];
title: string;
icon?: JSX.Element;
iconColor?: string;
networks?: Showcase[];
}
export const NetworkSection = ({
title,
icon,
iconColor,
networks
title,
icon,
iconColor,
networks,
}: NetworkSectionProps): JSX.Element => {
return (
<div className="margin-top--lg container">
<div
className="margin-bottom--sm"
style={{
display: "flex",
alignItems: "center"
}}
>
<h2>{title}</h2>
{icon && (
<span
style={{
marginBottom: "0.5rem",
marginLeft: "0.5rem",
fontSize: "1.25rem",
lineHeight: "1.75rem",
color: iconColor
}}
>
{icon}
</span>
)}
</div>
<ul
style={{
position: "relative",
display: "grid",
gap: "1.5rem",
gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
paddingLeft: "0"
}}
>
{networks ? (
<>
{networks.map((network) => (
<Card key={network.title} network={network} />
))}
{networks.length === 0 && <h2>No result</h2>}
</>
) : (
<div>
<PlaceholderCard />
</div>
)}
</ul>
</div>
);
return (
<div className="margin-top--lg container">
<div
className="margin-bottom--sm"
style={{
display: "flex",
alignItems: "center",
}}
>
<h2>{title}</h2>
{icon && (
<span
style={{
marginBottom: "0.5rem",
marginLeft: "0.5rem",
fontSize: "1.25rem",
lineHeight: "1.75rem",
color: iconColor,
}}
>
{icon}
</span>
)}
</div>
<ul
style={{
position: "relative",
display: "grid",
gap: "1.5rem",
gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
paddingLeft: "0",
}}
>
{networks ? (
<>
{networks.map((network) => (
<Card key={network.title} network={network} />
))}
{networks.length === 0 && <h2>No result</h2>}
</>
) : (
<div>
<PlaceholderCard />
</div>
)}
</ul>
</div>
);
};

View file

@ -4,7 +4,7 @@ import { FiHeart, FiSearch } from "react-icons/fi";
import useSWR from "swr";
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
import { useSelectedTags } from "@site/src/hooks/useSelectedTags";
import { useSelectedTags } from "../../../hooks/useSelectedTags";
import { useFilteredNetworks } from "../../../hooks/useFilteredNetworks";
import { Showcase } from "../../../utils/apiTypes";
@ -12,41 +12,41 @@ import { fetcher } from "../../../utils/swr";
import { NetworkSection } from "./NetworkSection";
export const Networks = (): JSX.Element => {
const { siteConfig } = useDocusaurusContext();
const { siteConfig } = useDocusaurusContext();
const { data, error } = useSWR<Showcase[]>(
`${siteConfig.customFields.API_URL}/showcase`,
fetcher
);
const { data, error } = useSWR<Showcase[]>(
`${siteConfig.customFields.API_URL}/showcase`,
fetcher,
);
const selectedTags = useSelectedTags();
const filteredNetworks = useFilteredNetworks(data ?? []);
const selectedTags = useSelectedTags();
const filteredNetworks = useFilteredNetworks(data ?? []);
return (
<section className="margin-top--lg margin-bottom--xl">
{!error ? (
selectedTags.length === 0 ? (
<>
<NetworkSection
title="Our favorites"
icon={<FiHeart />}
iconColor="rgb(190 24 93)"
networks={data?.filter((network) =>
network.tags.find((tag) => tag.label === "Favorite")
)}
/>
<NetworkSection title="All networks" networks={data} />
</>
) : (
<NetworkSection
title="Results"
icon={<FiSearch />}
networks={filteredNetworks}
/>
)
) : (
<div>{JSON.stringify(error)}</div>
)}
</section>
);
return (
<section className="margin-top--lg margin-bottom--xl">
{!error ? (
selectedTags.length === 0 ? (
<>
<NetworkSection
title="Our favorites"
icon={<FiHeart />}
iconColor="rgb(190 24 93)"
networks={data?.filter((network) =>
network.tags.find((tag) => tag.label === "Favorite"),
)}
/>
<NetworkSection title="All networks" networks={data} />
</>
) : (
<NetworkSection
title="Results"
icon={<FiSearch />}
networks={filteredNetworks}
/>
)
) : (
<div>{JSON.stringify(error)}</div>
)}
</section>
);
};

View file

@ -3,109 +3,109 @@ import "url-search-params-polyfill";
import React from "react";
import { useHistory, useLocation } from "@docusaurus/router";
import { ShowcaseTag } from "@site/src/utils/apiTypes";
import { ShowcaseTag } from "../../../utils/apiTypes";
import { toggleListItem } from "../../../utils/showcase";
interface Props extends React.ComponentProps<"input"> {
icon: React.ReactElement<React.ComponentProps<"svg">>;
label: React.ReactNode;
tag: ShowcaseTag;
icon: React.ReactElement<React.ComponentProps<"svg">>;
label: React.ReactNode;
tag: ShowcaseTag;
}
export function readSearchTags(search: string): string[] {
return new URLSearchParams(search).getAll("tags");
return new URLSearchParams(search).getAll("tags");
}
function replaceSearchTags(search: string, newTags: string[]) {
const searchParams = new URLSearchParams(search);
searchParams.delete("tags");
newTags.forEach((tag) => searchParams.append("tags", tag));
return searchParams.toString();
const searchParams = new URLSearchParams(search);
searchParams.delete("tags");
newTags.forEach((tag) => searchParams.append("tags", tag));
return searchParams.toString();
}
export const TagSelect = React.forwardRef<HTMLLabelElement, Props>(
({ icon, label, tag }) => {
const location = useLocation();
const history = useHistory();
const [selected, setSelected] = React.useState(false);
React.useEffect(() => {
const tags = readSearchTags(location.search);
setSelected(tags.includes(tag.label));
}, [tag, location]);
const toggleTag = React.useCallback(() => {
const tags = readSearchTags(location.search);
const newTags = toggleListItem(tags, tag.label);
const newSearch = replaceSearchTags(location.search, newTags);
history.push({ ...location, search: newSearch });
}, [tag, location, history]);
return (
<button
style={{
display: "flex",
alignItems: "center"
}}
className={`button button--sm button--outline button--secondary ${
selected ? "button--active" : ""
}`}
onClick={() => {
toggleTag();
}}
>
{label}
{icon}
</button>
);
}
({ icon, label, tag }) => {
const location = useLocation();
const history = useHistory();
const [selected, setSelected] = React.useState(false);
React.useEffect(() => {
const tags = readSearchTags(location.search);
setSelected(tags.includes(tag.label));
}, [tag, location]);
const toggleTag = React.useCallback(() => {
const tags = readSearchTags(location.search);
const newTags = toggleListItem(tags, tag.label);
const newSearch = replaceSearchTags(location.search, newTags);
history.push({ ...location, search: newSearch });
}, [tag, location, history]);
return (
<button
style={{
display: "flex",
alignItems: "center",
}}
className={`button button--sm button--outline button--secondary ${
selected ? "button--active" : ""
}`}
onClick={() => {
toggleTag();
}}
>
{label}
{icon}
</button>
);
},
);
export const PlaceholderTagSelect = (): JSX.Element => (
<div
style={{
boxSizing: "border-box",
position: "relative",
display: "inline-flex",
alignItems: "center",
height: "2rem",
marginTop: "0.5rem",
marginRight: "0.5rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
verticalAlign: "middle",
userSelect: "none"
}}
>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
animation: "pulse 2s infinite",
transform: "scale(1)"
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
animation: "pulse 2s infinite",
transform: "scale(1)",
marginLeft: 8
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
animation: "pulse 2s infinite",
transform: "scale(1)",
marginLeft: 8
}}
/>
</div>
<div
style={{
boxSizing: "border-box",
position: "relative",
display: "inline-flex",
alignItems: "center",
height: "2rem",
marginTop: "0.5rem",
marginRight: "0.5rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
verticalAlign: "middle",
userSelect: "none",
}}
>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
animation: "pulse 2s infinite",
transform: "scale(1)",
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
animation: "pulse 2s infinite",
transform: "scale(1)",
marginLeft: 8,
}}
/>
<div
style={{
width: "7rem",
height: "1.8rem",
borderRadius: "0.4rem",
backgroundColor: "gray",
animation: "pulse 2s infinite",
transform: "scale(1)",
marginLeft: 8,
}}
/>
</div>
);

View file

@ -10,26 +10,26 @@ import { Network } from "./_components/Network";
import { Networks } from "./_components/Networks";
const Showcase = (): JSX.Element => {
const location = useLocation();
const id = new URLSearchParams(location.search).get("id");
const location = useLocation();
const id = new URLSearchParams(location.search).get("id");
return (
<Layout
title="Showcase"
description="Portfolio of projects from the Meshtastic community"
>
<main className="margin-vert--lg">
{id ? (
<Network id={id} />
) : (
<>
<Filters />
<Networks />
</>
)}
</main>
</Layout>
);
return (
<Layout
title="Showcase"
description="Portfolio of projects from the Meshtastic community"
>
<main className="margin-vert--lg">
{id ? (
<Network id={id} />
) : (
<>
<Filters />
<Networks />
</>
)}
</main>
</Layout>
);
};
export default Showcase;

View file

@ -6,146 +6,146 @@ import { Protobuf } from "@meshtastic/meshtasticjs";
import Layout from "@theme/Layout";
const OEM = (): JSX.Element => {
const [oemAesKey, setOemAesKey] = useState<Uint8Array>(new Uint8Array());
const [oemFont, setOemFont] = useState<Protobuf.ScreenFonts>(
Protobuf.ScreenFonts.FONT_MEDIUM
);
const [oemIconBits, setOemIconBits] = useState<Uint8Array>(new Uint8Array());
const [oemIconHeight, setOemIconHeight] = useState<number>(0);
const [oemIconWidth, setOemIconWidth] = useState<number>(0);
const [oemText, setOemText] = useState<string>("");
const [oemBytes, setOemBytes] = useState<Uint8Array>(new Uint8Array());
const [oemAesKey, setOemAesKey] = useState<Uint8Array>(new Uint8Array());
const [oemFont, setOemFont] = useState<Protobuf.ScreenFonts>(
Protobuf.ScreenFonts.FONT_MEDIUM,
);
const [oemIconBits, setOemIconBits] = useState<Uint8Array>(new Uint8Array());
const [oemIconHeight, setOemIconHeight] = useState<number>(0);
const [oemIconWidth, setOemIconWidth] = useState<number>(0);
const [oemText, setOemText] = useState<string>("");
const [oemBytes, setOemBytes] = useState<Uint8Array>(new Uint8Array());
useEffect(() => {
setOemBytes(
Protobuf.OEMStore.toBinary({
oemAesKey,
oemFont,
oemIconBits,
oemIconHeight,
oemIconWidth,
oemText
})
);
}, [oemAesKey, oemFont, oemIconBits, oemIconHeight, oemIconWidth, oemText]);
useEffect(() => {
setOemBytes(
Protobuf.OEMStore.toBinary({
oemAesKey,
oemFont,
oemIconBits,
oemIconHeight,
oemIconWidth,
oemText,
}),
);
}, [oemAesKey, oemFont, oemIconBits, oemIconHeight, oemIconWidth, oemText]);
const enumOptions = Protobuf.ScreenFonts
? Object.entries(Protobuf.ScreenFonts).filter(
(value) => typeof value[1] === "number"
)
: [];
const enumOptions = Protobuf.ScreenFonts
? Object.entries(Protobuf.ScreenFonts).filter(
(value) => typeof value[1] === "number",
)
: [];
const readFile = (file: File) => {
return new Promise((resolve: (value: string) => void, reject) => {
const reader = new FileReader();
const readFile = (file: File) => {
return new Promise((resolve: (value: string) => void, reject) => {
const reader = new FileReader();
reader.onload = (res) => {
resolve(res.target.result as string);
};
reader.onerror = (err) => reject(err);
reader.onload = (res) => {
resolve(res.target.result as string);
};
reader.onerror = (err) => reject(err);
reader.readAsText(file);
});
};
reader.readAsText(file);
});
};
return (
<Layout title="OEM Generator" description="OEM Bin Generator">
<div className="container mt-8 flex flex-col gap-3">
<span>AES Key</span>
<div className="flex gap-2">
<button
onClick={() => {
const key = new Uint8Array(128 / 8);
setOemAesKey(crypto.getRandomValues(key));
}}
className="cursor-pointer rounded-md bg-tertiary p-2 hover:brightness-90"
>
Generate 128bit
</button>
<button
onClick={() => {
const key = new Uint8Array(256 / 8);
setOemAesKey(crypto.getRandomValues(key));
}}
className="mr-auto cursor-pointer rounded-md bg-tertiary p-2 hover:brightness-90"
>
Generate 256bit
</button>
</div>
<input
type="text"
name="oemAesKey"
value={fromByteArray(oemAesKey)}
onChange={(e) => {
setOemAesKey(toByteArray(e.target.value));
}}
/>
<span>Font</span>
<select
onChange={(e) => {
setOemFont(parseInt(e.target.value));
}}
>
{enumOptions.map(([name, value], index) => (
<option key={index} value={value}>
{name}
</option>
))}
</select>
<span>Logo XBM</span>
<input
type="file"
name="file"
onChange={(e) => {
readFile(e.target.files[0]).then((data) => {
setOemIconBits(
new Uint8Array(
data.split(",").map((s) => parseInt(s.trim(), 16))
)
);
});
}}
/>
<span>Logo Height</span>
<input
type="number"
name="oemIconHeight"
onChange={(e) => {
setOemIconHeight(parseInt(e.target.value));
}}
/>
<span>Logo Width</span>
<input
type="number"
name="oemIconWidth"
onChange={(e) => {
setOemIconWidth(parseInt(e.target.value));
}}
/>
<span>Boot Text</span>
<input
type="text"
name="oemText"
onChange={(e) => {
setOemText(e.target.value);
}}
/>
<a
className="cursor-pointer rounded-md bg-tertiary p-2 hover:brightness-90"
download="OEM.bin"
onClick={() => {
const blob = new Blob([oemBytes], {
type: "application/octet-stream"
});
window.open(URL.createObjectURL(blob));
}}
>
Download
</a>
{oemBytes.toString()}
</div>
</Layout>
);
return (
<Layout title="OEM Generator" description="OEM Bin Generator">
<div className="container mt-8 flex flex-col gap-3">
<span>AES Key</span>
<div className="flex gap-2">
<button
onClick={() => {
const key = new Uint8Array(128 / 8);
setOemAesKey(crypto.getRandomValues(key));
}}
className="cursor-pointer rounded-md bg-tertiary p-2 hover:brightness-90"
>
Generate 128bit
</button>
<button
onClick={() => {
const key = new Uint8Array(256 / 8);
setOemAesKey(crypto.getRandomValues(key));
}}
className="mr-auto cursor-pointer rounded-md bg-tertiary p-2 hover:brightness-90"
>
Generate 256bit
</button>
</div>
<input
type="text"
name="oemAesKey"
value={fromByteArray(oemAesKey)}
onChange={(e) => {
setOemAesKey(toByteArray(e.target.value));
}}
/>
<span>Font</span>
<select
onChange={(e) => {
setOemFont(parseInt(e.target.value));
}}
>
{enumOptions.map(([name, value], index) => (
<option key={index} value={value}>
{name}
</option>
))}
</select>
<span>Logo XBM</span>
<input
type="file"
name="file"
onChange={(e) => {
readFile(e.target.files[0]).then((data) => {
setOemIconBits(
new Uint8Array(
data.split(",").map((s) => parseInt(s.trim(), 16)),
),
);
});
}}
/>
<span>Logo Height</span>
<input
type="number"
name="oemIconHeight"
onChange={(e) => {
setOemIconHeight(parseInt(e.target.value));
}}
/>
<span>Logo Width</span>
<input
type="number"
name="oemIconWidth"
onChange={(e) => {
setOemIconWidth(parseInt(e.target.value));
}}
/>
<span>Boot Text</span>
<input
type="text"
name="oemText"
onChange={(e) => {
setOemText(e.target.value);
}}
/>
<a
className="cursor-pointer rounded-md bg-tertiary p-2 hover:brightness-90"
download="OEM.bin"
onClick={() => {
const blob = new Blob([oemBytes], {
type: "application/octet-stream",
});
window.open(URL.createObjectURL(blob));
}}
>
Download
</a>
{oemBytes.toString()}
</div>
</Layout>
);
};
export default OEM;

View file

@ -1,65 +1,65 @@
export interface Showcase {
id: string;
title: string;
summary: string;
body: string;
createdAt: Date;
updatedAt: Date;
id: string;
title: string;
summary: string;
body: string;
createdAt: Date;
updatedAt: Date;
tags: ShowcaseTag[];
nodes?: Node[];
materials?: Material[];
author?: Author;
authorId?: string;
tags: ShowcaseTag[];
nodes?: Node[];
materials?: Material[];
author?: Author;
authorId?: string;
}
export interface ShowcaseTag {
id: string;
label: string;
description: string;
color: string;
id: string;
label: string;
description: string;
color: string;
showcases?: Showcase[];
showcases?: Showcase[];
}
export interface Node {
id: string;
latitude: string;
longitude: string;
id: string;
latitude: string;
longitude: string;
showcase?: Showcase;
showcaseId?: string;
showcase?: Showcase;
showcaseId?: string;
}
export interface Material {
id: string;
name: string;
details: string;
image: string;
url: string;
id: string;
name: string;
details: string;
image: string;
url: string;
showcases?: Showcase[];
showcases?: Showcase[];
}
export interface Author {
id: string;
githubUsername: string;
bio: string;
id: string;
githubUsername: string;
bio: string;
showcase?: Showcase[];
showcase?: Showcase[];
}
export interface DeviceFirmwareResource {
id: string;
title: string;
page_url?: string;
zip_url?: string;
id: string;
title: string;
page_url?: string;
zip_url?: string;
}
export interface FirmwareReleases {
releases: {
stable: DeviceFirmwareResource[];
alpha: DeviceFirmwareResource[];
};
pullRequests: DeviceFirmwareResource[];
releases: {
stable: DeviceFirmwareResource[];
alpha: DeviceFirmwareResource[];
};
pullRequests: DeviceFirmwareResource[];
}

View file

@ -1,23 +1,23 @@
export default function calculateADC() {
//const variables
const BAT_MILLIVOLTS_FULL = 4.2;
const BAT_MILLIVOLTS_EMPTY = 3.27;
const BAT_FULL_PERCENT = 1;
//variable
const batteryChargePercent =
parseFloat(
(<HTMLInputElement>document.getElementById("batteryChargePercent")).value
) / 100;
const operativeAdcMultiplier = parseFloat(
(<HTMLInputElement>document.getElementById("operativeAdcMultiplier")).value
);
const result =
(operativeAdcMultiplier *
((BAT_FULL_PERCENT - 1) * BAT_MILLIVOLTS_EMPTY -
BAT_FULL_PERCENT * BAT_MILLIVOLTS_FULL)) /
((batteryChargePercent - 1) * BAT_MILLIVOLTS_EMPTY -
batteryChargePercent * BAT_MILLIVOLTS_FULL);
(<HTMLInputElement>(
document.getElementById("newOperativeAdcMultiplier")
)).value = result.toFixed(4);
//const variables
const BAT_MILLIVOLTS_FULL = 4.2;
const BAT_MILLIVOLTS_EMPTY = 3.27;
const BAT_FULL_PERCENT = 1;
//variable
const batteryChargePercent =
parseFloat(
(<HTMLInputElement>document.getElementById("batteryChargePercent")).value,
) / 100;
const operativeAdcMultiplier = parseFloat(
(<HTMLInputElement>document.getElementById("operativeAdcMultiplier")).value,
);
const result =
(operativeAdcMultiplier *
((BAT_FULL_PERCENT - 1) * BAT_MILLIVOLTS_EMPTY -
BAT_FULL_PERCENT * BAT_MILLIVOLTS_FULL)) /
((batteryChargePercent - 1) * BAT_MILLIVOLTS_EMPTY -
batteryChargePercent * BAT_MILLIVOLTS_FULL);
(<HTMLInputElement>(
document.getElementById("newOperativeAdcMultiplier")
)).value = result.toFixed(4);
}

View file

@ -1,13 +1,13 @@
import { Node } from "./apiTypes.js";
import { Node } from "./apiTypes";
export const mapUrl = (nodes: Node[]): string => {
const width = 900;
const height = 400;
const access_token =
"pk.eyJ1Ijoic2FjaGF3IiwiYSI6ImNrNW9meXozZjBsdW0zbHBjM2FnNnV6cmsifQ.3E4n8eFGD9ZOFo-XDVeZnQ";
const nodeCoords = nodes.map(
({ latitude, longitude }) => `pin-l+67ea94(${longitude},${latitude})`
);
const width = 900;
const height = 400;
const access_token =
"pk.eyJ1Ijoic2FjaGF3IiwiYSI6ImNrNW9meXozZjBsdW0zbHBjM2FnNnV6cmsifQ.3E4n8eFGD9ZOFo-XDVeZnQ";
const nodeCoords = nodes.map(
({ latitude, longitude }) => `pin-l+67ea94(${longitude},${latitude})`,
);
return `https://api.mapbox.com/styles/v1/mapbox/satellite-v9/static/${nodeCoords}/auto/${width}x${height}@2x?access_token=${access_token}`;
return `https://api.mapbox.com/styles/v1/mapbox/satellite-v9/static/${nodeCoords}/auto/${width}x${height}@2x?access_token=${access_token}`;
};

View file

@ -1,22 +1,22 @@
export const sortBy = <T>(array: T[], getter: (item: T) => unknown): T[] => {
const sortedArray = [...array];
sortedArray.sort((a, b) =>
getter(a) > getter(b) ? 1 : getter(b) > getter(a) ? -1 : 0
);
return sortedArray;
const sortedArray = [...array];
sortedArray.sort((a, b) =>
getter(a) > getter(b) ? 1 : getter(b) > getter(a) ? -1 : 0,
);
return sortedArray;
};
export const difference = <T>(...arrays: T[][]): T[] => {
return arrays.reduce((a, b) => a.filter((c) => !b.includes(c)));
return arrays.reduce((a, b) => a.filter((c) => !b.includes(c)));
};
export const toggleListItem = <T>(list: T[], item: T): T[] => {
const itemIndex = list.indexOf(item);
if (itemIndex === -1) {
return list.concat(item);
} else {
const newList = [...list];
newList.splice(itemIndex, 1);
return newList;
}
const itemIndex = list.indexOf(item);
if (itemIndex === -1) {
return list.concat(item);
} else {
const newList = [...list];
newList.splice(itemIndex, 1);
return newList;
}
};

View file

@ -1,22 +0,0 @@
// trunk-ignore(eslint/no-undef)
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
darkMode: "class",
theme: {
extend: {
colors: {
accent: "var(--accent)",
base: "var(--base)",
primary: "var(--primary)",
secondary: "var(--secondary)",
tertiary: "var(--tertiary)",
mute: "var(--mute)",
primaryInv: "var(--primaryInv)",
secondaryInv: "var(--secondaryInv)",
tertiaryInv: "var(--tertiaryInv)"
}
}
},
// trunk-ignore(eslint/no-undef)
plugins: [require("@tailwindcss/typography")]
};

23
tailwind.config.ts Normal file
View file

@ -0,0 +1,23 @@
import type { Config } from "tailwindcss";
import typography from "@tailwindcss/typography";
export default {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
darkMode: "class",
theme: {
extend: {
colors: {
accent: "var(--accent)",
base: "var(--base)",
primary: "var(--primary)",
secondary: "var(--secondary)",
tertiary: "var(--tertiary)",
mute: "var(--mute)",
primaryInv: "var(--primaryInv)",
secondaryInv: "var(--secondaryInv)",
tertiaryInv: "var(--tertiaryInv)",
},
},
},
plugins: [typography()],
} satisfies Config;