Initial
This commit is contained in:
27
src/components/BatteryCalculator.tsx
Normal file
27
src/components/BatteryCalculator.tsx
Normal file
@@ -0,0 +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 className="card__footer">
|
||||
<button
|
||||
type="button"
|
||||
className="button button--secondary button--block"
|
||||
>
|
||||
See All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
16
src/components/Button.tsx
Normal file
16
src/components/Button.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
13
src/components/ColorMode.tsx
Normal file
13
src/components/ColorMode.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from "react";
|
||||
|
||||
export interface ColorModeProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Dark = ({ children }: ColorModeProps): JSX.Element => {
|
||||
return <div className="hideLight">{children}</div>;
|
||||
};
|
||||
|
||||
export const Light = ({ children }: ColorModeProps): JSX.Element => {
|
||||
return <div className="hideDark">{children}</div>;
|
||||
};
|
||||
274
src/components/HardwareComponents.tsx
Normal file
274
src/components/HardwareComponents.tsx
Normal file
@@ -0,0 +1,274 @@
|
||||
// import React from 'react';
|
||||
// import data from '/docs/hardware/supported/devices.json'
|
||||
|
||||
// function checkVersionOverrides(selectedDevice, version, value) {
|
||||
|
||||
// var versionOverride = selectedDevice.versionOverrides[version]
|
||||
// var device = selectedDevice
|
||||
// var objectSegment = value.split('.')
|
||||
|
||||
// while (objectSegment.length > 1) {
|
||||
// console.log(objectSegment)
|
||||
// let test = objectSegment.shift()
|
||||
// console.log('test', test, 'og objectSegment', objectSegment)
|
||||
// versionOverride = versionOverride[test]
|
||||
// device = device[test]
|
||||
// }
|
||||
// if (versionOverride) {
|
||||
// return versionOverride
|
||||
// } else return device
|
||||
|
||||
// // if (selectedDevice.versionOverrides[version][value]) {
|
||||
// // return selectedDevice.versionOverrides[version][value]
|
||||
// // } else {
|
||||
// // console.log("no", selectedDevice, value, selectedDevice[value])
|
||||
// // return selectedDevice[value]
|
||||
// // }
|
||||
// }
|
||||
|
||||
// export const MeshtasticFeatures = ({device, version}): JSX.Element => {
|
||||
|
||||
// const selectedDevice = data[device]
|
||||
|
||||
// return (
|
||||
// <table>
|
||||
// <thead>
|
||||
// <th style={{align: "center"}}>
|
||||
// Meshtastic Feature
|
||||
// </th>
|
||||
// <th style={{align: "center"}}>
|
||||
// Device Support
|
||||
// </th>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Support Status
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// {checkVersionOverrides(selectedDevice, version, 'supportStatus')}
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Bluetooth
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// {checkVersionOverrides(selectedDevice, version, "features.bluetoothCapable")}
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Module - Canned Message
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Module - External Notification
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Module - Range Test
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Module - Rotary Encoder
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Module - Store and Forward
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Module - Telemetry (aka Environmental Measurement)
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Router - Always Powered
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Router - Solar Powered
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// WiFi
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// </tbody>
|
||||
// </table>
|
||||
// );
|
||||
// };
|
||||
|
||||
// export const HardwareSpecifications = ({device, version}): JSX.Element => {
|
||||
|
||||
// const selectedDevice = data[device]
|
||||
|
||||
// return (
|
||||
// <table>
|
||||
// <thead>
|
||||
// <th style={{align: "center"}}>
|
||||
// Specification
|
||||
// </th>
|
||||
// <th style={{align: "center"}}>
|
||||
// Value
|
||||
// </th>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Bluetooth
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Bluetooth Antenna
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Chipset
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Driver
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// GPS
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Flash
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Frequency - 433MHz
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Frequency - 868MHz
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Frequency - 915MHz
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// Frequency - 923MHz
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// LoRa Transceiver
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// PSRAM
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// RAM
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// WiFi
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td style={{align: "center"}}>
|
||||
// WiFi Antenna
|
||||
// </td>
|
||||
// <td style={{align: "center"}}>
|
||||
// VALUE
|
||||
// </td>
|
||||
// </tr>
|
||||
// </tbody>
|
||||
// </table>
|
||||
// );
|
||||
// };
|
||||
49
src/components/Modal.tsx
Normal file
49
src/components/Modal.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
import { Dialog } from "@headlessui/react";
|
||||
|
||||
export interface ModalProps {
|
||||
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>
|
||||
|
||||
<span
|
||||
className="inline-block h-screen align-middle"
|
||||
aria-hidden="true"
|
||||
>
|
||||
​
|
||||
</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>
|
||||
);
|
||||
};
|
||||
25
src/components/PageLayout.tsx
Normal file
25
src/components/PageLayout.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
|
||||
import Layout from "@theme/Layout";
|
||||
|
||||
export interface PageLayoutProps {
|
||||
title: string;
|
||||
description: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const PageLayout = ({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
}: PageLayoutProps): JSX.Element => {
|
||||
return (
|
||||
<Layout title={title} description={description}>
|
||||
{children}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export interface ColorModeProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
31
src/components/homepage/SocialCard.tsx
Normal file
31
src/components/homepage/SocialCard.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
|
||||
import { FiExternalLink } from "react-icons/fi";
|
||||
|
||||
export interface SocialCardProps {
|
||||
children: React.ReactNode;
|
||||
color: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const SocialCard = ({
|
||||
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>
|
||||
);
|
||||
};
|
||||
345
src/components/tools/FrequencyCalculator.tsx
Normal file
345
src/components/tools/FrequencyCalculator.tsx
Normal file
@@ -0,0 +1,345 @@
|
||||
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);
|
||||
const calculatedNumChannels = Math.floor(
|
||||
(selectedRegion.freq_end - selectedRegion.freq_start) /
|
||||
(selectedRegion.spacing + selectedModemPreset.bw / 1000),
|
||||
);
|
||||
|
||||
setNumChannels(calculatedNumChannels);
|
||||
|
||||
let updatedChannel = channel;
|
||||
if (updatedChannel >= calculatedNumChannels) {
|
||||
updatedChannel = 0;
|
||||
}
|
||||
|
||||
setChannel(updatedChannel);
|
||||
|
||||
setChannelFrequency(
|
||||
selectedRegion.freq_start +
|
||||
selectedModemPreset.bw / 2000 +
|
||||
updatedChannel * (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 + 1}
|
||||
</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>
|
||||
);
|
||||
};
|
||||
208
src/css/custom.css
Normal file
208
src/css/custom.css
Normal file
@@ -0,0 +1,208 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* You can override the default Infima variables here. */
|
||||
:root {
|
||||
--ifm-color-primary: #eab167;
|
||||
--ifm-color-primary-dark: rgb(175, 102, 33);
|
||||
--ifm-color-primary-darker: rgb(165, 85, 31);
|
||||
--ifm-color-primary-darkest: rgb(136, 75, 26);
|
||||
--ifm-color-primary-light: rgb(203, 130, 70);
|
||||
--ifm-color-primary-lighter: rgb(212, 164, 102);
|
||||
--ifm-color-primary-lightest: rgb(224, 199, 146);
|
||||
--ifm-list-item-margin: 0;
|
||||
--ifm-code-font-size: 95%;
|
||||
--ifm-z-index-fixed: 1;
|
||||
--accent: #eab167;
|
||||
--base: #f3f4f6;
|
||||
--primary: #ffffff;
|
||||
--secondary: #e5e7eb;
|
||||
--tertiary: #d1d5db;
|
||||
--mute: #6b7280;
|
||||
--primaryInv: #242526;
|
||||
--secondaryInv: #18191a;
|
||||
--tertiaryInv: #4c4e50;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--base: #38393b;
|
||||
--primary: #242526;
|
||||
--secondary: #18191a;
|
||||
--tertiary: #4c4e50;
|
||||
--mute: #9ca3af;
|
||||
--primaryInv: #ffffff;
|
||||
--secondaryInv: #e5e7eb;
|
||||
--tertiaryInv: #d1d5db;
|
||||
}
|
||||
|
||||
.docusaurus-highlight-code-line {
|
||||
background-color: rgb(72, 77, 91);
|
||||
display: block;
|
||||
margin: 0 calc(-1 * var(--ifm-pre-padding));
|
||||
padding: 0 var(--ifm-pre-padding);
|
||||
}
|
||||
|
||||
.header-github-link:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.header-github-link:before {
|
||||
content: "";
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")
|
||||
no-repeat;
|
||||
}
|
||||
|
||||
html[data-theme="dark"] .header-github-link:before {
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")
|
||||
no-repeat;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 10px rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(0.98);
|
||||
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.cta--button {
|
||||
--ifm-button-border-color: var(--ifm-link-color);
|
||||
color: var(--ifm-link-color);
|
||||
}
|
||||
|
||||
.split-container {
|
||||
display: flex;
|
||||
}
|
||||
.split-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.indexCtasBody {
|
||||
--ifm-button-size-multiplier: 1.6;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.indexCtasBody a:last-of-type {
|
||||
margin: 20px 36px;
|
||||
}
|
||||
|
||||
.indexCtas {
|
||||
--ifm-button-size-multiplier: 1.6;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.indexCtas a {
|
||||
color: var(--ifm-font-color-base-inverse);
|
||||
--ifm-button-border-color: var(--ifm-font-color-base-inverse);
|
||||
}
|
||||
|
||||
.indexCtas a:hover {
|
||||
color: rgb(77, 77, 77);
|
||||
--ifm-button-border-color: rgb(77, 77, 77);
|
||||
}
|
||||
|
||||
.indexCtas a:last-of-type {
|
||||
margin: 20px 36px;
|
||||
}
|
||||
|
||||
.theme-doc-markdown {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
a + .navbar__link {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
a + .navbar__link > svg {
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
.markdown {
|
||||
@apply prose prose-lg;
|
||||
}
|
||||
.markdown img {
|
||||
@apply rounded-lg mx-auto;
|
||||
}
|
||||
[data-theme="dark"] .markdown {
|
||||
@apply prose-invert;
|
||||
}
|
||||
[data-theme="dark"] .hideDark {
|
||||
@apply hidden;
|
||||
}
|
||||
[data-theme="dark"] .hideLight {
|
||||
@apply block;
|
||||
}
|
||||
.hideLight {
|
||||
@apply hidden;
|
||||
}
|
||||
.hideDark {
|
||||
@apply block;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown :where(li):not(:where([class~="not-prose"] *)) {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown
|
||||
:where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"] *)) {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: var(--ifm-heading-font-weight);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: var(--ifm-h1-font-size);
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: var(--ifm-h2-font-size);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--ifm-h3-font-size);
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: var(--ifm-h4-font-size);
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: var(--ifm-h5-font-size);
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: var(--ifm-h6-font-size);
|
||||
}
|
||||
29
src/hooks/useFilteredNetworks.tsx
Normal file
29
src/hooks/useFilteredNetworks.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
|
||||
import { Showcase } from "../utils/apiTypes";
|
||||
import { useSelectedTags } from "./useSelectedTags";
|
||||
|
||||
const filterNetworks = (
|
||||
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),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const useFilteredNetworks = (networks: Showcase[]) => {
|
||||
const selectedTags = useSelectedTags();
|
||||
return React.useMemo(
|
||||
() => filterNetworks(networks, selectedTags),
|
||||
[selectedTags],
|
||||
);
|
||||
};
|
||||
16
src/hooks/useSelectedTags.tsx
Normal file
16
src/hooks/useSelectedTags.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
|
||||
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]);
|
||||
|
||||
return selectedTags;
|
||||
};
|
||||
44
src/pages/credits/_components/Avatar.tsx
Normal file
44
src/pages/credits/_components/Avatar.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
|
||||
export interface avatarProps {
|
||||
imgUrl: string;
|
||||
name?: string;
|
||||
userName?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface avatarLayoutProps {
|
||||
list: list[];
|
||||
}
|
||||
|
||||
export const Avatar = ({
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
165
src/pages/credits/index.tsx
Normal file
165
src/pages/credits/index.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import React from "react";
|
||||
import Layout from "@theme/Layout";
|
||||
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
|
||||
we’re 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;
|
||||
146
src/pages/downloads/_components/DownloadCard.tsx
Normal file
146
src/pages/downloads/_components/DownloadCard.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React from "react";
|
||||
|
||||
export interface downloadCardProps {
|
||||
client: string;
|
||||
imgUrl: string;
|
||||
url: string;
|
||||
imgUrl2: string;
|
||||
url2: string;
|
||||
notes: string;
|
||||
buttonText: string;
|
||||
}
|
||||
|
||||
export const DownloadCard = ({
|
||||
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} />
|
||||
</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",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
/>
|
||||
<a className="button disabled button--primary button--block"> </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>
|
||||
);
|
||||
};
|
||||
151
src/pages/downloads/_components/FirmwareCard.tsx
Normal file
151
src/pages/downloads/_components/FirmwareCard.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import React from "react";
|
||||
|
||||
import { DeviceFirmwareResource } from "../../../utils/apiTypes";
|
||||
|
||||
export interface releaseCardProps {
|
||||
variant: string;
|
||||
description: string;
|
||||
release?: DeviceFirmwareResource[];
|
||||
}
|
||||
|
||||
export const FirmwareCard = ({
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
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"> </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>
|
||||
);
|
||||
};
|
||||
18
src/pages/downloads/_components/HeaderText.tsx
Normal file
18
src/pages/downloads/_components/HeaderText.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
|
||||
export const HeaderText = ({ type, text, link }): JSX.Element => {
|
||||
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>
|
||||
);
|
||||
};
|
||||
288
src/pages/downloads/index.tsx
Normal file
288
src/pages/downloads/index.tsx
Normal file
@@ -0,0 +1,288 @@
|
||||
import React from "react";
|
||||
|
||||
import { FaAndroid, FaApple } from "react-icons/fa";
|
||||
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
ComputerDesktopIcon,
|
||||
GlobeAltIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import Layout from "@theme/Layout";
|
||||
|
||||
const Firmware = (): JSX.Element => {
|
||||
return (
|
||||
<Layout
|
||||
title="Завантаження"
|
||||
description="Клієнти для наших сервісів для різних платформ."
|
||||
>
|
||||
<div className="container mt-8 flex flex-col gap-3">
|
||||
<div className="flex w-full overflow-hidden rounded-xl">
|
||||
<div className="flex w-1/5 bg-gradient-to-r from-rose-500 to-primary">
|
||||
<span className="m-auto h-20 text-4xl font-black">#IRC</span>
|
||||
</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="#"
|
||||
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="#"
|
||||
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="#"
|
||||
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">
|
||||
<a href="https://sr.ht/~emersion/gamja/">gamja</a> веб клієнт
|
||||
для IRC.
|
||||
</div>
|
||||
<div className="card__footer mt-auto">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://irc.dead.guru"
|
||||
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"
|
||||
>
|
||||
irc.dead.guru
|
||||
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* */}
|
||||
<div className="flex w-full overflow-hidden rounded-xl">
|
||||
<div className="flex w-1/5 bg-gradient-to-r from-green-500 to-primary">
|
||||
<span className="m-auto h-20 text-4xl font-black">#MATRIX</span>
|
||||
</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="#"
|
||||
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="#"
|
||||
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="#"
|
||||
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">
|
||||
fluffychat веб клієнт для matrix.
|
||||
</div>
|
||||
<div className="card__footer mt-auto">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://xmpp.dead.guru:5281/conversejs"
|
||||
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"
|
||||
>
|
||||
https://fluffychat.im/
|
||||
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* */}
|
||||
<div className="flex w-full overflow-hidden rounded-xl">
|
||||
<div className="flex w-1/5 bg-gradient-to-r from-yellow-500 to-primary">
|
||||
<span className="m-auto h-20 text-4xl font-black">#XMPP</span>
|
||||
</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="#"
|
||||
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="#"
|
||||
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="#"
|
||||
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">
|
||||
<a href="https://conversejs.org/">Converse.js</a> веб клієнт для
|
||||
XMPP.
|
||||
</div>
|
||||
<div className="card__footer mt-auto">
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://xmpp.dead.guru:5281/conversejs"
|
||||
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"
|
||||
>
|
||||
https://xmpp.dead.guru:5281/conversejs
|
||||
<ArrowTopRightOnSquareIcon className="m-auto ml-2 h-4" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Firmware;
|
||||
7
src/pages/e/index.md
Normal file
7
src/pages/e/index.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
id: e
|
||||
title: Dead Channel Settings
|
||||
sidebar_label: e
|
||||
---
|
||||
|
||||
## TBD
|
||||
300
src/pages/index.tsx
Normal file
300
src/pages/index.tsx
Normal file
@@ -0,0 +1,300 @@
|
||||
import "react-responsive-carousel/lib/styles/carousel.min.css"; // requires a loader
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Carousel } from "react-responsive-carousel";
|
||||
|
||||
import Head from "@docusaurus/Head";
|
||||
import Link from "@docusaurus/Link";
|
||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
||||
import Layout from "@theme/Layout";
|
||||
|
||||
import { SocialCard, SocialCardProps } from "../components/homepage/SocialCard";
|
||||
|
||||
const features = [
|
||||
{
|
||||
title: "Основа мережі — Спілкування",
|
||||
imageUrl: "img/homepage/messages.svg",
|
||||
description: (
|
||||
<>
|
||||
Мережу в першу чергу, створюють люди, а не технології. Спілкування
|
||||
дозволяє нам зберігати зв'язок з друзями та родиною, а також будувати
|
||||
новий, цікавий і вільний світ.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Захист",
|
||||
imageUrl: "img/homepage/encryption.svg",
|
||||
description: (
|
||||
<>
|
||||
Ми намагаємося захистити вашу приватність, використовуючи шифрування де
|
||||
це можливо та необхідно. Ви можете використовувати власні ключі
|
||||
шифрування, щоб забезпечити максимальний рівень захисту. Ми також
|
||||
відкриті для аудиту та вдосконалення наших методів шифрування та
|
||||
захисту.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Незалежність",
|
||||
imageUrl: "img/homepage/platforms.svg",
|
||||
description: (
|
||||
<>
|
||||
Свобода спілкування не повинна бути обмежена. Ми намагаємося
|
||||
підтримувати якомога більше платформ, щоб ви могли спілкуватися з
|
||||
друзями та родиною, незалежно від того, який пристрій ви використовуєте.
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Open Source",
|
||||
imageUrl: "img/homepage/opensource.svg",
|
||||
description: (
|
||||
<>
|
||||
Мережа побудована на основі відкритих проектів, що дозволяє вам приймати
|
||||
участь в розвитку та вдосконаленні мережі.
|
||||
</>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const SocialCards: SocialCardProps[] = [
|
||||
{
|
||||
color: "bg-[#3875EA]",
|
||||
link: "https://irc.dead.guru",
|
||||
children: (
|
||||
<img alt="irc" className="m-auto h-10" src="/img/services/irc.svg" />
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "bg-[#FFFFFF]",
|
||||
link: "https://matrix.to/#/#adhd_lab:hypogea.org",
|
||||
children: (
|
||||
<img
|
||||
alt="matrix"
|
||||
className="m-auto h-14"
|
||||
src="/img/services/Matrix_logo.svg"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "bg-[#95c0d9]",
|
||||
link: "https://xmpp.dead.guru:5281/",
|
||||
children: (
|
||||
<img
|
||||
alt="xmpp"
|
||||
className="m-auto h-14"
|
||||
src="/img/services/XMPP_logo.svg"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "bg-[#f05539]",
|
||||
link: "https://git.dead.guru/",
|
||||
children: (
|
||||
<img alt="git" className="m-auto h-14" src="/img/services/git_logo.svg" />
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "bg-[#9e9b9a]",
|
||||
link: "https://mine.dead.guru",
|
||||
children: (
|
||||
<img
|
||||
alt="minecraft"
|
||||
className="m-auto h-16"
|
||||
src="/img/services/minecraft_logo.svg"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "bg-[#39322b]",
|
||||
link: "https://bookhouse.hypogea.org/",
|
||||
children: (
|
||||
<img
|
||||
alt="Calibre"
|
||||
className="m-auto h-12"
|
||||
src="/img/services/Calibre_logo.png"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
color: "bg-[#6366f1]",
|
||||
link: "https://pi.dead.guru",
|
||||
children: (
|
||||
<img
|
||||
alt="reddit"
|
||||
className="m-auto h-20"
|
||||
src="/img/services/Pixelfed_logo.svg"
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
function Home() {
|
||||
const context = useDocusaurusContext();
|
||||
const { siteConfig } = context;
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
<meta property="og:title" content="Dead Network" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://meshtastic.org/design/web/social-preview-1200x630.png"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Надаємо різноманітні сервіси-платформи, які дають змогу окремим особам і спільнотам спілкуватися, ділитися та розвиватися разом."
|
||||
/>
|
||||
<meta property="og:url" content="https://dead.guru/" />
|
||||
<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="Dead Logo"
|
||||
className="header__logo"
|
||||
src={useBaseUrl("img/dead-banner.png")}
|
||||
/>
|
||||
</h1>
|
||||
<p className="hero__subtitle">{siteConfig.tagline}</p>
|
||||
<div className="indexCtas">
|
||||
<Link className="button button--lg" to="/docs/introduction">
|
||||
Дізнатися більше
|
||||
</Link>
|
||||
<Link className="button button--lg" to="/docs/getting-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">Приєднуйтесь до нас.</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">
|
||||
Доєднайтеся до "Мертвої" мережі в 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. Оберіть платформу</h3>
|
||||
</div>
|
||||
<div
|
||||
className="card__body"
|
||||
style={{ display: "flex", justifyContent: "center" }}
|
||||
>
|
||||
<p>
|
||||
Для старту почніть з вибору платформи:
|
||||
<ul>
|
||||
<li>IRC</li>
|
||||
<li>XMPP</li>
|
||||
<li>Matrix</li>
|
||||
<li>
|
||||
Або взагалі проігноруйте сервіси повідомлень і почніть з{" "}
|
||||
<a href="">
|
||||
<strong>ігрових серверів</strong>
|
||||
</a>
|
||||
!
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div
|
||||
className="card__header"
|
||||
style={{ display: "flex", justifyContent: "space-between" }}
|
||||
>
|
||||
<h3>2. Завантажте і зареєструйтесь!</h3>
|
||||
</div>
|
||||
<div
|
||||
className="card__body"
|
||||
style={{ display: "flex", justifyContent: "center" }}
|
||||
>
|
||||
<p>
|
||||
<a href="/downloads">
|
||||
<strong>Завантажте</strong>
|
||||
</a>{" "}
|
||||
додаток для обраної платформи, перегляньте наш{" "}
|
||||
<a href="">
|
||||
<strong>мануал по налаштуванню</strong>
|
||||
</a>{" "}
|
||||
і зареєструйтесь в сервісі мережі.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div
|
||||
className="card__header"
|
||||
style={{ display: "flex", justifyContent: "space-between" }}
|
||||
>
|
||||
<h3>3. Приєднайтесь до чату</h3>
|
||||
</div>
|
||||
<div
|
||||
className="card__body"
|
||||
style={{ display: "flex", justifyContent: "center" }}
|
||||
>
|
||||
<p>
|
||||
В залежності від ваших інтересів і обраної платформи, ви
|
||||
можете приєднатися до:
|
||||
<ul>
|
||||
<li>Загальний чат</li>
|
||||
<li>ADHD Lab</li>
|
||||
<li>Ігрові чати</li>
|
||||
<li>HAM загальний чат</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<br />
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
110
src/pages/showcase/_components/Card.tsx
Normal file
110
src/pages/showcase/_components/Card.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import React from "react";
|
||||
|
||||
import { Showcase } from "../../../utils/apiTypes";
|
||||
import { mapUrl } from "../../../utils/map";
|
||||
import { CardTags } from "./CardTags";
|
||||
|
||||
export interface CardProps {
|
||||
network: Showcase;
|
||||
}
|
||||
|
||||
export const Card = React.memo(({ network }: CardProps) => (
|
||||
<div className="card">
|
||||
<div className="card__image">
|
||||
<div style={{ height: "140px" }}>
|
||||
<img src={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">
|
||||
<button
|
||||
className="button disabled button--primary button--block"
|
||||
style={{ marginBottom: "0.5rem" }}
|
||||
>
|
||||
|
||||
</button>
|
||||
<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>
|
||||
);
|
||||
29
src/pages/showcase/_components/CardTags.tsx
Normal file
29
src/pages/showcase/_components/CardTags.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
|
||||
import { ShowcaseTag } from "../../../utils/apiTypes";
|
||||
|
||||
export interface CardTagsProps {
|
||||
tags: ShowcaseTag[];
|
||||
}
|
||||
|
||||
export const CardTags = ({ tags }: CardTagsProps) => {
|
||||
return (
|
||||
<div>
|
||||
{tags.map(({ color, label }) => {
|
||||
return (
|
||||
<span
|
||||
className="badge"
|
||||
key={label}
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
marginRight: "0.3rem",
|
||||
userSelect: "none",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
90
src/pages/showcase/_components/Filters.tsx
Normal file
90
src/pages/showcase/_components/Filters.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React from "react";
|
||||
|
||||
import { FiHeart } from "react-icons/fi";
|
||||
import useSWR from "swr";
|
||||
|
||||
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
||||
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 { 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) => {
|
||||
const { label, color } = tag;
|
||||
const id = `showcase_checkbox_id_${tag};`;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={tag.id}
|
||||
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>
|
||||
);
|
||||
};
|
||||
318
src/pages/showcase/_components/Network.tsx
Normal file
318
src/pages/showcase/_components/Network.tsx
Normal file
@@ -0,0 +1,318 @@
|
||||
import React from "react";
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
||||
import { Showcase } from "../../../utils/apiTypes";
|
||||
import { fetcher } from "../../../utils/swr";
|
||||
|
||||
interface NetworkProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const Network = ({ id }: NetworkProps): JSX.Element => {
|
||||
const { siteConfig } = useDocusaurusContext();
|
||||
|
||||
const { data, error } = useSWR<Showcase>(
|
||||
`${siteConfig.customFields.API_URL}/showcase/${id}`,
|
||||
fetcher,
|
||||
);
|
||||
|
||||
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>
|
||||
|
||||
<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) => (
|
||||
<div
|
||||
key={material.id}
|
||||
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%" }}
|
||||
>
|
||||
|
||||
</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%" }}
|
||||
>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
67
src/pages/showcase/_components/NetworkSection.tsx
Normal file
67
src/pages/showcase/_components/NetworkSection.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
|
||||
import { Showcase } from "../../../utils/apiTypes";
|
||||
import { Card, PlaceholderCard } from "./Card";
|
||||
|
||||
interface NetworkSectionProps {
|
||||
title: string;
|
||||
icon?: JSX.Element;
|
||||
iconColor?: string;
|
||||
networks?: Showcase[];
|
||||
}
|
||||
|
||||
export const NetworkSection = ({
|
||||
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>
|
||||
);
|
||||
};
|
||||
52
src/pages/showcase/_components/Networks.tsx
Normal file
52
src/pages/showcase/_components/Networks.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
|
||||
import { FiHeart, FiSearch } from "react-icons/fi";
|
||||
import useSWR from "swr";
|
||||
|
||||
import useDocusaurusContext from "@docusaurus/useDocusaurusContext";
|
||||
import { useSelectedTags } from "../../../hooks/useSelectedTags";
|
||||
|
||||
import { useFilteredNetworks } from "../../../hooks/useFilteredNetworks";
|
||||
import { Showcase } from "../../../utils/apiTypes";
|
||||
import { fetcher } from "../../../utils/swr";
|
||||
import { NetworkSection } from "./NetworkSection";
|
||||
|
||||
export const Networks = (): JSX.Element => {
|
||||
const { siteConfig } = useDocusaurusContext();
|
||||
|
||||
const { data, error } = useSWR<Showcase[]>(
|
||||
`${siteConfig.customFields.API_URL}/showcase`,
|
||||
fetcher,
|
||||
);
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
111
src/pages/showcase/_components/TagSelect.tsx
Normal file
111
src/pages/showcase/_components/TagSelect.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import "url-search-params-polyfill";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { useHistory, useLocation } from "@docusaurus/router";
|
||||
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;
|
||||
}
|
||||
|
||||
export function readSearchTags(search: string): string[] {
|
||||
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();
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
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>
|
||||
);
|
||||
35
src/pages/showcase/index.tsx
Normal file
35
src/pages/showcase/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import "url-search-params-polyfill";
|
||||
|
||||
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 Dead community"
|
||||
>
|
||||
<main className="margin-vert--lg">
|
||||
{id ? (
|
||||
<Network id={id} />
|
||||
) : (
|
||||
<>
|
||||
<Filters />
|
||||
<Networks />
|
||||
</>
|
||||
)}
|
||||
</main>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Showcase;
|
||||
151
src/pages/tools/OEM.tsx
Normal file
151
src/pages/tools/OEM.tsx
Normal file
@@ -0,0 +1,151 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { fromByteArray, toByteArray } from "base64-js";
|
||||
|
||||
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());
|
||||
|
||||
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 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.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]) => (
|
||||
<option key={name} 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;
|
||||
2
src/theme/NotFound.d.ts
vendored
Normal file
2
src/theme/NotFound.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="react" />
|
||||
export default function NotFound(): JSX.Element;
|
||||
33
src/theme/NotFound.js
Normal file
33
src/theme/NotFound.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import Translate, { translate } from "@docusaurus/Translate";
|
||||
import { PageMetadata } from "@docusaurus/theme-common";
|
||||
import Layout from "@theme/Layout";
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<>
|
||||
<PageMetadata
|
||||
title={translate({
|
||||
id: "theme.NotFound.title",
|
||||
message: "Page Not Found",
|
||||
})}
|
||||
/>
|
||||
<Layout>
|
||||
<main className="container margin-vert--xl">
|
||||
<div className="row">
|
||||
<div className="col col--6 col--offset-3">
|
||||
<h1 className="hero__title">
|
||||
<Translate
|
||||
id="theme.NotFound.title"
|
||||
description="The title of the 404 page"
|
||||
>
|
||||
404 - Page Not Found
|
||||
</Translate>
|
||||
</h1>
|
||||
<img src="/design/chirpy.png" alt='Chirpy' />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
65
src/utils/apiTypes.ts
Normal file
65
src/utils/apiTypes.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export interface Showcase {
|
||||
id: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
body: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
tags: ShowcaseTag[];
|
||||
nodes?: Node[];
|
||||
materials?: Material[];
|
||||
author?: Author;
|
||||
authorId?: string;
|
||||
}
|
||||
|
||||
export interface ShowcaseTag {
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
color: string;
|
||||
|
||||
showcases?: Showcase[];
|
||||
}
|
||||
|
||||
export interface Node {
|
||||
id: string;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
|
||||
showcase?: Showcase;
|
||||
showcaseId?: string;
|
||||
}
|
||||
|
||||
export interface Material {
|
||||
id: string;
|
||||
name: string;
|
||||
details: string;
|
||||
image: string;
|
||||
url: string;
|
||||
|
||||
showcases?: Showcase[];
|
||||
}
|
||||
|
||||
export interface Author {
|
||||
id: string;
|
||||
githubUsername: string;
|
||||
bio: string;
|
||||
|
||||
showcase?: Showcase[];
|
||||
}
|
||||
|
||||
export interface DeviceFirmwareResource {
|
||||
id: string;
|
||||
title: string;
|
||||
page_url?: string;
|
||||
zip_url?: string;
|
||||
}
|
||||
|
||||
export interface FirmwareReleases {
|
||||
releases: {
|
||||
stable: DeviceFirmwareResource[];
|
||||
alpha: DeviceFirmwareResource[];
|
||||
};
|
||||
pullRequests: DeviceFirmwareResource[];
|
||||
}
|
||||
1
src/utils/breakpoints.ts
Normal file
1
src/utils/breakpoints.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280 };
|
||||
23
src/utils/calculateADC.ts
Normal file
23
src/utils/calculateADC.ts
Normal file
@@ -0,0 +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);
|
||||
}
|
||||
13
src/utils/map.ts
Normal file
13
src/utils/map.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
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})`,
|
||||
);
|
||||
|
||||
return `https://api.mapbox.com/styles/v1/mapbox/satellite-v9/static/${nodeCoords}/auto/${width}x${height}@2x?access_token=${access_token}`;
|
||||
};
|
||||
22
src/utils/showcase.ts
Normal file
22
src/utils/showcase.ts
Normal file
@@ -0,0 +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;
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
1
src/utils/swr.ts
Normal file
1
src/utils/swr.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const fetcher = (url: string) => fetch(url).then((res) => res.json());
|
||||
Reference in New Issue
Block a user