Initial showcase release

This commit is contained in:
Sacha Weatherstone 2021-11-30 21:46:31 +11:00
parent 9df6f8ed4b
commit f1a50b3a99
74 changed files with 2186 additions and 353 deletions

View file

@ -25,6 +25,7 @@ const config = {
},
navbar: {
title: "Meshtastic",
hideOnScroll: true,
logo: {
alt: "Meshtastic Logo",
src: "img/meshtastic-design/logo/svg/Mesh_Logo_Black.svg",
@ -32,28 +33,14 @@ const config = {
},
items: [
{
label: "Showcase",
to: "showcase",
activeBasePath: "showcase",
},
{
label: "Docs",
to: "docs/getting-started",
activeBasePath: "docs/getting-started",
label: "Getting Started",
position: "left",
},
{
to: "docs/software",
activeBasePath: "docs/software",
label: "Software",
position: "left",
},
{
to: "docs/hardware",
activeBasePath: "docs/hardware",
label: "Hardware",
position: "left",
},
{
to: "docs/developers",
activeBasePath: "docs/developers",
label: "Developers",
position: "left",
},
{
href: "https://meshtastic.discourse.group",
@ -75,20 +62,12 @@ const config = {
title: "Docs",
items: [
{
label: "Getting Started",
label: "Get Started",
to: "docs/getting-started",
},
{
label: "Software",
to: "docs/software",
},
{
label: "Hardware",
to: "docs/hardware",
},
{
label: "Developers",
to: "docs/developers",
label: "Showcase",
to: "showcase",
},
],
},
@ -128,6 +107,7 @@ const config = {
searchParameters: {},
},
},
presets: [
[
"@docusaurus/preset-classic",
@ -144,6 +124,7 @@ const config = {
},
],
],
plugins: ["@docusaurus/plugin-ideal-image"],
};
module.exports = config;

View file

@ -2,6 +2,7 @@
"name": "meshtastic",
"version": "0.0.0",
"private": true,
"license": "GPL-3.0-only",
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
@ -10,9 +11,10 @@
"clear": "docusaurus clear"
},
"dependencies": {
"@algolia/client-search": "^4.10.3",
"@docusaurus/core": "^2.0.0-beta.8",
"@docusaurus/preset-classic": "^2.0.0-beta.8",
"@algolia/client-search": "^4.11.0",
"@docusaurus/core": "^2.0.0-beta.9",
"@docusaurus/plugin-ideal-image": "^2.0.0-beta.9",
"@docusaurus/preset-classic": "^2.0.0-beta.9",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.1.1",
"react": "^17.0.2",
@ -31,9 +33,9 @@
]
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^2.0.0-beta.8",
"@docusaurus/module-type-aliases": "^2.0.0-beta.9",
"@tsconfig/docusaurus": "^1.0.4",
"@types/node": "^16.11.7",
"typescript": "^4.4.4"
"@types/node": "^16.11.10",
"typescript": "^4.5.2"
}
}

View file

@ -1,10 +1,3 @@
/* stylelint-disable docusaurus/copyright-header */
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #67ea94;

View file

@ -0,0 +1,365 @@
export const rakWireless = {
/**
* Base modules
*/
RAK19003: {
name: "RAK19003",
details: "WisBlock Mini Base Board",
image: "/img/hardware/rak/RAK19003.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK19003/Overview/",
},
RAK5005_O: {
name: "RAK5005-O",
details: "WisBlock Base Board",
image: "/img/hardware/rak/RAK5005-O.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK5005-O/Overview/",
},
/**
* Core modules
*/
RAK11200: {
name: "RAK11200",
details: "WisBlock WiFi Module",
image: "/img/hardware/rak/RAK11200.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK11200/Overview/",
},
RAK11310: {
name: "RAK11310",
details: "WisBlock LPWAN Module",
image: "/img/hardware/rak/RAK11310.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK11310/Overview/",
},
RAK4631: {
name: "RAK4631",
details: "WisBlock LPWAN Module",
image: "/img/hardware/rak/RAK4631.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Overview/",
},
/**
* Wireless modules
*/
RAK13101: {
name: "RAK13101",
details: "WisBlock GSM/GPRS Module",
image: "/img/hardware/rak/RAK13101.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK13101/Overview/",
},
RAK2305: {
name: "RAK2305",
details: "WisBlock WiFi Interface Module",
image: "/img/hardware/rak/RAK2305.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK2305/Overview/",
},
RAK5860: {
name: "RAK5860",
details: "WisBlock NB-IoT Interface Module",
image: "/img/hardware/rak/RAK5860.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK5860/Overview/",
},
/**
* Sensor modules
*/
RAK12003: {
name: "RAK12003",
details: "WisBlock Infrared Temperature Sensor",
image: "/img/hardware/rak/RAK12003.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12003/Overview/",
},
RAK12004: {
name: "RAK12004",
details: "WisBlock MQ2 Gas Sensor Module",
image: "/img/hardware/rak/RAK12004.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12004/Overview/",
},
RAK12005: {
name: "RAK12005",
details: "WisBlock Rain Sensor Module",
image: "/img/hardware/rak/RAK12005.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12005/Overview/",
},
RAK12006: {
name: "RAK12006",
details: "WisBlock PIR Module",
image: "/img/hardware/rak/RAK12006.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12006/Overview/",
},
RAK12007: {
name: "RAK12007",
details: "WisBlock Ultrasonic Module",
image: "/img/hardware/rak/RAK12007.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12007/Overview/",
},
RAK12009: {
name: "RAK12009",
details: "WisBlock MQ3 Alcohol Gas Sensor Module",
image: "/img/hardware/rak/RAK12009.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12009/Overview/",
},
RAK12010: {
name: "RAK12010",
details: "WisBlock Ambient Light Sensor Module",
image: "/img/hardware/rak/RAK12010.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12010/Overview/",
},
RAK12011: {
name: "RAK12011",
details: "WisBlock Barometer WT Sensor Module",
image: "/img/hardware/rak/RAK12011.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12011/Overview/",
},
RAK12012: {
name: "RAK12012",
details: "WisBlock Heart Rate Module",
image: "/img/hardware/rak/RAK12012.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12012/Overview/",
},
RAK12015: {
name: "RAK12015",
details: "WisBlock Vibration Detection Module",
image: "/img/hardware/rak/RAK12015.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12015/Overview/",
},
RAK12500: {
name: "RAK12500",
details: "WisBlock GNSS Location Module",
image: "/img/hardware/rak/RAK12500.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12500/Overview/",
},
RAK16000: {
name: "RAK16000",
details: "WisBlock DC Current Module",
image: "/img/hardware/rak/RAK16000.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK16000/Overview/",
},
RAK18000: {
name: "RAK18000",
details: "WisBlock PDM Stereo Microphone Module",
image: "/img/hardware/rak/RAK18000.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK18000/Overview/",
},
RAK1901: {
name: "RAK1901",
details: "WisBlock Temperature and Humidity Sensor",
image: "/img/hardware/rak/RAK1901.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK1901/Overview/",
},
RAK1902: {
name: "RAK1902",
details: "WisBlock Barometer Pressure Sensor",
image: "/img/hardware/rak/RAK1902.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK1902/Overview/",
},
RAK1903: {
name: "RAK1903",
details: "WisBlock Ambient Light Sensor",
image: "/img/hardware/rak/RAK1903.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK1903/Overview/",
},
RAK1904: {
name: "RAK1904",
details: "WisBlock 3-axis Acceleration Sensor",
image: "/img/hardware/rak/RAK1904.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK1904/Overview/",
},
RAK1906: {
name: "RAK1906",
details: "WisBlock Environmental Sensor",
image: "/img/hardware/rak/RAK1906.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK1906/Overview/",
},
RAK1910: {
name: "RAK1910",
details: "WisBlock GNSS Location Module",
image: "/img/hardware/rak/RAK1910.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK1910/Overview/",
},
/**
* Interface modules
*/
RAK13001: {
name: "RAK13001",
details: "WisBlock Relay IO Module",
image: "/img/hardware/rak/RAK13001.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK13001/Overview/",
},
RAK13002: {
name: "RAK13002",
details: "WisBlock IO Module",
image: "/img/hardware/rak/RAK13002.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK13002/Overview/",
},
RAK13003: {
name: "RAK13003",
details: "WisBlock IO Expansion Module",
image: "/img/hardware/rak/RAK13003.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK13003/Overview/",
},
RAK13004: {
name: "RAK13004",
details: "WisBlock PWM Expander Module",
image: "/img/hardware/rak/RAK13004.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK13004/Overview/",
},
RAK13005: {
name: "RAK13005",
details: "WisBlock LIN Module",
image: "/img/hardware/rak/RAK13005.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK13005/Overview/",
},
RAK14002: {
name: "RAK14002",
details: "WisBlock Touch Sensor Module",
image: "/img/hardware/rak/RAK14002.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK14002/Overview/",
},
RAK16001: {
name: "RAK16001",
details: "WisBlock ADC Module",
image: "/img/hardware/rak/RAK16001.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK16001/Overview/",
},
RAK1920: {
name: "RAK1920",
details: "WisBlock Sensor Adapter Module",
image: "/img/hardware/rak/RAK1920.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK1920/Overview/",
},
RAK5801: {
name: "RAK5801",
details: "WisBlock 4-20mA Interface Module",
image: "/img/hardware/rak/RAK5801.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK5801/Overview/",
},
RAK5802: {
name: "RAK5802",
details: "WisBlock RS485 Interface Module",
image: "/img/hardware/rak/RAK5802.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK5802/Overview/",
},
RAK5804: {
name: "RAK5804",
details: "WisBlock Interface Extension Module",
image: "/img/hardware/rak/RAK5804.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK5804/Overview/",
},
RAK5811: {
name: "RAK5811",
details: "WisBlock 0-5V Interface Module",
image: "/img/hardware/rak/RAK5811.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK5811/Overview/",
},
/**
* Display Modules
*/
RAK14000: {
name: "RAK14000",
details: "WisBlock E-Ink Display",
image: "/img/hardware/rak/RAK14000.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK14000/Overview/",
},
RAK14001: {
name: "RAK14001",
details: "WisBlock RGB LED Module",
image: "/img/hardware/rak/RAK14001.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK14001/Overview/",
},
RAK14003: {
name: "RAK14003",
details: "WisBlock LED Bar Graph Module",
image: "/img/hardware/rak/RAK14003.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK14003/Overview/",
},
RAK1921: {
name: "RAK1921",
details: "WisBlock OLED Display",
image: "/img/hardware/rak/RAK1921.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK1921/Overview/",
},
/**
* Extra modules
*/
RAK12002: {
name: "RAK12002",
details: "WisBlock RTC Module",
image: "/img/hardware/rak/RAK12002.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK12002/Overview/",
},
RAK18001: {
name: "RAK18001",
details: "WisBlock Buzzer Module",
image: "/img/hardware/rak/RAK18001.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK18001/Overview/",
},
RAK19005: {
name: "RAK19005",
details: "WisBlock Sensor Extension Cable",
image: "/img/hardware/rak/RAK19005.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK19005/Overview/",
},
RAK19008: {
name: "RAK19008",
details: "WisBlock IO Extension Cable",
image: "/img/hardware/rak/RAK19008.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK19008/Overview/",
},
/**
* Storage modules
*/
RAK15000: {
name: "RAK15000",
details: "WisBlock EEPROM Module",
image: "/img/hardware/rak/RAK15000.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK15000/Overview/",
},
RAK15001: {
name: "RAK15001",
details: "WisBlock Flash Module",
image: "/img/hardware/rak/RAK15001.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK15001/Overview/",
},
RAK15002: {
name: "RAK15002",
details: "WisBlock Micro SD Card Module",
image: "/img/hardware/rak/RAK15002.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK15002/Overview/",
},
/**
* Power modules
*/
RAK19002: {
name: "RAK19002",
details: "WisBlock Boost Module",
image: "/img/hardware/rak/RAK19002.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK19002/Overview/",
},
RAK19004: {
name: "RAK19004",
details: "WisBlock Green Power Module",
image: "/img/hardware/rak/RAK19004.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK19004/Overview/",
},
RAK19006: {
name: "RAK19006",
details: "WisBlock Wireless Charge Module",
image: "/img/hardware/rak/RAK19006.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK19006/Overview/",
},
/**
* Motor modules
*/
RAK17000: {
name: "RAK17000",
details: "WisBlock Motor Control Module",
image: "/img/hardware/rak/RAK17000.png",
url: "https://docs.rakwireless.com/Product-Categories/WisBlock/RAK17000/Overview/",
},
};

View file

@ -0,0 +1,80 @@
import { ShowcaseNetwork } from '../../utils/showcase';
export const networks: ShowcaseNetwork[] = [
{
id: "ckwhq3l5a000008kufkw8f3dg",
title: "Network 1",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed eget dui mollis.",
nodes: [
{
latitude: -37.656719,
longitude: 145.632219,
},
{
latitude: -37.633466,
longitude: 145.692371,
},
{
latitude: -37.559148,
longitude: 145.735771,
},
],
tags: ["community", "largeNetwork"],
},
{
id: "ckwhq4jch000108kuawlwaz0y",
title: "Network 2",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut mattis felis.",
nodes: [
{
latitude: -27.069626,
longitude: 139.961265,
},
{
latitude: -26.520932,
longitude: 139.773739,
},
{
latitude: -26.233798,
longitude: 139.752755,
},
],
tags: ["favorite", "portable"],
},
{
id: "ckwhq4skm000208ku4h8ta03q",
title: "Network 3",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, faucibus ut.",
nodes: [
{
latitude: -5.64,
longitude: 134.749708,
},
{
latitude: -5.561545,
longitude: 134.706247,
},
],
tags: ["longDistance"],
},
{
id: "ckwhq4yau000308kufwqz6pri",
title: "Network 4",
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce luctus.",
nodes: [
{
latitude: 46.658126,
longitude: 62.22795,
},
{
latitude: 46.697482,
longitude: 61.560184,
},
],
tags: ["favorite", "largeNetwork", "offGrid"],
},
];

View file

@ -0,0 +1,88 @@
import { NetworkWriteup } from '../../../utils/showcase';
import { rakWireless } from '../../hardware/rakWireless';
export const writeup: NetworkWriteup = {
summary: "This is the article summary.",
body: [
{
heading: "This is the first heading.",
body: "This is the first body segment.",
},
{
heading: "This is the second heading.",
body: "This is the second body segment.",
},
{
heading: "This is the second heading.",
body: "This is the second body segment.",
},
],
bom: [
{
name: "This is the first material name.",
details: "This is the first material details.",
image: "https://example.com/image.png",
url: "https://example.com/material",
},
rakWireless.RAK19003,
rakWireless.RAK5005_O,
rakWireless.RAK11200,
rakWireless.RAK11310,
rakWireless.RAK4631,
rakWireless.RAK13101,
rakWireless.RAK2305,
rakWireless.RAK5860,
rakWireless.RAK12003,
rakWireless.RAK12004,
rakWireless.RAK12005,
rakWireless.RAK12006,
rakWireless.RAK12007,
rakWireless.RAK12009,
rakWireless.RAK12010,
rakWireless.RAK12011,
rakWireless.RAK12012,
rakWireless.RAK12015,
rakWireless.RAK12500,
rakWireless.RAK16000,
rakWireless.RAK18000,
rakWireless.RAK1901,
rakWireless.RAK1902,
rakWireless.RAK1903,
rakWireless.RAK1904,
rakWireless.RAK1906,
rakWireless.RAK1910,
rakWireless.RAK13001,
rakWireless.RAK13002,
rakWireless.RAK13003,
rakWireless.RAK13004,
rakWireless.RAK13005,
rakWireless.RAK14002,
rakWireless.RAK16001,
rakWireless.RAK1920,
rakWireless.RAK5801,
rakWireless.RAK5802,
rakWireless.RAK5804,
rakWireless.RAK5811,
rakWireless.RAK14000,
rakWireless.RAK14001,
rakWireless.RAK14003,
rakWireless.RAK1921,
rakWireless.RAK12002,
rakWireless.RAK18001,
rakWireless.RAK19005,
rakWireless.RAK19008,
rakWireless.RAK15000,
rakWireless.RAK15001,
rakWireless.RAK15002,
rakWireless.RAK19002,
rakWireless.RAK19004,
rakWireless.RAK19006,
rakWireless.RAK17000,
],
author: {
name: "Author Name",
about: "This is the author's about text.",
avatarUrl: "https://avatars0.githubusercontent.com/u/1234?s=460&v=4",
url: "",
},
};

View file

@ -0,0 +1,27 @@
import React from 'react';
import { ShowcaseNetwork, sortedNetworks, TagType } from '../utils/showcase';
import { useSelectedTags } from './useSelectedTags';
const filterNetworks = (
showcaseNetworks: ShowcaseNetwork[],
selectedTags: TagType[]
) => {
if (selectedTags.length === 0) {
return showcaseNetworks;
}
return showcaseNetworks.filter((showcaseNetwork) => {
if (showcaseNetwork.tags.length === 0) {
return false;
}
return selectedTags.every((tag) => showcaseNetwork.tags.includes(tag));
});
};
export const useFilteredNetworks = () => {
const selectedTags = useSelectedTags();
return React.useMemo(
() => filterNetworks(sortedNetworks, selectedTags),
[selectedTags]
);
};

View file

@ -0,0 +1,17 @@
import React from 'react';
import { useLocation } from '@docusaurus/router';
import { readSearchTags } from '../pages/showcase/_components/TagSelect';
import { TagType } from '../utils/showcase';
export const useSelectedTags = () => {
const location = useLocation();
const [selectedTags, setSelectedTags] = React.useState<TagType[]>([]);
React.useEffect(() => {
const tags = readSearchTags(location.search);
setSelectedTags(tags);
}, [location]);
return selectedTags;
};

View file

@ -1,13 +1,9 @@
import React from 'react';
import clsx from 'clsx';
import Head from '@docusaurus/Head';
import useBaseUrl from '@docusaurus/useBaseUrl';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import Head from '@docusaurus/Head';
import styles from './styles.module.css';
const features = [
{
@ -15,7 +11,10 @@ const features = [
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.
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.
</>
),
},
@ -24,7 +23,10 @@ const features = [
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.
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.
</>
),
},
@ -33,7 +35,9 @@ const features = [
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.
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.
</>
),
},
@ -42,7 +46,10 @@ const features = [
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 decentralised, encrypted messaging network, your imagination is the limit.
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 decentralised, encrypted
messaging network, your imagination is the limit.
</>
),
},
@ -51,7 +58,9 @@ const features = [
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.
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.
</>
),
},
@ -60,7 +69,8 @@ const features = [
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!
All Meshtastic software is open source. If you want an improvement,
submit a pull request or file an issue on Github. Happy coding!
</>
),
},
@ -69,10 +79,10 @@ const features = [
function Feature({ imageUrl, title, description }) {
const imgUrl = useBaseUrl(imageUrl);
return (
<div className={clsx("col col--4", styles.feature)}>
<div className="col col--4">
{imgUrl && (
<div className="text--center">
<img className={styles.featureImage} src={imgUrl} alt={title} />
<img width={200} height={200} src={imgUrl} alt={title} />
</div>
)}
<h3>{title}</h3>
@ -90,7 +100,9 @@ function Home() {
<meta property="og:title" content="Meshtastic" />
<meta
property="og:image"
content={useBaseUrl("img/meshtastic-design/web/social-preview-1200x630.png")}
content={useBaseUrl(
"img/meshtastic-design/web/social-preview-1200x630.png"
)}
/>
<meta
property="og:description"
@ -99,7 +111,7 @@ function Home() {
<meta property="og:url" content="https://meshtastic.org/" />
<meta name="twitter:card" content="summary_large_image" />
</Head>
<header className={clsx("hero hero--primary", styles.heroBanner)}>
<header style={{ textAlign: "center" }} className="hero hero--primary">
<div className="container">
<h1 className="hero__title">
<img
@ -114,7 +126,9 @@ function Home() {
</header>
<main>
{features && features.length > 0 && (
<section className={styles.features}>
<section
style={{ display: "flex", alignItems: "center", padding: "2rem" }}
>
<div className="container">
<div className="row">
{features.map((props, idx) => (

View file

@ -0,0 +1,99 @@
import React from 'react';
import Image from '@theme/IdealImage';
import {
Node,
ShowcaseNetwork,
sortBy,
Tag,
TagList,
Tags,
TagType,
} from '../../../utils/showcase';
interface Props extends Tag {
id: string;
}
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})`
);
return `https://api.mapbox.com/styles/v1/mapbox/satellite-v9/static/${nodeCoords}/auto/${width}x${height}@2x?access_token=${access_token}`;
};
const CardTags = ({ tags }: { tags: TagType[] }) => {
const tagObjects = tags.map((tag) => ({ tag, ...Tags[tag] }));
const tagObjectsSorted = sortBy(tagObjects, (tagObject) =>
TagList.indexOf(tagObject.tag)
);
return (
<ul className="pills">
{tagObjectsSorted.map(({ color, description, label }, index) => {
return (
<li
key={index}
style={{
display: "inline-flex",
alignItems: "center",
alignContent: "center",
gap: "0.3rem",
fontSize: "0.6rem",
lineHeight: "1rem",
cursor: "default",
userSelect: "none",
padding: "0.2rem",
border: "2px solid gray",
}}
className="pills__item"
title={description}
>
<span>{label.toLowerCase()}</span>
<span
style={{
backgroundColor: color,
width: "0.5rem",
height: "0.5rem",
borderRadius: "50%",
}}
/>
</li>
);
})}
</ul>
);
};
export const Card = React.memo(
({ showcaseNetwork }: { showcaseNetwork: ShowcaseNetwork }) => (
<div className="card">
<div className="card__image">
<Image
img={mapUrl(showcaseNetwork.nodes)}
alt={showcaseNetwork.title}
/>
</div>
<div className="card__body">
<h4>{showcaseNetwork.title}</h4>
<small>{showcaseNetwork.description}</small>
</div>
<div className="card__footer">
<a
href={`?id=${showcaseNetwork.id}`}
className="button button--primary button--block"
style={{ marginBottom: "0.5rem" }}
>
Get Started
</a>
<CardTags tags={showcaseNetwork.tags} />
</div>
</div>
)
);

View file

@ -0,0 +1,74 @@
import React from 'react';
import { FiHeart } from 'react-icons/fi';
import { TagList, Tags } from '../../../utils/showcase';
import { TagSelect } from './TagSelect';
export const Filters = (): JSX.Element => {
return (
<section className="container margin-top--l margin-bottom--lg">
<ul
style={{
padding: "0",
display: "flex",
alignItems: "center",
flexWrap: "wrap",
}}
>
{TagList.map((tag, i) => {
const { label, description, color } = Tags[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 === "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>
</section>
);
};

View file

@ -0,0 +1,117 @@
import React from 'react';
import { networks } from '../../../data/networks/_overview';
import { NetworkWriteup } from '../../../utils/showcase';
interface NetworkProps {
id: string;
}
export const Network = ({ id }: NetworkProps): JSX.Element => {
import(`../../../data/networks/${id}/writeup.ts`).then((data) => {
setNetworkWriteup(data.writeup as NetworkWriteup);
});
// console.log(data);
const [networkWriteup, setNetworkWriteup] = React.useState<NetworkWriteup>();
React.useEffect(() => {
// data.then((data) => setNetworkWriteup(data));
}, []);
const network = networks.find((network) => network.id === id);
return network && networkWriteup ? (
<div className="container">
<h1>{network.title}</h1>
<p>{network.description}</p>
<div className="avatar">
<img
src={networkWriteup.author.avatarUrl}
alt={networkWriteup.author.name}
className="avatar__photo"
/>
<div className="avatar__intro">
<div className="avatar__name">{networkWriteup.author.name}</div>
<div className="avatar__subtitle">{networkWriteup.author.about}</div>
</div>
</div>
{networkWriteup.body.map((segment, index) => (
<div key={index}>
<h2>{segment.heading}</h2>
<p>{segment.body}</p>
</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">
{networkWriteup.bom.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"
href={material.url}
className="button button--outline button--secondary"
style={{
marginTop: "auto",
marginBottom: "auto",
}}
>
View
</a>
</div>
))}
</div>
</div>
</div>
) : (
<div>
<h1>Network not found</h1>
</div>
);
};

View file

@ -0,0 +1,97 @@
import React from 'react';
import { FiHeart, FiSearch } from 'react-icons/fi';
import { useFilteredNetworks } from '../../../hooks/useFilteredNetworks';
import { useSelectedTags } from '../../../hooks/useSelectedTags';
import { ShowcaseNetwork, sortedNetworks } from '../../../utils/showcase';
import { Card } from './Card';
const favoriteNetworks = sortedNetworks.filter((network) =>
network.tags.includes("favorite")
);
const otherNetworks = sortedNetworks.filter(
(network) => !network.tags.includes("favorite")
);
interface NetworkSectionProps {
title: string;
icon?: JSX.Element;
iconColor?: string;
networks: ShowcaseNetwork[];
}
const NetworkSection = ({
title,
icon,
iconColor,
networks,
}: NetworkSectionProps): JSX.Element => {
return (
<div className="container margin-top--lg">
<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.map((network) => (
<Card key={network.title} showcaseNetwork={network} />
))}
{networks.length === 0 && <h2>No result</h2>}
</ul>
</div>
);
};
export const Networks = (): JSX.Element => {
const selectedTags = useSelectedTags();
const filteredNetworks = useFilteredNetworks();
return (
<section className="margin-top--lg margin-bottom--xl">
{selectedTags.length === 0 ? (
<>
<NetworkSection
title="Our favorites"
icon={<FiHeart />}
iconColor="rgb(190 24 93)"
networks={favoriteNetworks}
/>
<NetworkSection title="All networks" networks={otherNetworks} />
</>
) : (
<NetworkSection
title="Results"
icon={<FiSearch />}
networks={filteredNetworks}
/>
)}
</section>
);
};

View file

@ -0,0 +1,57 @@
import React from 'react';
import { useHistory, useLocation } from '@docusaurus/router';
import { TagType, toggleListItem } from '../../../utils/showcase';
interface Props extends React.ComponentProps<"input"> {
icon: React.ReactElement<React.ComponentProps<"svg">>;
label: React.ReactNode;
tag: TagType;
}
export function readSearchTags(search: string): TagType[] {
return new URLSearchParams(search).getAll("tags") as TagType[];
}
function replaceSearchTags(search: string, newTags: TagType[]) {
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>(
({ id, icon, label, tag, ...rest }, ref) => {
const location = useLocation();
const history = useHistory();
const [selected, setSelected] = React.useState(false);
React.useEffect(() => {
const tags = readSearchTags(location.search);
setSelected(tags.includes(tag));
}, [tag, location]);
const toggleTag = React.useCallback(() => {
const tags = readSearchTags(location.search);
const newTags = toggleListItem(tags, tag);
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>
);
}
);

View file

@ -0,0 +1,33 @@
import React from 'react';
import { useLocation } from '@docusaurus/router';
import Layout from '@theme/Layout';
import { Filters } from './_components/Filters';
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");
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

@ -1,37 +0,0 @@
/* stylelint-disable docusaurus/copyright-header */
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
}
@media screen and (max-width: 966px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureImage {
height: 200px;
width: 200px;
}

View file

@ -0,0 +1,125 @@
import { networks } from '../data/networks/_overview';
export interface Material {
name: string;
details: string;
image?: string;
url?: string;
}
interface Author {
name: string;
about: string;
url?: string;
avatarUrl?: string;
}
interface BodySegment {
heading: string;
body: string;
}
export interface NetworkWriteup {
summary: string;
body: BodySegment[];
bom: Material[];
author: Author;
}
export type Tag = {
label: string;
description: string;
color: string;
};
export type TagType =
| "portable"
| "offGrid"
| "largeNetwork"
| "longDistance"
| "community"
| "favorite";
export interface Node {
latitude: number;
longitude: number;
}
export type ShowcaseNetwork = {
id: string; //please get id from https://www.getuniqueid.com/cuid
title: string;
description: string;
nodes: Node[];
tags: TagType[];
};
export const Tags: Record<TagType, Tag> = {
portable: {
label: "Portable",
description: "Networks that move",
color: "#560bad",
},
offGrid: {
label: "Off Grid",
description: "No mains power here",
color: "#2a9d8f",
},
largeNetwork: {
label: "Large Network",
description: "Many users or nodes",
color: "#2191bc",
},
longDistance: {
label: "Long Distance",
description: "Links over massive distances",
color: "#e9c46a",
},
community: {
label: "Community",
description: "General access networks for many users",
color: "#e76f51",
},
favorite: {
label: "Favorite",
description: "Our picks for the coolest networks",
color: "#e9669e",
},
};
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;
};
export const TagList = Object.keys(Tags) as TagType[];
function sortNetworks() {
let result = networks;
result = sortBy(result, (user) => user.title.toLowerCase());
result = sortBy(result, (user) => !user.tags.includes("favorite"));
return result;
}
export const sortedNetworks = sortNetworks();
export const difference = <T>(...arrays: T[][]): T[] => {
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;
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View file

@ -1,4 +1,8 @@
{
"extends": "@tsconfig/docusaurus/tsconfig.json",
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"strict": true
},
"include": ["src/"]
}

File diff suppressed because it is too large Load diff