Merge branch 'meshtastic:master' into master

This commit is contained in:
Garth Vander Houwen 2021-11-30 20:32:34 -08:00 committed by GitHub
commit 7f4a72b883
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
79 changed files with 2292 additions and 371 deletions

View file

@ -10,10 +10,20 @@ If youd like to do real releases with your changes, the procedure is:
## Device
* Update protobufs
* * cd proto
* * git checkout master && git pull
* * cd ..
* * git add proto
* * git commit -m "updating proto submodule to latest"
* run bin/regen-protos.sh
* edit version.properties and check it into the root project
* run bin/promote-release.sh - this should cause github to start a release build (see the CI actions)
* edit the draft release text and click publish
### Update Protobufs
## Android
TBD
@ -24,10 +34,32 @@ TBD
## Python
if any dev wants to take this on, send me a note and Ill bless you with pypi
### Pre-requistes
* Python Packages
* * pip3 install pdoc3
* * pip3 install pygatt
* * pip3 install pandoc
* * pip install twine
* https://pandoc.org/installing.html
* nanopb 0.4.4 installed
### Instructions
* Update protobufs
* * cd proto
* * git checkout master && git pull
* * cd ..
* * git add proto
* * git commit -m "updating proto submodule to latest"
* run bin/regen-protos.sh
* bump the version in setup.py
* run bin/test-release.sh
* * Ensure no errors.
* run bin/upload-release.sh
I usually just edit setup.py to bump the version number, then run "bin/upload-release.sh" (though you should use bin/test-release.sh for the first time - which is just a dry deploy to the pypi test server). This script does the build (including new docs - which will end up in the git checkin) and upload to pypi. Then I do a git commit/push and tag wit the version number.
:::note
You need permissions in the github project to make a build:::
You need permissions in the github project to make a build
:::

View file

@ -1,7 +1,7 @@
---
id: faq
title: Frequenty Asked Questions (FAQ)
sidebar_label: Frequenty Asked Questions
title: Frequently Asked Questions (FAQ)
sidebar_label: Frequently Asked Questions
slug: /getting-started/faq
---
import Tabs from '@theme/Tabs';
@ -23,13 +23,13 @@ import TabItem from '@theme/TabItem';
## General
Q: What is Meshtastic?
* Meshtastic is most awesome long range, low power communications service on the planet earth! That's not even an exageration!
* Meshtastic is most awesome long range, low power communications service on the planet earth! That's not even an exaggeration!
Q: Where can I get additional help, ask questions or bond with the Meshtastic community?
* After reading this FAQ and checking out the links on the left, there are two places ... The preferred place is to check out the [Fourm](https://meshtastic.discourse.group). There you can be part of our growing community and search for previosly posts that may be similar to what you're looking for. We also have the [Meshtastic Discord](https://discord.com/invite/UQJ5QuM7vq) server where you may connect with like minded people.
* After reading this FAQ and checking out the links on the left, there are two places... The preferred place is to check out the [Forum](https://meshtastic.discourse.group). There you can be part of our growing community and search for previous posts that may be similar to what you're looking for. We also have the [Meshtastic Discord](https://discord.com/invite/UQJ5QuM7vq) server where you may connect with like-minded people.
Q: How can I contribute to Meshtastic?
* Everyone contributes in a different way. Join the [Fourm](https://meshtastic.discourse.group) and/or the [Meshtastic Discord](https://discord.com/invite/UQJ5QuM7vq) and introduce yourself. We're all very friendly. If you'd like to pitch in some code, check out the [Developers](https://meshtastic.org/docs/developers) menu on the left.
* Everyone contributes in a different way. Join the [Forum](https://meshtastic.discourse.group) and/or the [Meshtastic Discord](https://discord.com/invite/UQJ5QuM7vq) and introduce yourself. We're all very friendly. If you'd like to pitch in some code, check out the [Developers](https://meshtastic.org/docs/developers) menu on the left.
## Device (aka Node)
@ -63,7 +63,7 @@ Q: How can I tell the device not to sleep?
### Plugins
Q: What are Plugins?
* Plugins are features that expand the basic device functionalty and/or integrations with other services.
* Plugins are features that expand the basic device functionality and/or integrate with other services.
Q: What plugins do we have available?
* To see the list of available plugins, please go to: https://meshtastic.org/docs/software/plugins/plugins
@ -132,11 +132,10 @@ Q: What is a Primary Channel?
Q: What is a Secondary Channel?
* As this is a new feature, secondary Channels work on the device and the Python Script. Support for secondary channels by other clients are pending. For more information: https://meshtastic.org/docs/software/device/device-channels#how-to-use-secondary-channels
## Commmand Line / Python
## Command Line / Python
Q: How do I find out more about installing (and using) Meshtastic via command line?
* See https://meshtastic.org/docs/software/python/python-installation
Q: How do I find out more about using python to interact?
* See https://meshtastic.org/docs/software/python/python-usage

View file

@ -8,12 +8,59 @@ import TabItem from '@theme/TabItem';
## Pre-requisits
Please ensure that you use a data USB cable, as some cables provide power only and not the data lines.
:::tip
Please ensure that you use a data USB-C cable, as many USB-C cables provide power only and not the data lines.
:::
### T-Echo
:::tip
The usb-C to usb-A cable from LILYGO is *NOT* a "data cable", and can only be used for charging.
:::
#### Windows:
You may need to install the [USB device drivers](http://www.wch-ic.com/search?q=ch340&t=downloads) if your device does not show up when connected.
#### Mac OS
Last Verified T-Echo nRF52840 on: Mac OS Monterey v12.0.1 (Intel chipset)
:::tip
You can use the latest [Apple USB-C Charge cables](https://www.apple.com/shop/product/MLL82AM/A/usb-c-charge-cable-2-m). The cable that is provided with the iPad Pro works. Older Laptop usb-C Power cables will *NOT* work, as they are missing the data lines.
:::
:::caution
With the latest versions of MacOS, the USB Serial driver is built-in. Do *NOT* download any USB device drivers - this will actually prevent you from connecting to your T-Echo from your Mac. If you downloaded/installed any already, please Remove them.
:::
<details>
<summary>Removing the CH34x (CH340/CH341) USB Drivers</summary>
<div>
<div>
If you have already downloaded/installed the MacOS WCH-IC CH340 ("CH341SER_MAC") drivers via the `CH34x_Install_V1.5.pkg`, you will have to Uninstall the kernel extension:
<br />
<br />
1.) Unplug your T-Echo<br />
2.) Open the Terminal and run:<br />
3.) sudo -rf /Library/Extensions/usbserial.kext`<br />
4.) Reboot
</div>
</div>
</details>
Verify successful connections with:
* Plug in your T-Echo
* Open the Terminal
* `ls -l /dev/tty.usbmodem*`
If the device file exists, you will also notice a "TECHOBOOT" volume in the Finder, or in the Terminal after double-clicking the Reset Button (see below)
* `ls /Volumes/TECHOBOOT`
### WisBlock RAK4631
Please ensure that you have updated the bootloader to the latest version using the information on the [RAK Documentation Center](https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Quickstart/#how-to-check-if-you-have-the-updated-rak4631-bootloader) website.
@ -32,7 +79,15 @@ Be careful to install the correct load for your board. While it is unlikely that
* Connect your device to your computer with a USB cable. If you computer complains about needing to format a new drive, cancel the format command.
* Double click the reset button on your device, this will put it into boot loader mode.
* Double click the `Reset` button on your device, this will put it into boot loader mode.
[<img alt="LILYGO T-Echo" src="/img/hardware/t-echo-lilygo.jpg" style={{zoom:'25%'}} />](/img/hardware/t-echo-lilygo.jpg)
* A new drive will then be mounted on your computer. Open this drive and you should see three files: `CURRENT.UF2`, `INDEX.HTM`, and `INFO_UF2.TXT`
* Copy the appropriate `firmware-xxxxx-1.2.x.uf2` file from the firmware zip file onto the new drive.
:::note
You are going to copy/drop "as is" 'firmware-xxxxx-1.2.x.uf2' (*NOT* over the "CURRENT.UF2" file) in the volume, and the device reboot will copy it/load it correctly.
:::
* Once the file has finished copying over, the device will reboot, loading the new firmware as it does.

View file

@ -14,10 +14,10 @@ Power settings on a Meshtastic device can be set like other user-define settings
For example, if we wanted to disable sleep mode, like when we put the device into router mode, we could use the command:
```bash
meshtastic --set mesh_sds_timeout_secs MAXUNIT
meshtastic --set mesh_sds_timeout_secs 4294967295
```
:::note
See MAXUNIT from `mesh_sds_timeout_secs` below:
See MAXUINT from `mesh_sds_timeout_secs` below:
For a description and more information on what exactly all of these mean, please refer to [Power Management State Machine](../other/power)
@ -31,7 +31,7 @@ For a description and more information on what exactly all of these mean, please
| ls_secs | `integer` (seconds) | `0` (see note) |
| mesh_sds_timeout_secs | `integer` (seconds) | `0` |
| min_wake_secs | `integer` (seconds) | `0` |
| phone_sds_timeout_sec | `integer` (seconds) | `0` | Power management state machine option. See the [power page](../other/power) for details. 0 for default of two hours, MAXUINT for disabled |
| phone_sds_timeout_sec | `integer` (seconds) | `0` | Power management state machine option. See the [power page](../other/power) for details. 0 for default of two hours, use the value of MAXUINT or 4294967295 to disable |
| phone_timeout_secs | `integer` (seconds) | `0` |
| screen_on_secs | `integer` (seconds) | `0` |
| sds_secs | `integer` (seconds) | `0` |
@ -69,7 +69,7 @@ Power management state machine option. See the [power page](../other/power) for
### mesh_sds_timeout_secs
Power management state machine option. See the [power page](../other/power) for details. 0 for default of two hours, MAXUINT for disabled
Power management state machine option. See the [power page](../other/power) for details. 0 for default of two hours, use the MAXUINT or 4294967295 to disable
### min_wake_secs
@ -77,7 +77,7 @@ Power management state machine option. See the [power page](../other/power)for d
### phone_sds_timeout_sec
Power management state machine option. See the [power page](../other/power) for details. 0 for default of two hours, MAXUINT for disabled
Power management state machine option. See the [power page](../other/power) for details. 0 for default of two hours, use the MAXUINT or 4294967295 to disable
### phone_timeout_secs
@ -112,7 +112,9 @@ Power management state machine option. See the [power page](../other/power) for
]}>
<TabItem value="cli">
TODO
```bash
meshtastic --set mesh_sds_timeout_secs 0
```
</TabItem>
<TabItem value="android">

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 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