Compare commits
No commits in common. "master" and "master-old" have entirely different histories.
master
...
master-old
@ -1,12 +0,0 @@
|
||||
# For cross compilation
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
linker = "aarch64-linux-gnu-gcc"
|
||||
|
||||
[target.armv7-unknown-linux-musleabi]
|
||||
linker = "arm-linux-gnueabi-gcc"
|
||||
|
||||
[target.arm-unknown-linux-musleabi]
|
||||
linker = "arm-linux-gnueabi-gcc"
|
||||
|
||||
[target.armv7-unknown-linux-gnueabihf]
|
||||
linker = "arm-linux-gnueabihf-gcc"
|
15
Cargo.toml
15
Cargo.toml
@ -1,19 +1,10 @@
|
||||
[package]
|
||||
name = "aw-lights"
|
||||
version = "0.1.0"
|
||||
authors = ["Austen Adler <agadler@austenadler.com>"]
|
||||
authors = ["root"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rppal = "0.7"
|
||||
ws2818-rgb-led-spi-driver = { path = "lib-ws2818-rgb-led-spi-driver/" }
|
||||
serde = {version = "1.0", features = ["derive"]}
|
||||
actix-web = {version = "3", default_features = false}
|
||||
rust-embed="6.0.0"
|
||||
hex = "0.4.3"
|
||||
serde_json = "1"
|
||||
actix-web-static-files = "3.0"
|
||||
|
||||
[build-dependencies]
|
||||
actix-web-static-files = "3.0"
|
||||
|
11
Makefile
11
Makefile
@ -1,11 +0,0 @@
|
||||
.PHONY: build deploy run
|
||||
|
||||
build:
|
||||
cargo build --target=armv7-unknown-linux-gnueabihf
|
||||
|
||||
deploy: build
|
||||
arm-linux-gnueabihf-strip ./target/armv7-unknown-linux-gnueabihf/debug/aw-lights
|
||||
scp ./target/armv7-unknown-linux-gnueabihf/debug/aw-lights pi:aw-lights-bin
|
||||
|
||||
run: deploy
|
||||
ssh pi ./aw-lights
|
13
README.adoc
13
README.adoc
@ -1,13 +0,0 @@
|
||||
# aw-lights
|
||||
|
||||
----
|
||||
# Install packages
|
||||
xbps-install fake-hwclock
|
||||
ln -s /etc/sv/fake-hwclock/ /var/service/
|
||||
|
||||
# Enable SPI
|
||||
echo 'dtparam=spi=on' >>/boot/config.txt
|
||||
|
||||
# Allow big buf for SPI
|
||||
echo 'options spidev bufsiz=65536' > /etc/modprobe.d/spidev.conf
|
||||
----
|
12
build.rs
12
build.rs
@ -1,12 +0,0 @@
|
||||
use actix_web_static_files::NpmBuild;
|
||||
fn main() {
|
||||
NpmBuild::new("./web")
|
||||
.install()
|
||||
.unwrap()
|
||||
.run("build")
|
||||
.unwrap()
|
||||
.target("./web/public")
|
||||
.to_resource_dir()
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
14
entr.sh
14
entr.sh
@ -1,14 +0,0 @@
|
||||
CMD="$(cat <<'EOF'
|
||||
set -euo pipefail
|
||||
HEIGHT="$(($(tput lines) - 1))"
|
||||
clear
|
||||
for i in check fmt build clippy; do
|
||||
echo "+ cargo "${i}""
|
||||
cargo --color=always "${i}" |& head -n "${HEIGHT}"
|
||||
done
|
||||
EOF
|
||||
)"
|
||||
{
|
||||
fd -tf -ers
|
||||
printf "%s\n" "Cargo.toml"
|
||||
} | entr bash -c "${CMD}"
|
@ -1 +0,0 @@
|
||||
hi
|
4
lib-ws2818-rgb-led-spi-driver/.gitignore
vendored
4
lib-ws2818-rgb-led-spi-driver/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/debug/
|
||||
/target/
|
||||
/Cargo.lock
|
||||
**/*.rs.bk
|
@ -1,27 +0,0 @@
|
||||
[package]
|
||||
name = "ws2818-rgb-led-spi-driver"
|
||||
description = "Simple, stripped down, educational, no_std-compatible driver for WS28XX (WS2811/12) RGB LEDs. Uses SPI device for timing/clock, and works definitely on Linux/Raspberry Pi."
|
||||
version = "2.0.0"
|
||||
authors = ["Philipp Schuster <phip1611@gmail.com>", "Austen Adler <>"]
|
||||
edition = "2018"
|
||||
exclude = [
|
||||
"examples",
|
||||
".travis.yml",
|
||||
]
|
||||
keywords = ["spi", "ws2811", "ws2812", "ws2818", "neopixel"]
|
||||
categories = ["hardware-support", "no-std"]
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
homepage = "https://github.com/phip1611/ws2818-rgb-led-spi-driver"
|
||||
repository = "https://github.com/phip1611/ws2818-rgb-led-spi-driver"
|
||||
documentation = "https://docs.rs/ws2818-rgb-led-spi-driver/"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
# by default this crate needs "std" and uses "spidev" to access SPI device on Linux
|
||||
default = ["adapter_spidev"]
|
||||
adapter_spidev = ["spidev"]
|
||||
|
||||
[dependencies]
|
||||
spidev = { version = "0.4.1", optional = true }
|
@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Philipp Schuster
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1 +0,0 @@
|
||||
This library is almost verbatim from https://github.com/phip1611/ws2818-rgb-led-spi-driver/ with some cleanup.
|
@ -1,68 +0,0 @@
|
||||
//! Generic Hardware Abstraction Layer, no_std-compatible.
|
||||
|
||||
use crate::encoding::encode_rgb_slice;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
|
||||
/// SPI-device abstraction.
|
||||
pub trait HardwareDev {
|
||||
fn write_all(&mut self, encoded_data: &[u8]) -> Result<(), String>;
|
||||
}
|
||||
|
||||
pub trait Ws28xxAdapter {
|
||||
/// Returns a reference to the hardware device.
|
||||
/// This function only needs to be implemented once in the generic adapter.
|
||||
fn get_hw_dev(&mut self) -> &mut Box<dyn HardwareDev>;
|
||||
|
||||
/// Encodes RGB values and write them via the hardware device to the LEDs. The length of the vector
|
||||
/// is the number of LEDs you want to write to. *Note* that if you have performance critical
|
||||
/// applications (like you need a signal on the LEDS on a given time) it's a better idea
|
||||
/// to encode the data earlier by yourself using `crate::encoding`-module and calling
|
||||
/// `Ws28xxAdapter::write_encoded_rgb`. Otherwise and if your device is slow the encoding
|
||||
/// could cost a few microseconds to milliseconds - depending on your amount of data and machine.
|
||||
fn write_rgb(&mut self, rgb_data: &[(u8, u8, u8)]) -> Result<(), String> {
|
||||
let encoded_data = encode_rgb_slice(rgb_data);
|
||||
self.write_encoded_rgb(&encoded_data)
|
||||
}
|
||||
|
||||
/// Clears all LEDs. Sets each to (0, 0, 0).
|
||||
// fn clear(&mut self, num_leds: usize) {
|
||||
// let data = vec![(0, 0, 0); num_leds];
|
||||
// self.write_rgb(&data).expect("Critical data write error!");
|
||||
// }
|
||||
|
||||
/// Directly writes encoded RGB values via hardware device to the LEDs. This method and the encoded data
|
||||
/// must fulfill the restrictions given by [`crate::timings`] and [`crate::encoding`] if the hardware
|
||||
/// device uses the specified frequency in [`crate::timings::PI_SPI_HZ`].
|
||||
fn write_encoded_rgb(&mut self, encoded_data: &[u8]) -> Result<(), String> {
|
||||
self.get_hw_dev().write_all(&encoded_data)
|
||||
.map_err(|_| {
|
||||
format!(
|
||||
"Failed to send {} bytes via the specified hardware device. If you use SPI on Linux Perhaps your SPI buffer is too small!\
|
||||
Check https://www.raspberrypi.org/forums/viewtopic.php?p=309582#p309582 for example.",
|
||||
encoded_data.len()
|
||||
)}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform agnostic (generic) adapter that connects your application via your specified
|
||||
/// hardware interface to your Ws28xx LEDs. *Handle this as something like an abstract class
|
||||
/// for concrete implementations!* This works in `#[no-std]`-environments.
|
||||
pub struct Ws28xxGenAdapter {
|
||||
hw: Box<dyn HardwareDev>,
|
||||
}
|
||||
|
||||
impl Ws28xxGenAdapter {
|
||||
/// Constructor that stores the hardware device in the adapter.
|
||||
pub fn new(hw: Box<dyn HardwareDev>) -> Self {
|
||||
Self { hw }
|
||||
}
|
||||
}
|
||||
|
||||
// Implement the getter for the hardware device.
|
||||
impl Ws28xxAdapter for Ws28xxGenAdapter {
|
||||
fn get_hw_dev(&mut self) -> &mut Box<dyn HardwareDev> {
|
||||
&mut self.hw
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
//! Adapter for SPI-dev on Linux-systems. This requires std.
|
||||
|
||||
use crate::adapter_gen::{HardwareDev, Ws28xxAdapter, Ws28xxGenAdapter};
|
||||
use crate::timings::PI_SPI_HZ;
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::{String, ToString};
|
||||
use spidev::{SpiModeFlags, Spidev, SpidevOptions};
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
/// Wrapper around Spidev.
|
||||
struct SpiHwAdapterDev(Spidev);
|
||||
|
||||
// Implement Hardwareabstraction for device.
|
||||
impl HardwareDev for SpiHwAdapterDev {
|
||||
fn write_all(&mut self, encoded_data: &[u8]) -> Result<(), String> {
|
||||
self.0.write_all(&encoded_data)
|
||||
.map_err(|_| {
|
||||
format!(
|
||||
"Failed to send {} bytes via SPI. Perhaps your SPI buffer is too small!\
|
||||
Check https://www.raspberrypi.org/forums/viewtopic.php?p=309582#p309582 for example.",
|
||||
encoded_data.len()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SpiHwAdapterDev {
|
||||
/// Connects your application with the SPI-device of your device.
|
||||
/// This uses the `spidev`-crate. Returns a new adapter object
|
||||
/// for the Ws28xx LEDs.
|
||||
///
|
||||
/// * `dev` - Device name. Probably "/dev/spidev0.0" if available.
|
||||
///
|
||||
/// Fails if connection to SPI can't be established.
|
||||
pub fn new(dev: &str) -> io::Result<Self> {
|
||||
let mut spi = Spidev::open(dev)?;
|
||||
let options = SpidevOptions::new()
|
||||
.bits_per_word(8)
|
||||
// According to https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md
|
||||
.max_speed_hz(PI_SPI_HZ)
|
||||
.mode(SpiModeFlags::SPI_MODE_0)
|
||||
.build();
|
||||
spi.configure(&options)?;
|
||||
Ok(Self(spi))
|
||||
}
|
||||
}
|
||||
|
||||
/// Adapter that connects your application via SPI to your Ws28xx LEDs.
|
||||
/// This requires an SPI device on your machine. This doesn't work
|
||||
/// with `#[no-std]`.
|
||||
pub struct Ws28xxSpiAdapter {
|
||||
gen: Ws28xxGenAdapter,
|
||||
}
|
||||
|
||||
impl Ws28xxSpiAdapter {
|
||||
/// Connects your application with the SPI-device of your device.
|
||||
/// This uses the `spidev`-crate. Returns a new adapter object
|
||||
/// for the Ws28xx LEDs.
|
||||
///
|
||||
/// * `dev` - Device name. Probably "/dev/spidev0.0" if available.
|
||||
///
|
||||
/// Fails if connection to SPI can't be established.
|
||||
pub fn new(dev: &str) -> Result<Self, String> {
|
||||
let spi = SpiHwAdapterDev::new(dev).map_err(|err| err.to_string())?;
|
||||
let spi = Box::from(spi);
|
||||
let gen = Ws28xxGenAdapter::new(spi);
|
||||
Ok(Self { gen })
|
||||
}
|
||||
}
|
||||
|
||||
impl Ws28xxAdapter for Ws28xxSpiAdapter {
|
||||
fn get_hw_dev(&mut self) -> &mut Box<dyn HardwareDev> {
|
||||
// forward to generic adapter
|
||||
// todo this is not the best code design because this requires
|
||||
// each sub adapter (like a sub class in OOP) to implement
|
||||
// this manually..
|
||||
self.gen.get_hw_dev()
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
use crate::timings::encoding::{
|
||||
SPI_BYTES_PER_DATA_BIT, WS2812_LOGICAL_ONE_BYTES, WS2812_LOGICAL_ZERO_BYTES,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
const COLORS: usize = 3;
|
||||
const BITS_PER_COLOR: usize = 8;
|
||||
|
||||
/// The number of bytes that must be send over SPI to transfer the data of a single RGB pixel.
|
||||
pub const SPI_BYTES_PER_RGB_PIXEL: usize = COLORS * BITS_PER_COLOR * SPI_BYTES_PER_DATA_BIT;
|
||||
|
||||
/// Encodes RGB-Values to the bytes that must be transferred via SPI MOSI.
|
||||
/// These SPI bytes represent the logical zeros and ones for WS2818.
|
||||
/// This counts in the constraints that come from [`crate::timings`]-module.
|
||||
/// Due to the specification the data is send this way:
|
||||
/// G7..G0,R7..R0,B7..B0
|
||||
///
|
||||
/// The resulting is [`SPI_BYTES_PER_RGB_PIXEL`] bytes long.
|
||||
pub fn encode_rgb(r: u8, g: u8, b: u8) -> [u8; SPI_BYTES_PER_RGB_PIXEL] {
|
||||
let mut spi_bytes: [u8; SPI_BYTES_PER_RGB_PIXEL] = [0; SPI_BYTES_PER_RGB_PIXEL];
|
||||
let mut spi_bytes_i = 0;
|
||||
let mut grb = [g, r, b]; // order specified by specification
|
||||
for color_bits in grb.iter_mut() {
|
||||
for _ in 0..8 {
|
||||
// for each bit of our color; starting with most significant
|
||||
// we encode now one color bit in two spi bytes (for proper timings along with our frequency)
|
||||
if 0b10000000 & *color_bits == 0 {
|
||||
spi_bytes[spi_bytes_i] = WS2812_LOGICAL_ZERO_BYTES[0];
|
||||
spi_bytes[spi_bytes_i + 1] = WS2812_LOGICAL_ZERO_BYTES[1];
|
||||
} else {
|
||||
spi_bytes[spi_bytes_i] = WS2812_LOGICAL_ONE_BYTES[0];
|
||||
spi_bytes[spi_bytes_i + 1] = WS2812_LOGICAL_ONE_BYTES[1];
|
||||
}
|
||||
*color_bits <<= 1;
|
||||
spi_bytes_i += 2; // update array index;
|
||||
}
|
||||
}
|
||||
debug_assert_eq!(spi_bytes_i, SPI_BYTES_PER_RGB_PIXEL);
|
||||
spi_bytes
|
||||
}
|
||||
|
||||
/// Encodes multiple RGB values in a slice. Uses [`encode_rgb`] for each value.
|
||||
pub fn encode_rgb_slice(data: &[(u8, u8, u8)]) -> Vec<u8> {
|
||||
let mut bytes = vec![];
|
||||
data.iter()
|
||||
.for_each(|rgb| bytes.extend_from_slice(&encode_rgb(rgb.0, rgb.1, rgb.2)));
|
||||
bytes
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
#![no_std]
|
||||
|
||||
#[cfg(feature = "adapter_spidev")]
|
||||
extern crate std;
|
||||
|
||||
#[macro_use]
|
||||
extern crate alloc;
|
||||
|
||||
pub mod adapter_gen; // generic [no_std] hardware abstraction
|
||||
#[cfg(feature = "adapter_spidev")]
|
||||
pub mod adapter_spi; // specific [std]-implementation
|
||||
|
||||
pub mod encoding;
|
||||
pub mod timings;
|
@ -1,48 +0,0 @@
|
||||
/// SPI Frequency
|
||||
pub const PI_SPI_HZ: u32 = 15_600_000;
|
||||
// 15.6 Mhz, see https://www.raspberrypi.org/documentation/hardware/raspberrypi/spi/README.md
|
||||
|
||||
// this means 1 / 15_600_000 * 1E9 ns/cycle => 64ns / cycle => 15.6 MBit/s
|
||||
//
|
||||
// See data sheet: https://cdn-shop.adafruit.com/datasheets/WS2812.pdf
|
||||
//
|
||||
// Timings of WS2818:
|
||||
//
|
||||
// pub const _T0H_NS: u64 = 350; // +-150ns tolerance
|
||||
// pub const _T0L_NS: u64 = 800; // +-150ns tolerance
|
||||
// pub const _T1H_NS: u64 = 700; // +-150ns tolerance
|
||||
// pub const _T1L_NS: u64 = 600; // +-150ns tolerance
|
||||
// pub const _TRESET: u64 = 50_000; // >50 µs
|
||||
//
|
||||
// One Wire Protocol on WS2812 requires the
|
||||
// - "logical 0 Bit" to be:
|
||||
// - T0H_NS +-150ns to be high
|
||||
// - T0L_NS +-150ns to be low (most of the time; at the end)
|
||||
// - "logical 1 Bit" to be:
|
||||
// - T1H_NS +-150ns to be high (most of the time; at the beginning)
|
||||
// - T1L_NS +-150ns to be low
|
||||
//
|
||||
// T0H_NS = 350ns +-150ns => 1_1111 ( 5 bits * 64ns per bit ~ 320ns)
|
||||
// T0L_NS = 800ns +-150ns => 000_0000_0000 (11 bits * 64ns per bit ~ 704ns)
|
||||
//
|
||||
// T1H_NS = 700ns +-150ns => 1_1111_1111 (9 bits * 64ns per bit ~ 576ns)
|
||||
// T1L_NS = 600ns +-150ns => 000_0000 (7 bits * 64ns per bit ~ 448ns)
|
||||
//
|
||||
// => !! we encode one data bit in two SPI byte for the proper timings !!
|
||||
|
||||
pub mod encoding {
|
||||
/// How many SPI bytes must be send for a single data bit.
|
||||
/// This number of bytes result in one logical zero or one
|
||||
/// on WS2818 LED.
|
||||
pub const SPI_BYTES_PER_DATA_BIT: usize = 2;
|
||||
|
||||
/// See code comments above where this value comes from!
|
||||
/// These are the bits to send via SPI MOSI that represent a logical 0
|
||||
/// on WS2812 RGB LED interface. Frequency + length results in the proper timings.
|
||||
pub const WS2812_LOGICAL_ZERO_BYTES: [u8; SPI_BYTES_PER_DATA_BIT] = [0b1111_1000, 0b0000_0000];
|
||||
|
||||
/// See code comments above where this value comes from!
|
||||
/// These are the bits to send via SPI MOSI that represent a logical 1
|
||||
/// on WS2812 RGB LED interface. Frequency + length results in the proper timings.
|
||||
pub const WS2812_LOGICAL_ONE_BYTES: [u8; SPI_BYTES_PER_DATA_BIT] = [0b1111_1111, 0b1000_0000];
|
||||
}
|
3
package-lock.json
generated
3
package-lock.json
generated
@ -1,3 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 1
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
# unstable_features = true
|
||||
# imports_granularity = "Crate"
|
132
src/color.rs
132
src/color.rs
@ -1,132 +0,0 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::num::ParseIntError;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Rgb(pub u8, pub u8, pub u8);
|
||||
impl Rgb {
|
||||
pub const fn to_tuple(self) -> (u8, u8, u8) {
|
||||
(self.0, self.1, self.2)
|
||||
}
|
||||
pub fn to_float_tuple(self) -> (f32, f32, f32) {
|
||||
(f32::from(self.0), f32::from(self.1), f32::from(self.2))
|
||||
}
|
||||
// pub fn to_gamma_corrected_tuple(&self) -> (u8, u8, u8) {
|
||||
// (
|
||||
// GAMMA_CORRECT[self.0 as usize],
|
||||
// GAMMA_CORRECT[self.1 as usize],
|
||||
// GAMMA_CORRECT[self.2 as usize],
|
||||
// )
|
||||
// }
|
||||
}
|
||||
|
||||
impl FromStr for Rgb {
|
||||
type Err = ParseIntError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let [r, g, b] = s.split(' ').collect::<Vec<&str>>().as_slice() {
|
||||
return Ok(Self(r.parse::<u8>()?, g.parse::<u8>()?, b.parse::<u8>()?));
|
||||
}
|
||||
|
||||
hex::decode(
|
||||
s.trim_start_matches('#')
|
||||
.trim_start_matches("0x")
|
||||
.trim_start_matches("0X"),
|
||||
)
|
||||
.map_err(|_| ())
|
||||
.and_then(|v| {
|
||||
Ok(Self(
|
||||
*v.get(0).ok_or(())?,
|
||||
*v.get(1).ok_or(())?,
|
||||
*v.get(2).ok_or(())?,
|
||||
))
|
||||
})
|
||||
.or_else(|_| {
|
||||
// TODO: Return a proper error here
|
||||
let color_value = s.parse::<u8>()?;
|
||||
Ok(Self(color_value, color_value, color_value))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub const BLACK: Rgb = Rgb(0, 0, 0);
|
||||
#[allow(dead_code)]
|
||||
pub const WHITE: Rgb = Rgb(255, 255, 255);
|
||||
|
||||
// Corrections:
|
||||
// R: 0x10, G: 0x08, B: 0x00
|
||||
|
||||
// const GAMMA_CORRECT: [u8; 256] = [
|
||||
// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
|
||||
// 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
|
||||
// 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14,
|
||||
// 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 26, 27,
|
||||
// 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46,
|
||||
// 47, 48, 49, 50, 50, 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70, 72,
|
||||
// 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104,
|
||||
// 105, 107, 109, 110, 112, 114, 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137,
|
||||
// 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175,
|
||||
// 177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, 215, 218, 220,
|
||||
// 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255,
|
||||
// ];
|
||||
pub const RAINBOW: [Rgb; 7] = [
|
||||
Rgb(255, 0, 0), // R
|
||||
Rgb(255, 128, 0), // O
|
||||
Rgb(255, 255, 0), // Y
|
||||
Rgb(0, 255, 0), // G
|
||||
Rgb(0, 0, 255), // B
|
||||
Rgb(75, 0, 130), // I
|
||||
Rgb(148, 0, 211), // V
|
||||
];
|
||||
|
||||
/// Merges a color by some factor
|
||||
pub fn merge_colors(from_color: Rgb, to_color: Rgb, factor: f32) -> Rgb {
|
||||
let (from_r, from_g, from_b) = from_color.to_float_tuple();
|
||||
let (to_r, to_g, to_b) = to_color.to_float_tuple();
|
||||
|
||||
// TODO: Do not use as u8
|
||||
let r = (to_r - from_r).mul_add(factor, from_r) as u8;
|
||||
let g = (to_g - from_g).mul_add(factor, from_g) as u8;
|
||||
let b = (to_b - from_b).mul_add(factor, from_b) as u8;
|
||||
|
||||
Rgb(r, g, b)
|
||||
}
|
||||
|
||||
/// Builds a color ramp of length `length` of the (exclusive) bounds of `from_color` to `to_color`
|
||||
pub fn build_ramp(from_color: Rgb, to_color: Rgb, length: usize) -> Vec<Rgb> {
|
||||
let offset = 1.0_f32 / (length as f32 + 1.0_f32);
|
||||
let mut ret: Vec<Rgb> = vec![];
|
||||
for step in 1..=length {
|
||||
ret.push(merge_colors(from_color, to_color, offset * step as f32));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn min_with_factor(at_least: u16, factor: u16) -> Result<u16, ()> {
|
||||
Ok(at_least
|
||||
.checked_sub(1)
|
||||
.ok_or(())?
|
||||
.div_euclid(factor)
|
||||
.checked_add(1)
|
||||
.ok_or(())?
|
||||
.checked_mul(factor)
|
||||
.ok_or(())?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_to_tuple() {
|
||||
assert_eq!(Rgb(213, 14, 0).to_tuple(), (213, 14, 0));
|
||||
assert_eq!(WHITE.to_tuple(), (255, 255, 255));
|
||||
assert_eq!(BLACK.to_tuple(), (0, 0, 0));
|
||||
}
|
||||
#[test]
|
||||
fn test_black() {
|
||||
// Most important because this will blank out the strip
|
||||
assert_eq!(BLACK, Rgb(0, 0, 0));
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
use core::any::Any;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
pub type ProgramResult<T> = Result<T, ProgramError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Message {
|
||||
Error(ProgramError),
|
||||
Terminated,
|
||||
String(String),
|
||||
InputPrompt(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProgramError {
|
||||
General(String),
|
||||
UiError(String),
|
||||
Boxed(Box<dyn Any + Send>),
|
||||
IoError(io::Error),
|
||||
}
|
||||
|
||||
impl From<String> for ProgramError {
|
||||
fn from(e: String) -> Self {
|
||||
Self::General(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for ProgramError {
|
||||
fn from(e: &str) -> Self {
|
||||
Self::General(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn Any + Send>> for ProgramError {
|
||||
fn from(e: Box<dyn Any + Send>) -> Self {
|
||||
Self::Boxed(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ProgramError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match self {
|
||||
Self::General(s) => write!(f, "{}", s),
|
||||
Self::UiError(s) => write!(f, "Critial UI error: {}", s),
|
||||
Self::Boxed(s) => write!(f, "{:?}", s),
|
||||
Self::IoError(e) => write!(f, "{:?}", e),
|
||||
}
|
||||
}
|
||||
}
|
113
src/main.rs
113
src/main.rs
@ -1,105 +1,16 @@
|
||||
// Easy mode
|
||||
// #![allow(dead_code, unused_imports)]
|
||||
// Enable clippy 'hard mode'
|
||||
#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
|
||||
// Intended behavior (10_f64 as i32)
|
||||
// #![allow(clippy::cast_possible_truncation)]
|
||||
#![allow(
|
||||
// Cannot be fixed
|
||||
clippy::multiple_crate_versions,
|
||||
// Intentional code
|
||||
clippy::map_err_ignore,
|
||||
// "as f32" used frequently in this project
|
||||
clippy::cast_precision_loss,
|
||||
// This is fine
|
||||
clippy::implicit_return,
|
||||
// Missing docs is fine
|
||||
clippy::missing_docs_in_private_items,
|
||||
)]
|
||||
// Restriction lints
|
||||
#![warn(
|
||||
clippy::else_if_without_else,
|
||||
// Obviously bad
|
||||
clippy::indexing_slicing,
|
||||
clippy::integer_arithmetic,
|
||||
clippy::integer_division,
|
||||
clippy::expect_used,
|
||||
clippy::unwrap_used,
|
||||
)]
|
||||
// See https://rust-lang.github.io/rust-clippy/master/index.html for more lints
|
||||
mod wire_protocol;
|
||||
use wire_protocol::{LedPanel, RGB};
|
||||
|
||||
mod color;
|
||||
mod errors;
|
||||
mod pattern;
|
||||
mod strip;
|
||||
mod ui;
|
||||
mod webui;
|
||||
use errors::{ProgramError, ProgramResult};
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::sync::mpsc::{channel, Sender};
|
||||
use std::thread;
|
||||
use strip::LedStrip;
|
||||
use ui::console_ui_loop;
|
||||
fn main() {
|
||||
let mut panel = LedPanel::new(5);
|
||||
|
||||
fn main() -> ProgramResult<()> {
|
||||
// Strip control transmitter and receiver
|
||||
let (strip_tx, strip_rx) = channel::<strip::Message>();
|
||||
let (console_strip_tx, webui_strip_tx) = (strip_tx.clone(), strip_tx);
|
||||
let colors: Vec<RGB> = vec![
|
||||
RGB(0x00, 0x00, 0x00),
|
||||
RGB(0x00, 0x00, 0x0F),
|
||||
RGB(0x00, 0x00, 0xFF),
|
||||
RGB(0x00, 0x00, 0x00),
|
||||
RGB(0x00, 0x00, 0x00),
|
||||
];
|
||||
|
||||
let (message_tx, message_rx) = channel::<errors::Message>();
|
||||
|
||||
make_child(message_tx.clone(), move |message_tx| -> ProgramResult<()> {
|
||||
let mut strip = LedStrip::new(strip::Config {
|
||||
// I have 89 right now, but start off with 20
|
||||
num_lights: 89,
|
||||
// Skip 14 lights
|
||||
shift_lights: 14,
|
||||
// Scaling factor (scale 0..255)
|
||||
global_brightness_max: 255,
|
||||
tick_time_ms: strip::DEFAULT_TICK_TIME_MS,
|
||||
})?;
|
||||
strip.strip_loop(message_tx, &strip_rx)
|
||||
});
|
||||
|
||||
make_child(message_tx.clone(), move |message_tx| -> ProgramResult<()> {
|
||||
console_ui_loop(message_tx, &console_strip_tx)
|
||||
});
|
||||
|
||||
make_child(message_tx, move |message_tx| -> ProgramResult<()> {
|
||||
webui::start(message_tx.clone(), webui_strip_tx).map_err(ProgramError::IoError)
|
||||
});
|
||||
|
||||
let mut input_prompt: Option<String> = None;
|
||||
loop {
|
||||
match message_rx.recv() {
|
||||
Ok(errors::Message::String(s)) => println!("\r{}", s),
|
||||
Ok(errors::Message::Error(e)) => println!("\rError!! {:?}", e),
|
||||
Ok(errors::Message::Terminated) => {
|
||||
panic!("A thread terminated")
|
||||
}
|
||||
Ok(errors::Message::InputPrompt(i)) => input_prompt = Some(i),
|
||||
Err(e) => {
|
||||
return Err(ProgramError::General(format!(
|
||||
"All transmitters hung up! {:?}",
|
||||
e
|
||||
)))
|
||||
}
|
||||
}
|
||||
if let Some(ref s) = input_prompt {
|
||||
print!("{}: ", s);
|
||||
// We do not care if we can't flush
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_child<F: 'static>(message_tx: Sender<errors::Message>, f: F)
|
||||
where
|
||||
F: FnOnce(&Sender<errors::Message>) -> ProgramResult<()> + std::marker::Send,
|
||||
{
|
||||
thread::spawn(move || match f(&message_tx) {
|
||||
Ok(()) => message_tx.send(errors::Message::Terminated),
|
||||
Err(e) => message_tx.send(errors::Message::Error(e)),
|
||||
});
|
||||
panel.set_leds(&colors);
|
||||
}
|
||||
|
@ -1,89 +0,0 @@
|
||||
use crate::color::Rgb;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::vec_deque;
|
||||
|
||||
pub mod collide;
|
||||
pub mod fade;
|
||||
pub mod flashing;
|
||||
pub mod moving_pixel;
|
||||
pub mod moving_rainbow;
|
||||
pub mod orb;
|
||||
pub mod solid;
|
||||
pub use collide::Collide;
|
||||
pub use fade::Fade;
|
||||
pub use flashing::Flashing;
|
||||
pub use moving_pixel::MovingPixel;
|
||||
pub use moving_rainbow::MovingRainbow;
|
||||
pub use orb::Orb;
|
||||
pub use solid::Solid;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum Parameters {
|
||||
Collide(Rgb, Rgb, Rgb),
|
||||
Fade((Rgb,)),
|
||||
MovingPixel((Rgb,)),
|
||||
MovingRainbow(u8, bool, u8),
|
||||
Orb(Rgb, u8, u8),
|
||||
Solid((Rgb,)),
|
||||
Flashing(Vec<Rgb>, u8, u16),
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
pub fn into_pattern(self) -> Box<dyn Pattern + Send + Sync> {
|
||||
match self {
|
||||
Self::Collide(l, r, c) => Box::new(Collide::new(l, r, c)),
|
||||
Self::Fade((c,)) => Box::new(Fade::new(c)),
|
||||
Self::MovingPixel((c,)) => Box::new(MovingPixel::new(c)),
|
||||
Self::MovingRainbow(w, f, s) => Box::new(MovingRainbow::new(w, f, s)),
|
||||
Self::Orb(c, x, y) => Box::new(Orb::new(c, x, y)),
|
||||
Self::Solid((c,)) => Box::new(Solid::new(c)),
|
||||
Self::Flashing(cs, w, r) => Box::new(Flashing::new(cs, w, r)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Pattern: std::fmt::Debug + Send + Sync {
|
||||
fn init(&mut self, num_lights: u16) -> Result<(), ()>;
|
||||
fn step(&mut self) -> Result<bool, ()>;
|
||||
fn get_strip(&self) -> vec_deque::Iter<Rgb>;
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// const NUM_LIGHTS: u16 = 10;
|
||||
// fn test_strip() -> Vec<Rgb> {
|
||||
// vec![color::BLACK; NUM_LIGHTS.into()]
|
||||
// }
|
||||
// #[test]
|
||||
// fn moving_pixel() {
|
||||
// let color = Rgb(123, 152, 89);
|
||||
// let mut pat = MovingPixel::new(color.clone());
|
||||
// let mut strip = test_strip();
|
||||
|
||||
// assert!(pat.init(&mut strip, NUM_LIGHTS).is_ok());
|
||||
// // One is my color
|
||||
// assert_eq!(strip.iter().filter(|c| **c == color).count(), 1);
|
||||
// // The rest are off
|
||||
// assert_eq!(
|
||||
// strip.iter().filter(|c| **c == color::BLACK).count(),
|
||||
// (NUM_LIGHTS - 1).into()
|
||||
// );
|
||||
// pat.step(&mut strip);
|
||||
// // One is my color
|
||||
// assert_eq!(strip.iter().filter(|c| **c == color).count(), 1);
|
||||
// // The rest are off
|
||||
// assert_eq!(
|
||||
// strip.iter().filter(|c| **c == color::BLACK).count(),
|
||||
// (NUM_LIGHTS - 1).into()
|
||||
// );
|
||||
// }
|
||||
// #[test]
|
||||
// fn solid() {}
|
||||
// #[test]
|
||||
// fn moving_rainbow() {}
|
||||
// #[test]
|
||||
// fn fade() {}
|
||||
// #[test]
|
||||
// fn collide() {}
|
||||
// }
|
@ -1,155 +0,0 @@
|
||||
use super::Pattern;
|
||||
use crate::color::{self, Rgb};
|
||||
use std::collections::vec_deque;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Collide {
|
||||
num_lights: u16,
|
||||
left_color: Rgb,
|
||||
right_color: Rgb,
|
||||
conjoined_color: Rgb,
|
||||
step: u16,
|
||||
step_max: u16,
|
||||
conjoined_bounds: (u16, u16),
|
||||
offset_max: u16,
|
||||
offset: u16,
|
||||
previous_offset: u16,
|
||||
increase_offset: bool,
|
||||
lights_buf: VecDeque<Rgb>,
|
||||
}
|
||||
impl Collide {
|
||||
pub fn new(left_color: Rgb, right_color: Rgb, conjoined_color: Rgb) -> Self {
|
||||
Self {
|
||||
num_lights: 0,
|
||||
left_color,
|
||||
right_color,
|
||||
conjoined_color,
|
||||
step: 0,
|
||||
step_max: 0,
|
||||
conjoined_bounds: (0, 0),
|
||||
offset_max: 0,
|
||||
previous_offset: 0,
|
||||
offset: 0,
|
||||
increase_offset: true,
|
||||
lights_buf: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Pattern for Collide {
|
||||
fn step(&mut self) -> Result<bool, ()> {
|
||||
// TODO: Better range storage
|
||||
// Set the left and right color
|
||||
let colors =
|
||||
if ((self.conjoined_bounds.0)..=(self.conjoined_bounds.1)).contains(&(self.step)) {
|
||||
(self.conjoined_color, self.conjoined_color)
|
||||
} else {
|
||||
(self.left_color, self.right_color)
|
||||
};
|
||||
|
||||
// Turn off the previous LED
|
||||
*self
|
||||
.lights_buf
|
||||
.get_mut(usize::from(self.previous_offset))
|
||||
.ok_or(())? = color::BLACK;
|
||||
if self.previous_offset
|
||||
!= self
|
||||
.num_lights
|
||||
.saturating_sub(1)
|
||||
.saturating_sub(self.previous_offset)
|
||||
{
|
||||
*self
|
||||
.lights_buf
|
||||
.get_mut(usize::from(
|
||||
self.num_lights
|
||||
.saturating_sub(1)
|
||||
.saturating_sub(self.previous_offset),
|
||||
))
|
||||
.ok_or(())? = color::BLACK;
|
||||
}
|
||||
// Set the color of the current offset
|
||||
*self
|
||||
.lights_buf
|
||||
.get_mut(usize::from(self.offset))
|
||||
.ok_or(())? = colors.0;
|
||||
if self.offset
|
||||
!= self
|
||||
.num_lights
|
||||
.saturating_sub(1)
|
||||
.saturating_sub(self.offset)
|
||||
{
|
||||
*self
|
||||
.lights_buf
|
||||
.get_mut(usize::from(
|
||||
self.num_lights
|
||||
.saturating_sub(1)
|
||||
.saturating_sub(self.offset),
|
||||
))
|
||||
.ok_or(())? = colors.1;
|
||||
}
|
||||
|
||||
self.previous_offset = self.offset;
|
||||
if self.offset == 0 {
|
||||
self.increase_offset = true;
|
||||
self.offset = self.offset.saturating_add(1);
|
||||
} else if self.offset == self.offset_max {
|
||||
self.increase_offset = false;
|
||||
self.offset = self.offset.saturating_sub(1);
|
||||
} else if self.increase_offset {
|
||||
self.offset = self.offset.saturating_add(1);
|
||||
// The previous condition was false, so subtract
|
||||
} else {
|
||||
self.offset = self.offset.saturating_sub(1);
|
||||
}
|
||||
|
||||
self.step = self.step.saturating_add(1).rem_euclid(self.step_max);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
fn init(&mut self, num_lights: u16) -> Result<(), ()> {
|
||||
// Reset changing parameters
|
||||
self.step = 0;
|
||||
self.offset = 0;
|
||||
self.previous_offset = 0;
|
||||
self.num_lights = num_lights;
|
||||
self.increase_offset = true;
|
||||
if self.num_lights < 3 {
|
||||
return Err(());
|
||||
}
|
||||
self.lights_buf = VecDeque::from(vec![color::BLACK; self.num_lights.into()]);
|
||||
if self.num_lights.rem_euclid(2) == 0 {
|
||||
self.conjoined_bounds = (
|
||||
self.num_lights
|
||||
.div_euclid(2)
|
||||
.saturating_add(1_u16.saturating_sub(1)),
|
||||
self.num_lights
|
||||
.saturating_add(1)
|
||||
.saturating_mul(3)
|
||||
.div_euclid(2)
|
||||
.saturating_sub(4),
|
||||
);
|
||||
} else {
|
||||
self.conjoined_bounds = (
|
||||
self.num_lights
|
||||
.div_euclid(2)
|
||||
.saturating_add(1_u16.saturating_sub(1)),
|
||||
self.num_lights
|
||||
.saturating_add(1)
|
||||
.saturating_mul(3)
|
||||
.div_euclid(2)
|
||||
.saturating_sub(3),
|
||||
);
|
||||
}
|
||||
self.offset_max = self.num_lights.saturating_sub(1).div_euclid(2);
|
||||
self.step_max = self
|
||||
.num_lights
|
||||
.saturating_sub(1)
|
||||
.div_euclid(2)
|
||||
.saturating_mul(4);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_strip(&self) -> vec_deque::Iter<Rgb> {
|
||||
self.lights_buf.iter()
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
use super::Pattern;
|
||||
use crate::color::{self, Rgb};
|
||||
use std::collections::vec_deque;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Fade {
|
||||
color: Rgb,
|
||||
step: u8,
|
||||
direction: bool,
|
||||
num_lights: u16,
|
||||
lights_buf: VecDeque<Rgb>,
|
||||
}
|
||||
impl Fade {
|
||||
pub fn new(color: Rgb) -> Self {
|
||||
Self {
|
||||
color,
|
||||
step: 0,
|
||||
direction: true,
|
||||
num_lights: 1,
|
||||
lights_buf: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Pattern for Fade {
|
||||
fn step(&mut self) -> Result<bool, ()> {
|
||||
if self.direction {
|
||||
if self.step == 254 {
|
||||
self.direction = !self.direction;
|
||||
}
|
||||
self.step = self.step.saturating_add(1);
|
||||
} else {
|
||||
if self.step == 1 {
|
||||
self.direction = !self.direction;
|
||||
}
|
||||
self.step = self.step.saturating_sub(1);
|
||||
}
|
||||
self.lights_buf = VecDeque::from(vec![
|
||||
Rgb(self.step, self.step, self.step);
|
||||
self.num_lights.into()
|
||||
]);
|
||||
Ok(true)
|
||||
}
|
||||
fn init(&mut self, num_lights: u16) -> Result<(), ()> {
|
||||
if num_lights < 1 {
|
||||
return Err(());
|
||||
}
|
||||
self.step = 0;
|
||||
self.direction = true;
|
||||
self.num_lights = num_lights;
|
||||
self.lights_buf = VecDeque::from(vec![color::BLACK; self.num_lights.into()]);
|
||||
Ok(())
|
||||
}
|
||||
fn get_strip(&self) -> vec_deque::Iter<Rgb> {
|
||||
self.lights_buf.iter()
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
use super::Pattern;
|
||||
use crate::color::{self, Rgb};
|
||||
use std::{
|
||||
collections::{vec_deque, VecDeque},
|
||||
iter,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Flashing {
|
||||
lights_buf: VecDeque<Rgb>,
|
||||
width: u8,
|
||||
step: u16,
|
||||
tick_rate: u16,
|
||||
colors: Vec<Rgb>,
|
||||
}
|
||||
|
||||
impl Flashing {
|
||||
pub fn new(colors: Vec<Rgb>, width: u8, tick_rate: u16) -> Self {
|
||||
Self {
|
||||
colors,
|
||||
tick_rate,
|
||||
step: 0,
|
||||
width,
|
||||
lights_buf: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for Flashing {
|
||||
fn step(&mut self) -> Result<bool, ()> {
|
||||
self.step = self.step.wrapping_add(1).rem_euclid(self.tick_rate);
|
||||
if self.step != 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
self.lights_buf.rotate_right(self.width.into());
|
||||
Ok(true)
|
||||
}
|
||||
fn init(&mut self, num_lights: u16) -> Result<(), ()> {
|
||||
if num_lights < 1 {
|
||||
return Err(());
|
||||
}
|
||||
let length_factor = num_lights.saturating_mul(u16::from(self.width));
|
||||
|
||||
let buf_length = color::min_with_factor(num_lights, length_factor)?;
|
||||
|
||||
self.step = 0;
|
||||
|
||||
self.lights_buf = self
|
||||
.colors
|
||||
.iter()
|
||||
.flat_map(|&x| iter::repeat(x).take(self.width.into()))
|
||||
.cycle()
|
||||
.take(buf_length.into())
|
||||
.collect();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn get_strip(&self) -> vec_deque::Iter<Rgb> {
|
||||
self.lights_buf.iter()
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
use super::Pattern;
|
||||
use crate::color::{self, Rgb};
|
||||
use std::collections::vec_deque;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MovingPixel {
|
||||
color: Rgb,
|
||||
num_lights: u16,
|
||||
step: u16,
|
||||
lights_buf: VecDeque<Rgb>,
|
||||
}
|
||||
|
||||
impl MovingPixel {
|
||||
pub fn new(color: Rgb) -> Self {
|
||||
Self {
|
||||
color,
|
||||
step: 0,
|
||||
// TODO: Better initialization
|
||||
num_lights: 1,
|
||||
lights_buf: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern for MovingPixel {
|
||||
fn step(&mut self) -> Result<bool, ()> {
|
||||
let len = self.num_lights;
|
||||
self.lights_buf.swap(
|
||||
self.step.rem_euclid(len).into(),
|
||||
self.step.saturating_add(1).rem_euclid(len).into(),
|
||||
);
|
||||
self.step = self.step.wrapping_add(1);
|
||||
Ok(true)
|
||||
}
|
||||
fn init(&mut self, num_lights: u16) -> Result<(), ()> {
|
||||
if num_lights < 1 {
|
||||
return Err(());
|
||||
}
|
||||
self.step = 0;
|
||||
self.num_lights = num_lights;
|
||||
// Set the strip to black except for one pixel
|
||||
self.lights_buf = VecDeque::from(vec![color::BLACK; num_lights.into()]);
|
||||
*self.lights_buf.get_mut(0).ok_or(())? = self.color;
|
||||
Ok(())
|
||||
}
|
||||
fn get_strip(&self) -> vec_deque::Iter<Rgb> {
|
||||
self.lights_buf.iter()
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
use super::Pattern;
|
||||
use crate::color::{self, Rgb, RAINBOW};
|
||||
use std::collections::{vec_deque, VecDeque};
|
||||
use std::convert::TryFrom;
|
||||
use std::iter;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MovingRainbow {
|
||||
lights_buf: VecDeque<Rgb>,
|
||||
skip: u8,
|
||||
width: u8,
|
||||
forward: bool,
|
||||
}
|
||||
impl MovingRainbow {
|
||||
pub fn new(width: u8, forward: bool, skip: u8) -> Self {
|
||||
Self {
|
||||
lights_buf: VecDeque::new(),
|
||||
skip,
|
||||
width,
|
||||
forward,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Pattern for MovingRainbow {
|
||||
fn step(&mut self) -> Result<bool, ()> {
|
||||
if self.forward {
|
||||
self.lights_buf.rotate_left(1);
|
||||
} else {
|
||||
self.lights_buf.rotate_right(1);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
fn init(&mut self, num_lights: u16) -> Result<(), ()> {
|
||||
if !(1..=255).contains(&num_lights) {
|
||||
return Err(());
|
||||
}
|
||||
if self.width < 1 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
// (width + skip) * RAINBOW.len()
|
||||
let length_factor = u16::from(self.width)
|
||||
.checked_add(self.skip.into())
|
||||
.ok_or(())?
|
||||
.saturating_mul(u16::try_from(RAINBOW.len()).or(Err(()))?);
|
||||
// The length of the buffer
|
||||
// Always a factor of length_factor
|
||||
let buf_length = color::min_with_factor(num_lights, length_factor.into())?;
|
||||
println!(
|
||||
"Got buf length: {} with #lights {} and length factor {} ({})",
|
||||
buf_length, num_lights, length_factor, (self.width+self.skip) as usize *RAINBOW.len()
|
||||
);
|
||||
// num_lights
|
||||
// .checked_sub(1)
|
||||
// .ok_or(())?
|
||||
// .div_euclid(length_factor)
|
||||
// .checked_add(1)
|
||||
// .ok_or(())?
|
||||
// .saturating_mul(length_factor);
|
||||
|
||||
self.lights_buf = RAINBOW
|
||||
.iter()
|
||||
.flat_map(|&x| {
|
||||
iter::repeat(x)
|
||||
.take(self.width.into())
|
||||
.chain(iter::repeat(color::BLACK).take(self.skip.into()))
|
||||
})
|
||||
.cycle()
|
||||
.take(buf_length.into())
|
||||
.collect();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn get_strip(&self) -> vec_deque::Iter<Rgb> {
|
||||
self.lights_buf.iter()
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
use super::Pattern;
|
||||
use crate::color::{self, Rgb};
|
||||
use std::collections::vec_deque;
|
||||
use std::collections::VecDeque;
|
||||
use std::iter;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Orb {
|
||||
/// Buffer to manage the lights
|
||||
lights_buf: VecDeque<Rgb>,
|
||||
/// The color of the orb
|
||||
color: Rgb,
|
||||
/// The width of the center of the orb
|
||||
center_width: u8,
|
||||
/// The width of each side's backoff (fadeout)
|
||||
backoff_width: u8,
|
||||
/// The total width of the orb, equal to center_width + 2*backoff_width
|
||||
total_width: u8,
|
||||
/// True if the orb should bounce from left to right, otherwise it will wrap around
|
||||
bounces: bool,
|
||||
/// The state of the program
|
||||
step: usize,
|
||||
/// The maximum number of steps for a given direction. The orb will turn around after this many steps
|
||||
step_max: usize,
|
||||
/// Direction of the orb. This can switch if `bounces` is true
|
||||
direction: bool,
|
||||
}
|
||||
impl Orb {
|
||||
pub fn new(color: Rgb, center_width: u8, backoff_width: u8) -> Self {
|
||||
Self {
|
||||
lights_buf: VecDeque::new(),
|
||||
color,
|
||||
center_width,
|
||||
backoff_width,
|
||||
total_width: center_width.saturating_add(backoff_width.saturating_mul(2)),
|
||||
bounces: false,
|
||||
step: 0,
|
||||
step_max: 0,
|
||||
direction: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Pattern for Orb {
|
||||
fn step(&mut self) -> Result<bool, ()> {
|
||||
if !self.bounces {
|
||||
// If we don't bounce, then just wrap and we're done
|
||||
self.lights_buf.rotate_right(1);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if self.direction {
|
||||
self.lights_buf.rotate_right(1);
|
||||
self.step = self.step.saturating_add(1);
|
||||
} else {
|
||||
self.lights_buf.rotate_left(1);
|
||||
self.step = self.step.saturating_sub(1);
|
||||
}
|
||||
|
||||
if self.step == self.step_max || self.step == 0 {
|
||||
self.direction = !self.direction;
|
||||
// } else if self.lights_buf - self.step == self.total_width {
|
||||
// self.direction = true;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
fn init(&mut self, num_lights: u16) -> Result<(), ()> {
|
||||
if num_lights < 1 {
|
||||
return Err(());
|
||||
}
|
||||
self.step = 0;
|
||||
let other_color = color::BLACK;
|
||||
let ramp = color::build_ramp(other_color, self.color, self.backoff_width.into());
|
||||
|
||||
self.lights_buf = iter::empty::<Rgb>()
|
||||
.chain(ramp.clone().into_iter())
|
||||
.chain(iter::repeat(self.color).take(self.center_width.into()))
|
||||
.chain(ramp.into_iter().rev())
|
||||
.chain(
|
||||
iter::repeat(other_color)
|
||||
.take(num_lights.saturating_sub(self.total_width.into()).into()),
|
||||
)
|
||||
.collect();
|
||||
|
||||
self.step_max = self
|
||||
.lights_buf
|
||||
.len()
|
||||
.checked_sub(self.total_width.into())
|
||||
.unwrap_or_else(|| self.lights_buf.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn get_strip(&self) -> vec_deque::Iter<Rgb> {
|
||||
self.lights_buf.iter()
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
use super::Pattern;
|
||||
use crate::color::Rgb;
|
||||
use std::collections::vec_deque;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Solid {
|
||||
color: Rgb,
|
||||
has_run: bool,
|
||||
lights_buf: VecDeque<Rgb>,
|
||||
}
|
||||
impl Solid {
|
||||
pub fn new(color: Rgb) -> Self {
|
||||
Self {
|
||||
color,
|
||||
has_run: false,
|
||||
lights_buf: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Pattern for Solid {
|
||||
fn step(&mut self) -> Result<bool, ()> {
|
||||
let ret = !self.has_run;
|
||||
self.has_run = true;
|
||||
Ok(ret)
|
||||
}
|
||||
fn init(&mut self, num_lights: u16) -> Result<(), ()> {
|
||||
if num_lights < 1 {
|
||||
return Err(());
|
||||
}
|
||||
self.has_run = false;
|
||||
self.lights_buf = VecDeque::from(vec![self.color; num_lights.into()]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_strip(&self) -> vec_deque::Iter<Rgb> {
|
||||
self.lights_buf.iter()
|
||||
}
|
||||
}
|
184
src/strip.rs
184
src/strip.rs
@ -1,184 +0,0 @@
|
||||
use crate::color;
|
||||
use crate::errors;
|
||||
use crate::errors::ProgramError;
|
||||
use crate::pattern::{self, Pattern};
|
||||
use std::cmp;
|
||||
use std::ops::Add;
|
||||
use std::process;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::time::{Duration, Instant};
|
||||
use ws2818_rgb_led_spi_driver::adapter_gen::Ws28xxAdapter;
|
||||
use ws2818_rgb_led_spi_driver::adapter_spi::Ws28xxSpiAdapter;
|
||||
|
||||
/// Maximum number of lights allowed
|
||||
const MAX_NUM_LIGHTS: u16 = 128;
|
||||
/// Default time per tick
|
||||
pub const DEFAULT_TICK_TIME_MS: u64 = 50;
|
||||
/// Minimum time per tick before strip breaks
|
||||
const MIN_TICK_TIME: u64 = 10;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
/// Number of lights
|
||||
pub num_lights: u16,
|
||||
/// Number of lights to skip
|
||||
pub shift_lights: u16,
|
||||
/// Global brightness multiplier
|
||||
pub global_brightness_max: u8,
|
||||
/// Time per tick
|
||||
pub tick_time_ms: u64,
|
||||
}
|
||||
|
||||
pub enum Message {
|
||||
ClearLights,
|
||||
ChangePattern(Box<dyn Pattern + Send>),
|
||||
SetNumLights(u16),
|
||||
SetTickTime(u64),
|
||||
Quit,
|
||||
}
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct LedStrip {
|
||||
pub adapter: Box<dyn Ws28xxAdapter>,
|
||||
pub config: Config,
|
||||
pub pattern: Box<dyn Pattern>,
|
||||
}
|
||||
|
||||
impl LedStrip {
|
||||
pub fn new(config: Config) -> Result<Self, ProgramError> {
|
||||
let adapter = Box::new(
|
||||
Ws28xxSpiAdapter::new("/dev/spidev0.0")
|
||||
.map_err(|_| "Cannot start device /dev/spidev0.0!")?,
|
||||
);
|
||||
let pattern = Box::new(pattern::Solid::new(color::BLACK));
|
||||
let num_lights = config.num_lights;
|
||||
let mut ret = Self {
|
||||
adapter,
|
||||
pattern,
|
||||
config,
|
||||
};
|
||||
ret.set_num_lights(num_lights);
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn write_buf_from_pattern(&mut self) -> Result<(), ProgramError> {
|
||||
let global_brightness_max = self.config.global_brightness_max;
|
||||
let data = vec![color::BLACK]
|
||||
.iter()
|
||||
.cycle()
|
||||
.take(self.config.shift_lights.into())
|
||||
.chain(
|
||||
self.pattern
|
||||
.get_strip()
|
||||
// .as_slice()
|
||||
.take(self.config.num_lights.into()),
|
||||
)
|
||||
.map(|c| c.to_tuple())
|
||||
.map(|(r, g, b)| {
|
||||
(
|
||||
cmp::min(r, global_brightness_max),
|
||||
cmp::min(g, global_brightness_max),
|
||||
cmp::min(b, global_brightness_max),
|
||||
)
|
||||
})
|
||||
// TODO: Do not re-collect as u8s
|
||||
.collect::<Vec<(u8, u8, u8)>>();
|
||||
self.adapter.write_rgb(data.as_slice())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_num_lights(&mut self, num_lights: u16) {
|
||||
if num_lights > MAX_NUM_LIGHTS {
|
||||
println!(
|
||||
"Cannot set lights to {} as it exceeds max of {}",
|
||||
num_lights, MAX_NUM_LIGHTS
|
||||
);
|
||||
return;
|
||||
}
|
||||
if self.pattern.init(num_lights).is_ok() {
|
||||
self.config.num_lights = num_lights;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn strip_loop(
|
||||
&mut self,
|
||||
message_tx: &Sender<errors::Message>,
|
||||
rx: &Receiver<Message>,
|
||||
) -> Result<(), ProgramError> {
|
||||
let mut exit = false;
|
||||
loop {
|
||||
let target_time = Instant::now().add(Duration::from_millis(self.config.tick_time_ms));
|
||||
|
||||
if let Ok(message) = rx.try_recv() {
|
||||
match message {
|
||||
Message::ClearLights => {
|
||||
let mut pat = Box::new(pattern::Solid::new(color::BLACK));
|
||||
if pat.init(self.config.num_lights).is_ok() {
|
||||
self.pattern = pat;
|
||||
} else {
|
||||
let _ = message_tx.send(errors::Message::String(format!(
|
||||
"Clearing light strip: {:?}",
|
||||
pat
|
||||
)));
|
||||
}
|
||||
}
|
||||
Message::ChangePattern(pat) => {
|
||||
let mut pat = pat;
|
||||
if pat.init(self.config.num_lights).is_ok() {
|
||||
self.pattern = pat;
|
||||
} else {
|
||||
let _ = message_tx.send(errors::Message::String(format!(
|
||||
"Error initializing pattern: {:?}",
|
||||
pat
|
||||
)));
|
||||
}
|
||||
}
|
||||
Message::SetNumLights(num_lights) => {
|
||||
self.set_num_lights(num_lights);
|
||||
}
|
||||
Message::SetTickTime(tick_time_ms) => {
|
||||
if tick_time_ms < MIN_TICK_TIME {
|
||||
let _ = message_tx.send(errors::Message::String(format!(
|
||||
"Error with tick time: {}",
|
||||
tick_time_ms
|
||||
)));
|
||||
}
|
||||
self.config.tick_time_ms = tick_time_ms;
|
||||
}
|
||||
Message::Quit => {
|
||||
exit = true;
|
||||
let mut pat = pattern::Solid::new(color::BLACK);
|
||||
if pat.init(self.config.num_lights).is_ok() {
|
||||
self.pattern = Box::new(pat);
|
||||
} else {
|
||||
let _ = message_tx.send(errors::Message::String(String::from(
|
||||
"Could not construct clear pattern",
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self
|
||||
.pattern
|
||||
.step()
|
||||
.map_err(|_| ProgramError::General(String::from("Pattern step failure")))?
|
||||
{
|
||||
self.write_buf_from_pattern()?;
|
||||
}
|
||||
|
||||
if exit {
|
||||
let _ = message_tx.send(errors::Message::String(String::from(
|
||||
"Exiting as requested",
|
||||
)));
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
loop {
|
||||
if Instant::now() >= target_time {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
144
src/ui.rs
144
src/ui.rs
@ -1,144 +0,0 @@
|
||||
use crate::color::Rgb;
|
||||
use crate::errors::{self, ProgramError, ProgramResult};
|
||||
use crate::pattern::{self, Pattern};
|
||||
use crate::strip;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
pub fn console_ui_loop(
|
||||
message_tx: &Sender<errors::Message>,
|
||||
strip_tx: &Sender<strip::Message>,
|
||||
) -> ProgramResult<()> {
|
||||
loop {
|
||||
let line = get_line(message_tx, "Command (cfqs)")?;
|
||||
if let Err(msg) = parse_cmd(strip_tx, &line) {
|
||||
println!("Command error: {}", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_cmd(strip_tx: &Sender<strip::Message>, s: &str) -> Result<(), String> {
|
||||
match s
|
||||
.split(char::is_whitespace)
|
||||
.collect::<Vec<&str>>()
|
||||
.as_slice()
|
||||
{
|
||||
["f", r, g, b] => {
|
||||
let color = Rgb(
|
||||
r.parse::<u8>()
|
||||
.map_err(|_| String::from("Red could not be parsed"))?,
|
||||
g.parse::<u8>()
|
||||
.map_err(|_| String::from("Green could not be parsed"))?,
|
||||
b.parse::<u8>()
|
||||
.map_err(|_| String::from("Blue could not be parsed"))?,
|
||||
);
|
||||
change_pattern(strip_tx, Box::new(pattern::Fade::new(color)))
|
||||
}
|
||||
["f", c] => {
|
||||
let color_value = c
|
||||
.parse::<u8>()
|
||||
.map_err(|_| String::from("Could not parse color"))?;
|
||||
let color = Rgb(color_value, color_value, color_value);
|
||||
change_pattern(strip_tx, Box::new(pattern::Fade::new(color)))
|
||||
}
|
||||
["m", r, g, b] => {
|
||||
let color = Rgb(
|
||||
r.parse::<u8>()
|
||||
.map_err(|_| String::from("Red could not be parsed"))?,
|
||||
g.parse::<u8>()
|
||||
.map_err(|_| String::from("Green could not be parsed"))?,
|
||||
b.parse::<u8>()
|
||||
.map_err(|_| String::from("Blue could not be parsed"))?,
|
||||
);
|
||||
change_pattern(strip_tx, Box::new(pattern::MovingPixel::new(color)))
|
||||
}
|
||||
["m", c] => {
|
||||
let color_value = c
|
||||
.parse::<u8>()
|
||||
.map_err(|_| String::from("Could not parse color"))?;
|
||||
let color = Rgb(color_value, color_value, color_value);
|
||||
change_pattern(strip_tx, Box::new(pattern::MovingPixel::new(color)))
|
||||
},
|
||||
["c", r, g, b] => {
|
||||
let color = parse_color(r, g, b)?;
|
||||
change_pattern(strip_tx, Box::new(pattern::Solid::new(color)))
|
||||
}
|
||||
["c", c] => {
|
||||
let color = parse_color(c, c, c)?;
|
||||
change_pattern(strip_tx, Box::new(pattern::Solid::new(color)))
|
||||
},
|
||||
["r"] => change_pattern(strip_tx, Box::new(pattern::MovingRainbow::new(4, true, 0))),
|
||||
["r", w] => {
|
||||
let width = w.parse::<u8>().map_err(|_| String::from("Width could not be parsed"))?;
|
||||
change_pattern(strip_tx, Box::new(pattern::MovingRainbow::new(width, true, 0)))
|
||||
},
|
||||
["r", w, f] => {
|
||||
let width = w.parse::<u8>().map_err(|_| String::from("Width could not be parsed"))?;
|
||||
change_pattern(strip_tx, Box::new(pattern::MovingRainbow::new(width, ["t", "T"].contains(f), 0)))
|
||||
},
|
||||
["r", w, f, s] => {
|
||||
let width = w.parse::<u8>().map_err(|_| String::from("Width could not be parsed"))?;
|
||||
let shift = s.parse::<u8>().map_err(|_| String::from("Shift could not be parsed"))?;
|
||||
change_pattern(strip_tx, Box::new(pattern::MovingRainbow::new(width, ["t", "T"].contains(f), shift)))
|
||||
},
|
||||
["b", r1, g1, b1, r2, g2, b2, r3, g3, b3] => {
|
||||
let left = parse_color(r1, g1, b1)?;
|
||||
let right = parse_color(r2, g2, b2)?;
|
||||
let combined = parse_color(r3, g3, b3)?;
|
||||
change_pattern(strip_tx, Box::new(pattern::Collide::new(left, right, combined)))
|
||||
}
|
||||
["x"] => strip_tx
|
||||
.send(strip::Message::ClearLights)
|
||||
.map_err(|e| e.to_string()),
|
||||
["q"] => {
|
||||
strip_tx.send(strip::Message::Quit).map_err(|e| e.to_string())?;
|
||||
// TODO
|
||||
panic!("i");
|
||||
},
|
||||
["s", n] => strip_tx
|
||||
.send(strip::Message::SetNumLights(
|
||||
n.parse::<u16>()
|
||||
.map_err(|_| String::from("Could not parse light count"))?,
|
||||
))
|
||||
.map_err(|e| e.to_string()),
|
||||
["t", n] => strip_tx.send(strip::Message::SetTickTime(
|
||||
n.parse::<u64>()
|
||||
.map_err(|_| String::from("Could not parse light count"))?,
|
||||
))
|
||||
.map_err(|e| e.to_string()),
|
||||
_ => Err(String::from("Unknown command. Available commands: solidColor movingRainbow Bouncing Fade clear(X) Setnumlights setTicktime")),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_color(r: &str, g: &str, b: &str) -> Result<Rgb, String> {
|
||||
Ok(Rgb(
|
||||
r.parse::<u8>()
|
||||
.map_err(|_| String::from("Red could not be parsed"))?,
|
||||
g.parse::<u8>()
|
||||
.map_err(|_| String::from("Green could not be parsed"))?,
|
||||
b.parse::<u8>()
|
||||
.map_err(|_| String::from("Blue could not be parsed"))?,
|
||||
))
|
||||
}
|
||||
|
||||
fn change_pattern(
|
||||
strip_tx: &Sender<strip::Message>,
|
||||
pat: Box<dyn Pattern + Send>,
|
||||
) -> Result<(), String> {
|
||||
strip_tx
|
||||
.send(strip::Message::ChangePattern(pat))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn get_line(message_tx: &Sender<errors::Message>, prompt: &str) -> ProgramResult<String> {
|
||||
let _ = message_tx.send(errors::Message::InputPrompt(String::from(prompt)));
|
||||
std::io::stdout()
|
||||
.flush()
|
||||
.map_err(|_| ProgramError::UiError(String::from("Could not flush stdout")))?;
|
||||
let mut input_text = String::new();
|
||||
io::stdin()
|
||||
.read_line(&mut input_text)
|
||||
.map_err(|_| ProgramError::UiError(String::from("Could not read from stdin")))?;
|
||||
Ok(input_text.trim().to_string())
|
||||
}
|
69
src/webui.rs
69
src/webui.rs
@ -1,69 +0,0 @@
|
||||
use crate::errors;
|
||||
use crate::pattern;
|
||||
use crate::strip;
|
||||
|
||||
use actix_web::{
|
||||
error::{JsonPayloadError, UrlencodedError},
|
||||
post, web,
|
||||
web::JsonConfig,
|
||||
App, HttpServer, Responder, Result,
|
||||
};
|
||||
use actix_web_static_files::ResourceFiles;
|
||||
use std::io;
|
||||
use std::sync::{mpsc::Sender, Arc, Mutex};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
||||
|
||||
struct AppState {
|
||||
strip_tx: Arc<Mutex<Sender<strip::Message>>>,
|
||||
}
|
||||
|
||||
#[post("/setcolor")]
|
||||
async fn set_color_json(
|
||||
data: web::Data<AppState>,
|
||||
params: web::Json<pattern::Parameters>,
|
||||
) -> Result<impl Responder> {
|
||||
println!("Got params: {:?}", params);
|
||||
data.strip_tx
|
||||
.lock()
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Failed to get a lock"))?
|
||||
.send(strip::Message::ChangePattern(params.0.into_pattern()))
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Failed to send to channel"))?;
|
||||
Ok("Success")
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
pub async fn start(
|
||||
message_tx: Sender<errors::Message>,
|
||||
strip_tx: Sender<strip::Message>,
|
||||
) -> std::io::Result<()> {
|
||||
let _ = message_tx.send(errors::Message::String(String::from("Starting webui")));
|
||||
HttpServer::new(move || {
|
||||
let generated = generate();
|
||||
App::new()
|
||||
.data(AppState {
|
||||
strip_tx: Arc::new(Mutex::new(strip_tx.clone())),
|
||||
})
|
||||
.service(
|
||||
web::scope("/api")
|
||||
.app_data(
|
||||
JsonConfig::default().error_handler(|err: JsonPayloadError, _req| {
|
||||
// let _ = message_tx.send(errors::Message::String(format!("JSON error: {:?}", err)));
|
||||
println!("JSON error: {:?}", err);
|
||||
err.into()
|
||||
}),
|
||||
)
|
||||
.app_data(web::FormConfig::default().error_handler(
|
||||
|err: UrlencodedError, _req| {
|
||||
println!("{:?}", err);
|
||||
err.into()
|
||||
},
|
||||
))
|
||||
.service(set_color_json),
|
||||
)
|
||||
.service(ResourceFiles::new("/", generated))
|
||||
})
|
||||
.bind(("0.0.0.0", 8080))?
|
||||
.run()
|
||||
.await
|
||||
}
|
117
src/wire_protocol.rs
Normal file
117
src/wire_protocol.rs
Normal file
@ -0,0 +1,117 @@
|
||||
use rppal::spi::{Bus, Mode, SlaveSelect, Spi};
|
||||
|
||||
pub struct LedPanel {
|
||||
/// stores [g, r, b] for each led (as opposed to the normal RGB)
|
||||
buffer: Vec<u8>,
|
||||
spi: Spi,
|
||||
num_leds: u32,
|
||||
}
|
||||
|
||||
/// Stores color as a tuple of (Red, Green, Blue)
|
||||
pub struct RGB(pub u8, pub u8, pub u8);
|
||||
|
||||
impl LedPanel {
|
||||
pub fn new(num_leds: u32) -> LedPanel {
|
||||
let bus = Bus::Spi0;
|
||||
let mode = Mode::Mode0;
|
||||
|
||||
let slave = SlaveSelect::Ss0;
|
||||
|
||||
let clock_speed = 2_400_000;
|
||||
let buffer = Vec::new();
|
||||
|
||||
LedPanel {
|
||||
buffer,
|
||||
spi: Spi::new(bus, slave, clock_speed, mode).unwrap(),
|
||||
num_leds,
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self) {
|
||||
let output = self
|
||||
.buffer
|
||||
.drain(..)
|
||||
.flat_map(|val| LedPanel::to_ws2812b_bytes(val).to_vec())
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
self.spi.write(&output).unwrap();
|
||||
}
|
||||
|
||||
pub fn set_leds(&mut self, hex_codes: &[RGB]) {
|
||||
hex_codes.iter().for_each(|hex_code| {
|
||||
// Swapping here from RGB to the GRB expected by the LED panel
|
||||
self.buffer
|
||||
.extend_from_slice(&[hex_code.1, hex_code.0, hex_code.2]);
|
||||
});
|
||||
self.write();
|
||||
}
|
||||
|
||||
// Turns all LEDs off and clears buffer
|
||||
pub fn clear_all_leds(&mut self) {
|
||||
self.buffer.clear();
|
||||
let mut clear_codes = vec![0; (self.num_leds * 3) as usize];
|
||||
|
||||
self.buffer.append(&mut clear_codes);
|
||||
|
||||
self.write();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps one byte to three bytes per the WS2821B protocol
|
||||
* 1 -> 110
|
||||
* 0 -> 100
|
||||
*/
|
||||
pub fn to_ws2812b_bytes(byte: u8) -> [u8; 3] {
|
||||
let mut ret: u32 = 0b0;
|
||||
|
||||
println!("Adding byte {:#b}", byte);
|
||||
// Each bit in byte
|
||||
// for i in (0..8).rev() // Correct order
|
||||
for i in 0..8 {
|
||||
// Reverse order
|
||||
println!(" Pushing bit {:} ({:})", (u32::from(byte) >> i) & 0b1, i);
|
||||
println!(" Now {:#b}", ret);
|
||||
// 0b -> 0b00
|
||||
ret <<= 2;
|
||||
// 0b00 -> 0b1<bit>
|
||||
println!(" Now {:#b}", ret);
|
||||
ret |= 0b10 | ((u32::from(byte) >> i) & 0b1);
|
||||
println!(" Now {:#b}", ret);
|
||||
// 0b1<bit> -> 0b1<bit>0
|
||||
ret <<= 1;
|
||||
println!(" Now {:#b}", ret);
|
||||
}
|
||||
let bytes: [u8; 4] = ret.to_be_bytes();
|
||||
[bytes[1], bytes[2], bytes[3]]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::LedPanel;
|
||||
|
||||
#[test]
|
||||
fn convert_0() {
|
||||
assert_eq!(
|
||||
vec![0b10010010, 0b01001001, 0b00100100],
|
||||
LedPanel::to_ws2812b_bytes(0b00000000)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_number() {
|
||||
// assert_eq!(vec![0b10011010, 0b01101001, 0b10110100], LedPanel::to_ws2812b_bytes(0b01010110)); // Regular
|
||||
assert_eq!(
|
||||
vec![0b10011011, 0b01001101, 0b00110100],
|
||||
LedPanel::to_ws2812b_bytes(0b01010110)
|
||||
); // Reversed
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_max() {
|
||||
assert_eq!(
|
||||
vec![0b11011011, 0b01101101, 0b10110110],
|
||||
LedPanel::to_ws2812b_bytes(0b11111111)
|
||||
);
|
||||
}
|
||||
}
|
4
web/.gitignore
vendored
4
web/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/node_modules/
|
||||
/public/build/
|
||||
|
||||
.DS_Store
|
3
web/.vscode/extensions.json
vendored
3
web/.vscode/extensions.json
vendored
@ -1,3 +0,0 @@
|
||||
{
|
||||
"recommendations": ["svelte.svelte-vscode"]
|
||||
}
|
105
web/README.md
105
web/README.md
@ -1,105 +0,0 @@
|
||||
*Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
|
||||
|
||||
---
|
||||
|
||||
# svelte app
|
||||
|
||||
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
|
||||
|
||||
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
|
||||
|
||||
```bash
|
||||
npx degit sveltejs/template svelte-app
|
||||
cd svelte-app
|
||||
```
|
||||
|
||||
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
|
||||
|
||||
|
||||
## Get started
|
||||
|
||||
Install the dependencies...
|
||||
|
||||
```bash
|
||||
cd svelte-app
|
||||
npm install
|
||||
```
|
||||
|
||||
...then start [Rollup](https://rollupjs.org):
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
|
||||
|
||||
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
|
||||
|
||||
If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.
|
||||
|
||||
## Building and running in production mode
|
||||
|
||||
To create an optimised version of the app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
|
||||
|
||||
|
||||
## Single-page app mode
|
||||
|
||||
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
|
||||
|
||||
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
|
||||
|
||||
```js
|
||||
"start": "sirv public --single"
|
||||
```
|
||||
|
||||
## Using TypeScript
|
||||
|
||||
This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:
|
||||
|
||||
```bash
|
||||
node scripts/setupTypeScript.js
|
||||
```
|
||||
|
||||
Or remove the script via:
|
||||
|
||||
```bash
|
||||
rm scripts/setupTypeScript.js
|
||||
```
|
||||
|
||||
## Deploying to the web
|
||||
|
||||
### With [Vercel](https://vercel.com)
|
||||
|
||||
Install `vercel` if you haven't already:
|
||||
|
||||
```bash
|
||||
npm install -g vercel
|
||||
```
|
||||
|
||||
Then, from within your project folder:
|
||||
|
||||
```bash
|
||||
cd public
|
||||
vercel deploy --name my-project
|
||||
```
|
||||
|
||||
### With [surge](https://surge.sh/)
|
||||
|
||||
Install `surge` if you haven't already:
|
||||
|
||||
```bash
|
||||
npm install -g surge
|
||||
```
|
||||
|
||||
Then, from within your project folder:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
surge public my-project.surge.sh
|
||||
```
|
962
web/package-lock.json
generated
962
web/package-lock.json
generated
@ -1,962 +0,0 @@
|
||||
{
|
||||
"name": "svelte-app",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz",
|
||||
"integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.14.5"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.14.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz",
|
||||
"integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
|
||||
"integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.14.5",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@polka/url": {
|
||||
"version": "1.0.0-next.15",
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.15.tgz",
|
||||
"integrity": "sha512-15spi3V28QdevleWBNXE4pIls3nFZmBbUGrW9IVPwiQczuSb9n76TCB4bsk8TSel+I1OkHEdPhu5QKMfY6rQHA=="
|
||||
},
|
||||
"@rollup/plugin-commonjs": {
|
||||
"version": "17.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz",
|
||||
"integrity": "sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.1.0",
|
||||
"commondir": "^1.0.1",
|
||||
"estree-walker": "^2.0.1",
|
||||
"glob": "^7.1.6",
|
||||
"is-reference": "^1.2.1",
|
||||
"magic-string": "^0.25.7",
|
||||
"resolve": "^1.17.0"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-node-resolve": {
|
||||
"version": "11.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz",
|
||||
"integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.1.0",
|
||||
"@types/resolve": "1.17.1",
|
||||
"builtin-modules": "^3.1.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"is-module": "^1.0.0",
|
||||
"resolve": "^1.19.0"
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-typescript": {
|
||||
"version": "8.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.2.5.tgz",
|
||||
"integrity": "sha512-QL/LvDol/PAGB2O0S7/+q2HpSUNodpw7z6nGn9BfoVCPOZ0r4EALrojFU29Bkoi2Hr2jgTocTejJ5GGWZfOxbQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "^3.1.0",
|
||||
"resolve": "^1.17.0"
|
||||
}
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
|
||||
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "0.0.39",
|
||||
"estree-walker": "^1.0.1",
|
||||
"picomatch": "^2.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"estree-walker": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
|
||||
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tsconfig/svelte": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-2.0.1.tgz",
|
||||
"integrity": "sha512-aqkICXbM1oX5FfgZd2qSSAGdyo/NRxjWCamxoyi3T8iVQnzGge19HhDYzZ6NrVOW7bhcWNSq9XexWFtMzbB24A==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "0.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
|
||||
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.4.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.4.13.tgz",
|
||||
"integrity": "sha512-bLL69sKtd25w7p1nvg9pigE4gtKVpGTPojBFLMkGHXuUgap2sLqQt2qUnqmVCDfzGUL0DRNZP+1prIZJbMeAXg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/pug": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.5.tgz",
|
||||
"integrity": "sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/resolve": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
|
||||
"integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/sass": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/sass/-/sass-1.16.1.tgz",
|
||||
"integrity": "sha512-iZUcRrGuz/Tbg3loODpW7vrQJkUtpY2fFSf4ELqqkApcS2TkZ1msk7ie8iZPB86lDOP8QOTTmuvWjc5S0R9OjQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"builtin-modules": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
|
||||
"integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
|
||||
"dev": true
|
||||
},
|
||||
"callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
|
||||
"integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.3.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"commondir": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"console-clear": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/console-clear/-/console-clear-1.1.1.tgz",
|
||||
"integrity": "sha512-pMD+MVR538ipqkG5JXeOEbKWS5um1H4LUUccUQG68qpeqBYbzYy79Gh55jkd2TtPdRfUaLWdv6LPP//5Zt0aPQ=="
|
||||
},
|
||||
"deepmerge": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
|
||||
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
|
||||
"dev": true
|
||||
},
|
||||
"detect-indent": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
|
||||
"integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"estree-walker": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
|
||||
"dev": true
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
},
|
||||
"get-port": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz",
|
||||
"integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
|
||||
"integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"parent-module": "^1.0.0",
|
||||
"resolve-from": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
},
|
||||
"is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"is-core-module": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz",
|
||||
"integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"is-extglob": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
|
||||
"dev": true
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
|
||||
"integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-extglob": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"is-module": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
|
||||
"integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
|
||||
"dev": true
|
||||
},
|
||||
"is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"is-reference": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
|
||||
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"jest-worker": {
|
||||
"version": "26.6.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
|
||||
"integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
"supports-color": "^7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"kleur": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="
|
||||
},
|
||||
"livereload": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz",
|
||||
"integrity": "sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": "^3.5.0",
|
||||
"livereload-js": "^3.3.1",
|
||||
"opts": ">= 1.2.0",
|
||||
"ws": "^7.4.3"
|
||||
}
|
||||
},
|
||||
"livereload-js": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.3.2.tgz",
|
||||
"integrity": "sha512-w677WnINxFkuixAoUEXOStewzLYGI76XVag+0JWMMEyjJQKs0ibWZMxkTlB96Lm3EjZ7IeOxVziBEbtxVQqQZA==",
|
||||
"dev": true
|
||||
},
|
||||
"local-access": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/local-access/-/local-access-1.1.0.tgz",
|
||||
"integrity": "sha512-XfegD5pyTAfb+GY6chk283Ox5z8WexG56OvM06RWLpAc/UHozO8X6xAxEkIitZOtsSMM1Yr3DkHgW5W+onLhCw=="
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.25.7",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
|
||||
"integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sourcemap-codec": "^1.4.4"
|
||||
}
|
||||
},
|
||||
"merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"mime": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz",
|
||||
"integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg=="
|
||||
},
|
||||
"min-indent": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
|
||||
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
},
|
||||
"mri": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz",
|
||||
"integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ=="
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"opts": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/opts/-/opts-2.0.2.tgz",
|
||||
"integrity": "sha512-k41FwbcLnlgnFh69f4qdUfvDQ+5vaSDnVPFI/y5XuhKRq97EnVVneO9F1ESVCdiVu4fCS2L8usX3mU331hB7pg==",
|
||||
"dev": true
|
||||
},
|
||||
"parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"callsites": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
|
||||
"integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"randombytes": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"require-relative": {
|
||||
"version": "0.8.7",
|
||||
"resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz",
|
||||
"integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=",
|
||||
"dev": true
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.20.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
|
||||
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-core-module": "^2.2.0",
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||
"dev": true
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.56.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.2.tgz",
|
||||
"integrity": "sha512-s8H00ZsRi29M2/lGdm1u8DJpJ9ML8SUOpVVBd33XNeEeL3NVaTiUcSBHzBdF3eAyR0l7VSpsuoVUGrRHq7aPwQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-css-only": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-3.1.0.tgz",
|
||||
"integrity": "sha512-TYMOE5uoD76vpj+RTkQLzC9cQtbnJNktHPB507FzRWBVaofg7KhIqq1kGbcVOadARSozWF883Ho9KpSPKH8gqA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@rollup/pluginutils": "4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.1.1.tgz",
|
||||
"integrity": "sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estree-walker": "^2.0.1",
|
||||
"picomatch": "^2.2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rollup-plugin-livereload": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-livereload/-/rollup-plugin-livereload-2.0.5.tgz",
|
||||
"integrity": "sha512-vqQZ/UQowTW7VoiKEM5ouNW90wE5/GZLfdWuR0ELxyKOJUIaj+uismPZZaICU4DnWPVjnpCDDxEqwU7pcKY/PA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"livereload": "^0.9.1"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-svelte": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-7.1.0.tgz",
|
||||
"integrity": "sha512-vopCUq3G+25sKjwF5VilIbiY6KCuMNHP1PFvx2Vr3REBNMDllKHFZN2B9jwwC+MqNc3UPKkjXnceLPEjTjXGXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"require-relative": "^0.8.7",
|
||||
"rollup-pluginutils": "^2.8.2"
|
||||
}
|
||||
},
|
||||
"rollup-plugin-terser": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz",
|
||||
"integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.10.4",
|
||||
"jest-worker": "^26.2.1",
|
||||
"serialize-javascript": "^4.0.0",
|
||||
"terser": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"rollup-pluginutils": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz",
|
||||
"integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"estree-walker": "^0.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"estree-walker": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz",
|
||||
"integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"sade": {
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz",
|
||||
"integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==",
|
||||
"requires": {
|
||||
"mri": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"semiver": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz",
|
||||
"integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg=="
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"sirv": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.12.tgz",
|
||||
"integrity": "sha512-+jQoCxndz7L2tqQL4ZyzfDhky0W/4ZJip3XoOuxyQWnAwMxindLl3Xv1qT4x1YX/re0leShvTm8Uk0kQspGhBg==",
|
||||
"requires": {
|
||||
"@polka/url": "^1.0.0-next.15",
|
||||
"mime": "^2.3.1",
|
||||
"totalist": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"sirv-cli": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/sirv-cli/-/sirv-cli-1.0.12.tgz",
|
||||
"integrity": "sha512-Rs5PvF3a48zuLmrl8vcqVv9xF/WWPES19QawVkpdzqx7vD5SMZS07+ece1gK4umbslXN43YeIksYtQM5csgIzQ==",
|
||||
"requires": {
|
||||
"console-clear": "^1.1.0",
|
||||
"get-port": "^3.2.0",
|
||||
"kleur": "^3.0.0",
|
||||
"local-access": "^1.0.1",
|
||||
"sade": "^1.6.0",
|
||||
"semiver": "^1.0.0",
|
||||
"sirv": "^1.0.12",
|
||||
"tinydate": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"sourcemap-codec": {
|
||||
"version": "1.4.8",
|
||||
"resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
|
||||
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
|
||||
"dev": true
|
||||
},
|
||||
"strip-indent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
|
||||
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"min-indent": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"svelte": {
|
||||
"version": "3.42.1",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.42.1.tgz",
|
||||
"integrity": "sha512-XtExLd2JAU3T7M2g/DkO3UNj/3n1WdTXrfL63OZ5nZq7nAqd9wQw+lR4Pv/wkVbrWbAIPfLDX47UjFdmnY+YtQ==",
|
||||
"dev": true
|
||||
},
|
||||
"svelte-check": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-2.2.4.tgz",
|
||||
"integrity": "sha512-eGEuZ3UEanOhlpQhICLjKejDxcZ9uYJlGnBGKAPW7uugolaBE6HpEBIiKFZN/TMRFFHQUURgGvsVn8/HJUBfeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"chokidar": "^3.4.1",
|
||||
"glob": "^7.1.6",
|
||||
"import-fresh": "^3.2.1",
|
||||
"minimist": "^1.2.5",
|
||||
"sade": "^1.7.4",
|
||||
"source-map": "^0.7.3",
|
||||
"svelte-preprocess": "^4.0.0",
|
||||
"typescript": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "~1.1.4"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"svelte-preprocess": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.7.4.tgz",
|
||||
"integrity": "sha512-mDAmaltQl6e5zU2VEtoWEf7eLTfuOTGr9zt+BpA3AGHo8MIhKiNSPE9OLTCTOMgj0vj/uL9QBbaNmpG4G1CgIA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/pug": "^2.0.4",
|
||||
"@types/sass": "^1.16.0",
|
||||
"detect-indent": "^6.0.0",
|
||||
"strip-indent": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"terser": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.7.1.tgz",
|
||||
"integrity": "sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"commander": "^2.20.0",
|
||||
"source-map": "~0.7.2",
|
||||
"source-map-support": "~0.5.19"
|
||||
}
|
||||
},
|
||||
"tinydate": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tinydate/-/tinydate-1.3.0.tgz",
|
||||
"integrity": "sha512-7cR8rLy2QhYHpsBDBVYnnWXm8uRTr38RoZakFSW7Bs7PzfMPNZthuMLkwqZv7MTu8lhQ91cOFYS5a7iFj2oR3w=="
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"totalist": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz",
|
||||
"integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==",
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz",
|
||||
"integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.5.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
|
||||
"integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "svelte-app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"dev": "rollup -c -w",
|
||||
"start": "sirv public --no-clear",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup-plugin-css-only": "^3.1.0",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
"rollup-plugin-svelte": "^7.0.0",
|
||||
"rollup-plugin-terser": "^7.0.0",
|
||||
"svelte": "^3.0.0",
|
||||
"svelte-check": "^2.0.0",
|
||||
"svelte-preprocess": "^4.0.0",
|
||||
"@rollup/plugin-typescript": "^8.0.0",
|
||||
"typescript": "^4.0.0",
|
||||
"tslib": "^2.0.0",
|
||||
"@tsconfig/svelte": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"sirv-cli": "^1.0.0"
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 3.1 KiB |
@ -1,63 +0,0 @@
|
||||
html, body {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgb(0,100,200);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: rgb(0,80,160);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input, button, select, textarea {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
-webkit-padding: 0.4em 0;
|
||||
padding: 0.4em;
|
||||
margin: 0 0 0.5em 0;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
button {
|
||||
color: #333;
|
||||
background-color: #f4f4f4;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
button:not(:disabled):active {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
border-color: #666;
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||
|
||||
<title>Svelte app</title>
|
||||
|
||||
<link rel='icon' type='image/png' href='/favicon.png'>
|
||||
<link rel='stylesheet' href='/global.css'>
|
||||
<link rel='stylesheet' href='/build/bundle.css'>
|
||||
|
||||
<script defer src='/build/bundle.js'></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
@ -1,83 +0,0 @@
|
||||
import svelte from 'rollup-plugin-svelte';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import livereload from 'rollup-plugin-livereload';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import sveltePreprocess from 'svelte-preprocess';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import css from 'rollup-plugin-css-only';
|
||||
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
|
||||
function serve() {
|
||||
let server;
|
||||
|
||||
function toExit() {
|
||||
if (server) server.kill(0);
|
||||
}
|
||||
|
||||
return {
|
||||
writeBundle() {
|
||||
if (server) return;
|
||||
server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
|
||||
stdio: ['ignore', 'inherit', 'inherit'],
|
||||
shell: true
|
||||
});
|
||||
|
||||
process.on('SIGTERM', toExit);
|
||||
process.on('exit', toExit);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
input: 'src/main.ts',
|
||||
output: {
|
||||
sourcemap: true,
|
||||
format: 'iife',
|
||||
name: 'app',
|
||||
file: 'public/build/bundle.js'
|
||||
},
|
||||
plugins: [
|
||||
svelte({
|
||||
preprocess: sveltePreprocess({ sourceMap: !production }),
|
||||
compilerOptions: {
|
||||
// enable run-time checks when not in production
|
||||
dev: !production
|
||||
}
|
||||
}),
|
||||
// we'll extract any component CSS out into
|
||||
// a separate file - better for performance
|
||||
css({ output: 'bundle.css' }),
|
||||
|
||||
// If you have external dependencies installed from
|
||||
// npm, you'll most likely need these plugins. In
|
||||
// some cases you'll need additional configuration -
|
||||
// consult the documentation for details:
|
||||
// https://github.com/rollup/plugins/tree/master/packages/commonjs
|
||||
resolve({
|
||||
browser: true,
|
||||
dedupe: ['svelte']
|
||||
}),
|
||||
commonjs(),
|
||||
typescript({
|
||||
sourceMap: !production,
|
||||
inlineSources: !production
|
||||
}),
|
||||
|
||||
// In dev mode, call `npm run start` once
|
||||
// the bundle has been generated
|
||||
!production && serve(),
|
||||
|
||||
// Watch the `public` directory and refresh the
|
||||
// browser on changes when not in production
|
||||
!production && livereload('public'),
|
||||
|
||||
// If we're building for production (npm run build
|
||||
// instead of npm run dev), minify
|
||||
production && terser()
|
||||
],
|
||||
watch: {
|
||||
clearScreen: true
|
||||
}
|
||||
};
|
@ -1,10 +0,0 @@
|
||||
<script lang="ts">
|
||||
export let name: string = "Light Control";
|
||||
import Form from "./Form.svelte";
|
||||
|
||||
function handleOnSubmit() {
|
||||
console.log("Submitted");
|
||||
}
|
||||
</script>
|
||||
|
||||
<Form on:submit={handleOnSubmit}></Form>
|
@ -1,21 +0,0 @@
|
||||
<script lang="ts">
|
||||
// From: https://css-tricks.com/snippets/javascript/random-hex-color/
|
||||
function randomColor() {
|
||||
return "#" + Math.floor(Math.random()*16777215).toString(16);
|
||||
}
|
||||
export let value = [randomColor()];
|
||||
|
||||
function newColor() {
|
||||
value = [...value, randomColor()];
|
||||
}
|
||||
function removeColor() {
|
||||
if(newColor.length > 1) {
|
||||
value = value.slice(0, -1);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{#each value as value}
|
||||
<input type="color" bind:value={value} />
|
||||
{/each}
|
||||
<button type="button" on:click={newColor} href="#">+</button>
|
||||
<button type="button" on:click={removeColor} href="#">-</button>
|
@ -1,96 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Colors from "./Colors.svelte";
|
||||
|
||||
let url = "/api/setcolor";
|
||||
let possiblePatterns = [
|
||||
{name: "Solid", text: "Solid", formElements: [
|
||||
{name: "color", type: "color", label: "Color", value: "#000000"},
|
||||
]},
|
||||
{name: "Collide", text: "Collide", formElements: [
|
||||
{name: "left_color", type: "color", label: "Left Color", value: "#000000"},
|
||||
{name: "right_color", type: "color", label: "Right Color", value: "#000000"},
|
||||
{name: "conjoined_color", type: "color", label: "Conjoined Color", value: "#000000"},
|
||||
]},
|
||||
{name: "Fade", text: "Fade", formElements: [
|
||||
{name: "color", type: "color", label: "Color", value: "#000000"},
|
||||
]},
|
||||
{name: "MovingPixel", text: "MovingPixel", formElements: [
|
||||
{name: "color", type: "color", label: "Color", value: "#000000"},
|
||||
]},
|
||||
{name: "MovingRainbow", text: "MovingRainbow", formElements: [
|
||||
{name: "width", type: "number", label: "Width", value: 4},
|
||||
{name: "forward", type: "checkbox", label: "More Forward?", value: ""},
|
||||
{name: "skip", type: "number", label: "# to skip", value: ""},
|
||||
]},
|
||||
{name: "Orb", text: "Orb", formElements: [
|
||||
{name: "color", type: "color", label: "Color", value: "#000000"},
|
||||
{name: "center_width", type: "number", label: "Center Width", value: "1"},
|
||||
{name: "backoff_width", type: "number", label: "Backoff Width", value: "0"},
|
||||
]},
|
||||
{name: "Flashing", text: "Flashing", formElements: [
|
||||
{name: "color", type: "colors", label: "Color", value: []},
|
||||
{name: "width", type: "number", label: "Width", value: 10},
|
||||
{name: "tick_rate", type: "number", label: "Tick Rate", value: 10},
|
||||
]},
|
||||
];
|
||||
let selectedPattern = possiblePatterns[0];
|
||||
|
||||
function hexToRgb(hex) {
|
||||
// Adapted from https://stackoverflow.com/a/5624139
|
||||
var result = /^#?([a-f\d]{1,2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
console.log(hex);
|
||||
result.splice(0, 1);
|
||||
return result ? result.map(r => parseInt(r, 16)) : null;
|
||||
}
|
||||
|
||||
function getFormData() {
|
||||
var ret = {};
|
||||
ret[selectedPattern.name] = selectedPattern.formElements.map(x => {
|
||||
switch(x.type) {
|
||||
case "color":
|
||||
return hexToRgb(x.value);
|
||||
break;
|
||||
case "number":
|
||||
return parseInt(x.value);
|
||||
break;
|
||||
case "checkbox":
|
||||
return x.value === "on";
|
||||
break;
|
||||
case "colors":
|
||||
console.log(x);
|
||||
return x.value.map(hexToRgb);
|
||||
break;
|
||||
default:
|
||||
return x.value;
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
const response = fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(getFormData())
|
||||
}).then(r => console.log(response));
|
||||
}
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={handleSubmit}>
|
||||
<select bind:value={selectedPattern}>
|
||||
{#each possiblePatterns as p}
|
||||
<option value={p}>{p.text}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#each selectedPattern.formElements as fe}
|
||||
<label for={fe.name}>{fe.label}</label>
|
||||
{#if fe.type === "colors"}
|
||||
<Colors bind:value={fe.value}/>
|
||||
{:else}
|
||||
<input type={fe.type} name={fe.name} on:input={(e) => fe.value = e.target.value} />
|
||||
{/if}
|
||||
{/each}
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
@ -1 +0,0 @@
|
||||
|
1
web/src/global.d.ts
vendored
1
web/src/global.d.ts
vendored
@ -1 +0,0 @@
|
||||
/// <reference types="svelte" />
|
@ -1,10 +0,0 @@
|
||||
import App from './App.svelte';
|
||||
|
||||
const app = new App({
|
||||
target: document.body,
|
||||
props: {
|
||||
name: 'world'
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
@ -1,6 +0,0 @@
|
||||
{
|
||||
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules/*", "__sapper__/*", "public/*"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user