Clean up obsolete files
- Remove old web.py, wifi.py, patterns.py - Remove old static files from root - Remove unused component files
This commit is contained in:
291
src/patterns.py
291
src/patterns.py
@@ -1,291 +0,0 @@
|
|||||||
from machine import Pin
|
|
||||||
from neopixel import NeoPixel
|
|
||||||
import utime
|
|
||||||
import random
|
|
||||||
|
|
||||||
class Patterns:
|
|
||||||
def __init__(self, pin, num_leds, color1=(0,0,0), color2=(0,0,0), brightness=127, selected="rainbow_cycle", delay=100):
|
|
||||||
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
|
|
||||||
self.num_leds = num_leds
|
|
||||||
self.pattern_step = 0
|
|
||||||
self.last_update = utime.ticks_ms()
|
|
||||||
self.delay = delay
|
|
||||||
self.brightness = brightness
|
|
||||||
self.patterns = {
|
|
||||||
"off": self.off,
|
|
||||||
"on" : self.on,
|
|
||||||
"color_wipe": self.color_wipe_step,
|
|
||||||
"rainbow_cycle": self.rainbow_cycle_step,
|
|
||||||
"theater_chase": self.theater_chase_step,
|
|
||||||
"blink": self.blink_step,
|
|
||||||
"random_color_wipe": self.random_color_wipe_step,
|
|
||||||
"random_rainbow_cycle": self.random_rainbow_cycle_step,
|
|
||||||
"random_theater_chase": self.random_theater_chase_step,
|
|
||||||
"random_blink": self.random_blink_step,
|
|
||||||
"color_transition": self.color_transition_step,
|
|
||||||
"external": None
|
|
||||||
}
|
|
||||||
self.selected = selected
|
|
||||||
self.color1 = color1
|
|
||||||
self.color2 = color2
|
|
||||||
self.transition_duration = 50 # Duration of color transition in milliseconds
|
|
||||||
self.transition_step = 0
|
|
||||||
|
|
||||||
def sync(self):
|
|
||||||
self.pattern_step=0
|
|
||||||
self.last_update = utime.ticks_ms()
|
|
||||||
|
|
||||||
def tick(self):
|
|
||||||
if self.patterns[self.selected]:
|
|
||||||
self.patterns[self.selected]()
|
|
||||||
|
|
||||||
def update_num_leds(self, pin, num_leds):
|
|
||||||
self.n = NeoPixel(Pin(pin, Pin.OUT), num_leds)
|
|
||||||
self.num_leds = num_leds
|
|
||||||
self.pattern_step = 0
|
|
||||||
|
|
||||||
def set_delay(self, delay):
|
|
||||||
self.delay = delay
|
|
||||||
|
|
||||||
def set_brightness(self, brightness):
|
|
||||||
self.brightness = brightness
|
|
||||||
|
|
||||||
def set_color1(self, color):
|
|
||||||
print(color)
|
|
||||||
self.color1 = self.apply_brightness(color)
|
|
||||||
|
|
||||||
def set_color2(self, color):
|
|
||||||
self.color2 = self.apply_brightness(color)
|
|
||||||
|
|
||||||
def apply_brightness(self, color):
|
|
||||||
return tuple(int(c * self.brightness / 255) for c in color)
|
|
||||||
|
|
||||||
def select(self, pattern):
|
|
||||||
if pattern in self.patterns:
|
|
||||||
self.selected = pattern
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def set(self, i, color):
|
|
||||||
self.n[i] = color
|
|
||||||
|
|
||||||
def write(self):
|
|
||||||
self.n.write()
|
|
||||||
|
|
||||||
def fill(self):
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
self.n[i] = self.color1
|
|
||||||
self.n.write()
|
|
||||||
|
|
||||||
def off(self):
|
|
||||||
color = self.color1
|
|
||||||
self.color1 = (0,0,0)
|
|
||||||
self.fill()
|
|
||||||
self.color1 = color
|
|
||||||
|
|
||||||
def on(self):
|
|
||||||
color = self.color1
|
|
||||||
self.color1 = self.apply_brightness(self.color1)
|
|
||||||
self.fill()
|
|
||||||
self.color1 = color
|
|
||||||
|
|
||||||
|
|
||||||
def color_wipe_step(self):
|
|
||||||
color = self.apply_brightness(self.color1)
|
|
||||||
current_time = utime.ticks_ms()
|
|
||||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
|
||||||
if self.pattern_step < self.num_leds:
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
self.n[i] = (0, 0, 0)
|
|
||||||
self.n[self.pattern_step] = self.apply_brightness(color)
|
|
||||||
self.n.write()
|
|
||||||
self.pattern_step += 1
|
|
||||||
else:
|
|
||||||
self.pattern_step = 0
|
|
||||||
self.last_update = current_time
|
|
||||||
|
|
||||||
def rainbow_cycle_step(self):
|
|
||||||
current_time = utime.ticks_ms()
|
|
||||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay/5:
|
|
||||||
def wheel(pos):
|
|
||||||
if pos < 85:
|
|
||||||
return (pos * 3, 255 - pos * 3, 0)
|
|
||||||
elif pos < 170:
|
|
||||||
pos -= 85
|
|
||||||
return (255 - pos * 3, 0, pos * 3)
|
|
||||||
else:
|
|
||||||
pos -= 170
|
|
||||||
return (0, pos * 3, 255 - pos * 3)
|
|
||||||
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
rc_index = (i * 256 // self.num_leds) + self.pattern_step
|
|
||||||
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
|
|
||||||
self.n.write()
|
|
||||||
self.pattern_step = (self.pattern_step + 1) % 256
|
|
||||||
self.last_update = current_time
|
|
||||||
|
|
||||||
def theater_chase_step(self):
|
|
||||||
current_time = utime.ticks_ms()
|
|
||||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
if (i + self.pattern_step) % 3 == 0:
|
|
||||||
self.n[i] = self.apply_brightness(self.color1)
|
|
||||||
else:
|
|
||||||
self.n[i] = (0, 0, 0)
|
|
||||||
self.n.write()
|
|
||||||
self.pattern_step = (self.pattern_step + 1) % 3
|
|
||||||
self.last_update = current_time
|
|
||||||
|
|
||||||
def blink_step(self):
|
|
||||||
current_time = utime.ticks_ms()
|
|
||||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
|
||||||
if self.pattern_step % 2 == 0:
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
self.n[i] = self.apply_brightness(self.color1)
|
|
||||||
else:
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
self.n[i] = (0, 0, 0)
|
|
||||||
self.n.write()
|
|
||||||
self.pattern_step = (self.pattern_step + 1) % 2
|
|
||||||
self.last_update = current_time
|
|
||||||
|
|
||||||
def random_color_wipe_step(self):
|
|
||||||
current_time = utime.ticks_ms()
|
|
||||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
|
||||||
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
|
||||||
if self.pattern_step < self.num_leds:
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
self.n[i] = (0, 0, 0)
|
|
||||||
self.n[self.pattern_step] = self.apply_brightness(color)
|
|
||||||
self.n.write()
|
|
||||||
self.pattern_step += 1
|
|
||||||
else:
|
|
||||||
self.pattern_step = 0
|
|
||||||
self.last_update = current_time
|
|
||||||
|
|
||||||
def random_rainbow_cycle_step(self):
|
|
||||||
current_time = utime.ticks_ms()
|
|
||||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
|
||||||
def wheel(pos):
|
|
||||||
if pos < 85:
|
|
||||||
return (pos * 3, 255 - pos * 3, 0)
|
|
||||||
elif pos < 170:
|
|
||||||
pos -= 85
|
|
||||||
return (255 - pos * 3, 0, pos * 3)
|
|
||||||
else:
|
|
||||||
pos -= 170
|
|
||||||
return (0, pos * 3, 255 - pos * 3)
|
|
||||||
|
|
||||||
random_offset = random.randint(0, 255)
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
rc_index = (i * 256 // self.num_leds) + self.pattern_step + random_offset
|
|
||||||
self.n[i] = self.apply_brightness(wheel(rc_index & 255))
|
|
||||||
self.n.write()
|
|
||||||
self.pattern_step = (self.pattern_step + 1) % 256
|
|
||||||
self.last_update = current_time
|
|
||||||
|
|
||||||
def random_theater_chase_step(self):
|
|
||||||
current_time = utime.ticks_ms()
|
|
||||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
|
||||||
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
if (i + self.pattern_step) % 3 == 0:
|
|
||||||
self.n[i] = self.apply_brightness(color)
|
|
||||||
else:
|
|
||||||
self.n[i] = (0, 0, 0)
|
|
||||||
self.n.write()
|
|
||||||
self.pattern_step = (self.pattern_step + 1) % 3
|
|
||||||
self.last_update = current_time
|
|
||||||
|
|
||||||
def random_blink_step(self):
|
|
||||||
current_time = utime.ticks_ms()
|
|
||||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
|
||||||
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
|
|
||||||
if self.pattern_step % 2 == 0:
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
self.n[i] = self.apply_brightness(color)
|
|
||||||
else:
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
self.n[i] = (0, 0, 0)
|
|
||||||
self.n.write()
|
|
||||||
self.pattern_step = (self.pattern_step + 1) % 2
|
|
||||||
self.last_update = current_time
|
|
||||||
|
|
||||||
def color_transition_step(self):
|
|
||||||
current_time = utime.ticks_ms()
|
|
||||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
|
||||||
# Calculate transition factor based on elapsed time
|
|
||||||
transition_factor = (self.pattern_step * 100) / self.transition_duration
|
|
||||||
if transition_factor > 100:
|
|
||||||
transition_factor = 100
|
|
||||||
color = self.interpolate_color(self.color1, self.color2, transition_factor / 100)
|
|
||||||
|
|
||||||
# Apply the interpolated color to all LEDs
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
self.n[i] = self.apply_brightness(color)
|
|
||||||
self.n.write()
|
|
||||||
|
|
||||||
self.pattern_step += self.delay
|
|
||||||
if self.pattern_step > self.transition_duration:
|
|
||||||
self.pattern_step = 0
|
|
||||||
|
|
||||||
self.last_update = current_time
|
|
||||||
|
|
||||||
def interpolate_color(self, color1, color2, factor):
|
|
||||||
return (
|
|
||||||
int(color1[0] + (color2[0] - color1[0]) * factor),
|
|
||||||
int(color1[1] + (color2[1] - color1[1]) * factor),
|
|
||||||
int(color1[2] + (color2[2] - color1[2]) * factor)
|
|
||||||
)
|
|
||||||
|
|
||||||
def two_steps_forward_one_step_back_step(self):
|
|
||||||
current_time = utime.ticks_ms()
|
|
||||||
if utime.ticks_diff(current_time, self.last_update) >= self.delay:
|
|
||||||
# Move forward 2 steps and backward 1 step
|
|
||||||
if self.direction == 1: # Moving forward
|
|
||||||
if self.scanner_position < self.num_leds - 2:
|
|
||||||
self.scanner_position += 2 # Move forward 2 steps
|
|
||||||
else:
|
|
||||||
self.direction = -1 # Change direction to backward
|
|
||||||
else: # Moving backward
|
|
||||||
if self.scanner_position > 0:
|
|
||||||
self.scanner_position -= 1 # Move backward 1 step
|
|
||||||
else:
|
|
||||||
self.direction = 1 # Change direction to forward
|
|
||||||
|
|
||||||
# Set all LEDs to off
|
|
||||||
for i in range(self.num_leds):
|
|
||||||
self.n[i] = (0, 0, 0)
|
|
||||||
|
|
||||||
# Set the current position to the color
|
|
||||||
self.n[self.scanner_position] = self.apply_brightness(self.color1)
|
|
||||||
|
|
||||||
# Apply the color transition
|
|
||||||
transition_factor = (self.pattern_step * 100) / self.transition_duration
|
|
||||||
if transition_factor > 100:
|
|
||||||
transition_factor = 100
|
|
||||||
color = self.interpolate_color(self.color1, self.color2, transition_factor / 100)
|
|
||||||
self.n[self.scanner_position] = self.apply_brightness(color)
|
|
||||||
|
|
||||||
self.n.write()
|
|
||||||
self.pattern_step += self.delay
|
|
||||||
if self.pattern_step > self.transition_duration:
|
|
||||||
self.pattern_step = 0
|
|
||||||
|
|
||||||
self.last_update = current_time
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
p = Patterns(4, 180)
|
|
||||||
p.set_color1((255,0,0))
|
|
||||||
p.set_color2((0,255,0))
|
|
||||||
#p.set_delay(10)
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
for key in p.patterns:
|
|
||||||
print(key)
|
|
||||||
p.select(key)
|
|
||||||
for _ in range(2000):
|
|
||||||
p.tick()
|
|
||||||
utime.sleep_ms(1)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
p.fill((0, 0, 0))
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
import { getWebSocket } from "./websocket.js";
|
|
||||||
|
|
||||||
export class LightComponent extends HTMLElement {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
// Create a shadow DOM for encapsulation
|
|
||||||
const shadow = this.attachShadow({ mode: "open" });
|
|
||||||
|
|
||||||
// Create the content for the component
|
|
||||||
const style = document.createElement("style");
|
|
||||||
style.textContent = `
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
background-color: #4caf50;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 100px;
|
|
||||||
cursor: grab;
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
:host:active {
|
|
||||||
cursor: grabbing;
|
|
||||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-picker {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Create the main content (draggable area)
|
|
||||||
const content = document.createElement("div");
|
|
||||||
content.textContent = this.textContent || "Light Me Up!";
|
|
||||||
content.style.position = "absolute";
|
|
||||||
content.style.top = "0";
|
|
||||||
content.style.left = "0";
|
|
||||||
content.style.width = "100%";
|
|
||||||
content.style.height = "100%";
|
|
||||||
content.style.display = "flex";
|
|
||||||
content.style.justifyContent = "center";
|
|
||||||
content.style.alignItems = "center";
|
|
||||||
|
|
||||||
// Create the color picker
|
|
||||||
const colorPicker = document.createElement("input");
|
|
||||||
colorPicker.type = "color";
|
|
||||||
colorPicker.classList.add("color-picker");
|
|
||||||
colorPicker.value = "#4caf50"; // Default color
|
|
||||||
colorPicker.addEventListener("input", () => {
|
|
||||||
this.style.backgroundColor = colorPicker.value;
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent("color-change", {
|
|
||||||
detail: { lightId: this.lightId, color: colorPicker.value },
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Append the style, content, and color picker to the shadow DOM
|
|
||||||
shadow.appendChild(style);
|
|
||||||
shadow.appendChild(content);
|
|
||||||
shadow.appendChild(colorPicker);
|
|
||||||
|
|
||||||
// Add event listeners for drag-and-drop
|
|
||||||
content.addEventListener("mousedown", this.handleMouseDown.bind(this));
|
|
||||||
document.addEventListener("mousemove", this.handleMouseMove.bind(this));
|
|
||||||
document.addEventListener("mouseup", this.handleMouseUp.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track the initial mouse position and component position
|
|
||||||
handleMouseDown(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// Get the initial mouse position relative to the component
|
|
||||||
this.initialMouseX = event.clientX;
|
|
||||||
this.initialMouseY = event.clientY;
|
|
||||||
|
|
||||||
// Get the initial position of the component
|
|
||||||
const rect = this.getBoundingClientRect();
|
|
||||||
this.initialComponentX = rect.left;
|
|
||||||
this.initialComponentY = rect.top;
|
|
||||||
|
|
||||||
// Add a class to indicate dragging
|
|
||||||
this.classList.add("dragging");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the component's position as the mouse moves
|
|
||||||
handleMouseMove(event) {
|
|
||||||
if (!this.classList.contains("dragging")) return;
|
|
||||||
|
|
||||||
// Calculate the new position of the component
|
|
||||||
const newX = this.initialComponentX + (event.clientX - this.initialMouseX);
|
|
||||||
const newY = this.initialComponentY + (event.clientY - this.initialMouseY);
|
|
||||||
|
|
||||||
// Update the component's position
|
|
||||||
this.style.left = `${newX}px`;
|
|
||||||
this.style.top = `${newY}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop dragging when the mouse is released
|
|
||||||
handleMouseUp() {
|
|
||||||
// Check if the component is being dragged
|
|
||||||
if (!this.classList.contains("dragging")) {
|
|
||||||
return; // Do nothing if not dragging
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the dragging class
|
|
||||||
this.classList.remove("dragging");
|
|
||||||
|
|
||||||
// Get the current position of the component
|
|
||||||
const rect = this.getBoundingClientRect();
|
|
||||||
const newX = rect.left;
|
|
||||||
const newY = rect.top;
|
|
||||||
|
|
||||||
// Dispatch an event to notify the parent about the updated position
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent("position-change", {
|
|
||||||
detail: { lightId: this.lightId, x: newX, y: newY },
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a property to hold the lightId
|
|
||||||
set lightId(id) {
|
|
||||||
this._lightId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
get lightId() {
|
|
||||||
return this._lightId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the custom element
|
|
||||||
customElements.define("light-component", LightComponent);
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// light-components.js
|
|
||||||
import { LightComponent } from "./light-component.js";
|
|
||||||
import { getWebSocket } from "./websocket.js";
|
|
||||||
|
|
||||||
// Map to store backend IDs and their corresponding components
|
|
||||||
const componentMap = new Map();
|
|
||||||
|
|
||||||
// Function to create and configure a light component
|
|
||||||
function createLightComponent(data, key, appContainer) {
|
|
||||||
const lightComponent = document.createElement("light-component");
|
|
||||||
lightComponent.style.left = `${data.x}px`; // Set the x position
|
|
||||||
lightComponent.style.top = `${data.y}px`; // Set the y position
|
|
||||||
lightComponent.style.backgroundColor = data.settings?.color || "#4caf50"; // Set the background color
|
|
||||||
lightComponent.textContent = data.name || "Light Me Up!"; // Set the text content
|
|
||||||
|
|
||||||
// Set the lightId property
|
|
||||||
lightComponent.lightId = key; // Use the backend ID as the lightId
|
|
||||||
|
|
||||||
// Store the component in the map
|
|
||||||
componentMap.set(key, lightComponent);
|
|
||||||
|
|
||||||
// Append the light component to the container
|
|
||||||
appContainer.appendChild(lightComponent);
|
|
||||||
|
|
||||||
// Handle position change
|
|
||||||
lightComponent.addEventListener("position-change", (event) => {
|
|
||||||
const { lightId, x, y } = event.detail;
|
|
||||||
updatePositionOnServer(lightId, x, y);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle color change
|
|
||||||
lightComponent.addEventListener("color-change", (event) => {
|
|
||||||
const { lightId, color } = event.detail;
|
|
||||||
sendColorToServer(lightId, color);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Example: Add a click event listener to the light-component
|
|
||||||
lightComponent.addEventListener("click", () => {
|
|
||||||
console.log(`Light component clicked! ID: ${lightComponent.lightId}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to create light components from the fetched data
|
|
||||||
export function createLightComponents(appContainer, lightData) {
|
|
||||||
for (const key in lightData) {
|
|
||||||
if (lightData.hasOwnProperty(key)) {
|
|
||||||
const light = lightData[key];
|
|
||||||
createLightComponent(light, key, appContainer); // Pass the backend ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to send the updated position to the server via a PATCH request
|
|
||||||
async function updatePositionOnServer(componentId, x, y) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/light/${componentId}`, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ x, y }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Updated position for component ${componentId}: x=${x}, y=${y}`,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating position on server:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to send the selected color to the server via WebSocket
|
|
||||||
function sendColorToServer(componentId, color) {
|
|
||||||
const websocket = getWebSocket();
|
|
||||||
const message = JSON.stringify({
|
|
||||||
componentId,
|
|
||||||
color,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (websocket.readyState === WebSocket.OPEN) {
|
|
||||||
websocket.send(message);
|
|
||||||
console.log("Sent color to server:", message);
|
|
||||||
} else {
|
|
||||||
console.warn("WebSocket is not open. Unable to send color.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,81 @@
|
|||||||
// main.js
|
import "./rgb-slider.js";
|
||||||
import { createLightComponents } from "./light-components.js";
|
|
||||||
import { getWebSocket } from "./websocket.js";
|
|
||||||
|
|
||||||
// Wait for the DOM to be fully loaded
|
const ws = new WebSocket("ws://localhost:8000/ws");
|
||||||
document.addEventListener("DOMContentLoaded", async () => {
|
|
||||||
// Select the container where the light-components will be added
|
|
||||||
const appContainer = document.getElementById("app");
|
|
||||||
|
|
||||||
// Fetch the JSON data from the /light endpoint
|
ws.onopen = () => {
|
||||||
try {
|
console.log("WebSocket connection established");
|
||||||
const response = await fetch("/light");
|
};
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
||||||
}
|
|
||||||
const lightData = await response.json();
|
|
||||||
|
|
||||||
// Create and configure light components
|
ws.onclose = () => {
|
||||||
createLightComponents(appContainer, lightData);
|
console.log("WebSocket connection closed");
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize WebSocket connection
|
ws.onerror = (error) => {
|
||||||
const websocket = getWebSocket();
|
console.error("WebSocket error:", error);
|
||||||
websocket.addEventListener("open", () => {
|
};
|
||||||
console.log("WebSocket connection established.");
|
|
||||||
});
|
// Number of sliders (tabs) you want to create
|
||||||
websocket.addEventListener("message", (event) => {
|
const numTabs = 3;
|
||||||
console.log("Message from server:", event.data);
|
|
||||||
});
|
// Select the container for tabs and content
|
||||||
} catch (error) {
|
const tabsContainer = document.querySelector(".tabs");
|
||||||
console.error("Error fetching light data:", error);
|
const tabContentContainer = document.querySelector(".tab-content");
|
||||||
|
|
||||||
|
// Create tabs dynamically
|
||||||
|
for (let i = 1; i <= numTabs; i++) {
|
||||||
|
// Create the tab button
|
||||||
|
const tabButton = document.createElement("button");
|
||||||
|
tabButton.classList.add("tab");
|
||||||
|
tabButton.id = `tab${i}`;
|
||||||
|
tabButton.textContent = `Tab ${i}`;
|
||||||
|
|
||||||
|
// Add the tab button to the container
|
||||||
|
tabsContainer.appendChild(tabButton);
|
||||||
|
|
||||||
|
// Create the corresponding tab content (RGB slider)
|
||||||
|
const tabContent = document.createElement("div");
|
||||||
|
tabContent.classList.add("tab-pane");
|
||||||
|
tabContent.id = `content${i}`;
|
||||||
|
const slider = document.createElement("rgb-slider");
|
||||||
|
slider.id = i;
|
||||||
|
tabContent.appendChild(slider);
|
||||||
|
|
||||||
|
// Add the tab content to the container
|
||||||
|
tabContentContainer.appendChild(tabContent);
|
||||||
|
|
||||||
|
// Listen for color change on each RGB slider
|
||||||
|
slider.addEventListener("color-change", (e) => {
|
||||||
|
const { r, g, b } = e.detail;
|
||||||
|
console.log(`Color changed in tab ${i}:`, e.detail);
|
||||||
|
// Send RGB data to WebSocket server
|
||||||
|
if (ws.readyState === WebSocket.OPEN) {
|
||||||
|
const colorData = { r, g, b };
|
||||||
|
ws.send(JSON.stringify(colorData));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to switch tabs
|
||||||
|
function switchTab(tabId) {
|
||||||
|
const tabs = document.querySelectorAll(".tab");
|
||||||
|
const tabContents = document.querySelectorAll(".tab-pane");
|
||||||
|
|
||||||
|
tabs.forEach((tab) => tab.classList.remove("active"));
|
||||||
|
tabContents.forEach((content) => content.classList.remove("active"));
|
||||||
|
|
||||||
|
// Activate the clicked tab and corresponding content
|
||||||
|
document.getElementById(tabId).classList.add("active");
|
||||||
|
document
|
||||||
|
.getElementById("content" + tabId.replace("tab", ""))
|
||||||
|
.classList.add("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listeners to tabs
|
||||||
|
tabsContainer.addEventListener("click", (e) => {
|
||||||
|
if (e.target.classList.contains("tab")) {
|
||||||
|
switchTab(e.target.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initially set the first tab as active
|
||||||
|
switchTab("tab1");
|
||||||
|
|||||||
@@ -1,20 +1,37 @@
|
|||||||
/* Default styles for the light component */
|
/* General tab styles */
|
||||||
light-component {
|
.tabs {
|
||||||
display: block;
|
display: flex;
|
||||||
width: 100px;
|
justify-content: center;
|
||||||
height: 100px;
|
margin-bottom: 20px;
|
||||||
background-color: #4caf50;
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 100px;
|
|
||||||
cursor: grab;
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styles when the component is being dragged */
|
.tab {
|
||||||
light-component:active {
|
padding: 10px 20px;
|
||||||
cursor: grabbing;
|
margin: 0 10px;
|
||||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
|
cursor: pointer;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active {
|
||||||
|
background-color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane.active {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
// websocket.js
|
|
||||||
let websocket = null;
|
|
||||||
|
|
||||||
export function getWebSocket() {
|
|
||||||
if (!websocket) {
|
|
||||||
// Replace 'ws://your-server-url' with your WebSocket server URL
|
|
||||||
websocket = new WebSocket(`ws://${window.location.host}/ws`);
|
|
||||||
|
|
||||||
// Handle WebSocket connection open
|
|
||||||
websocket.onopen = () => {
|
|
||||||
console.log("WebSocket connection established");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle WebSocket connection close
|
|
||||||
websocket.onclose = () => {
|
|
||||||
console.log("WebSocket connection closed");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle WebSocket errors
|
|
||||||
websocket.onerror = (error) => {
|
|
||||||
console.error("WebSocket error:", error);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return websocket;
|
|
||||||
}
|
|
||||||
@@ -2,15 +2,13 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<title>RGB Slider Tabs</title>
|
||||||
<title>Light Component</title>
|
<link rel="stylesheet" href="styles.css" />
|
||||||
<!-- Link to the external CSS file -->
|
|
||||||
<link rel="stylesheet" href="static/styles.css" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- The light-component will be added dynamically by main.js -->
|
<div class="tabs"></div>
|
||||||
<div id="app"></div>
|
<div class="tab-content"></div>
|
||||||
<!-- Import the JavaScript files -->
|
|
||||||
<script type="module" src="static/main.js"></script>
|
<script type="module" src="main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
113
src/web.py
113
src/web.py
@@ -1,113 +0,0 @@
|
|||||||
from microdot import Microdot, send_file, Response
|
|
||||||
from microdot.utemplate import Template
|
|
||||||
from microdot.websocket import with_websocket
|
|
||||||
import json
|
|
||||||
|
|
||||||
def web(settings):
|
|
||||||
app = Microdot()
|
|
||||||
Response.default_content_type = 'text/html'
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
async def index_handler(request):
|
|
||||||
return Template('/index.html').render(settings=settings)
|
|
||||||
|
|
||||||
@app.route("/static/<path:path>")
|
|
||||||
def static_handler(request, path):
|
|
||||||
if '..' in path:
|
|
||||||
# Directory traversal is not allowed
|
|
||||||
return 'Not found', 404
|
|
||||||
return send_file('static/' + path)
|
|
||||||
|
|
||||||
@app.route("/ws")
|
|
||||||
@with_websocket
|
|
||||||
async def ws(request, ws):
|
|
||||||
# Register the client's WebSocket connection
|
|
||||||
print("WebSocket connection established")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
data = await ws.receive()
|
|
||||||
if data:
|
|
||||||
try:
|
|
||||||
# Parse the JSON message from the client
|
|
||||||
message = json.loads(data)
|
|
||||||
light = message.get("light")
|
|
||||||
if message["light"] in settings.get("lights"):
|
|
||||||
settings["lights"][light].update(message["settings"])
|
|
||||||
if message["save"]:
|
|
||||||
settings.save()
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
print("Invalid JSON received")
|
|
||||||
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
print("WebSocket connection closed")
|
|
||||||
|
|
||||||
@app.get("/light")
|
|
||||||
async def get_lights(request):
|
|
||||||
return json.dumps(settings)
|
|
||||||
|
|
||||||
@app.get("/light/<light>")
|
|
||||||
async def get_light(request, light):
|
|
||||||
light_data = settings.get(light, None)
|
|
||||||
if light_data:
|
|
||||||
return json.dumps(light_data), 200
|
|
||||||
else:
|
|
||||||
return json.dumps({"error": "Light not found"}), 404
|
|
||||||
|
|
||||||
@app.post("/light/<light>")
|
|
||||||
async def add_light(request, light):
|
|
||||||
try:
|
|
||||||
# Parse the JSON request body
|
|
||||||
data = request.json
|
|
||||||
# Check if the light already exists
|
|
||||||
if light in settings:
|
|
||||||
return json.dumps({"error": "Light already exists"}), 409
|
|
||||||
|
|
||||||
# Add the new light to the settings
|
|
||||||
settings[light] = data
|
|
||||||
print(settings)
|
|
||||||
settings.save()
|
|
||||||
return json.dumps(
|
|
||||||
{"message": "Light added successfully",
|
|
||||||
"light": light}
|
|
||||||
), 200
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Exception: {e}")
|
|
||||||
return json.dumps({"error": "Invalid JSON request"}), 400
|
|
||||||
|
|
||||||
@app.patch("/light/<light>")
|
|
||||||
async def update_light(request, light):
|
|
||||||
try:
|
|
||||||
# Parse the JSON request body
|
|
||||||
data = request.json
|
|
||||||
print(light, data)
|
|
||||||
# Check if the light exists
|
|
||||||
if light not in settings:
|
|
||||||
return json.dumps({"error": "Light not found"}), 404
|
|
||||||
|
|
||||||
# Update the existing light with the provided data
|
|
||||||
settings[light].update(data)
|
|
||||||
settings.save() # Uncomment if using persistent storage
|
|
||||||
return json.dumps(
|
|
||||||
{"message": "Light updated successfully", "light": settings[light]}
|
|
||||||
), 200
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
return json.dumps({"error": "Invalid JSON request"}), 400
|
|
||||||
|
|
||||||
@app.delete("/light/<light>")
|
|
||||||
async def del_light(request, light):
|
|
||||||
if light in settings:
|
|
||||||
# Remove the light from the settings
|
|
||||||
del settings[light]
|
|
||||||
settings.save()
|
|
||||||
return json.dumps({"message": "Light deleted successfully"})
|
|
||||||
else:
|
|
||||||
return json.dumps({"error": "Light not found"}), 404
|
|
||||||
|
|
||||||
return app
|
|
||||||
|
|
||||||
# Example usage
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app = web(settings)
|
|
||||||
app.run()
|
|
||||||
38
src/wifi.py
38
src/wifi.py
@@ -1,38 +0,0 @@
|
|||||||
import network
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
def connect(ssid, password, ip, gateway):
|
|
||||||
if ssid is None or password is None:
|
|
||||||
print("Missing ssid or password")
|
|
||||||
return None
|
|
||||||
try:
|
|
||||||
sta_if = network.WLAN(network.STA_IF)
|
|
||||||
if ip is not None and gateway is not None:
|
|
||||||
sta_if.ifconfig((ip, '255.255.255.0', gateway, '1.1.1.1'))
|
|
||||||
if not sta_if.isconnected():
|
|
||||||
print('connecting to network...')
|
|
||||||
sta_if.active(True)
|
|
||||||
sta_if.connect(ssid, password)
|
|
||||||
sleep(0.1)
|
|
||||||
if sta_if.isconnected():
|
|
||||||
return sta_if.ifconfig()
|
|
||||||
return None
|
|
||||||
return sta_if.ifconfig()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Failed to connect to wifi {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def ap(ssid, password):
|
|
||||||
ap_if = network.WLAN(network.AP_IF)
|
|
||||||
ap_mac = ap_if.config('mac')
|
|
||||||
print(ssid)
|
|
||||||
ap_if.active(True)
|
|
||||||
ap_if.config(essid=ssid, password=password)
|
|
||||||
ap_if.active(False)
|
|
||||||
ap_if.active(True)
|
|
||||||
print(ap_if.ifconfig())
|
|
||||||
|
|
||||||
def get_mac():
|
|
||||||
ap_if = network.WLAN(network.AP_IF)
|
|
||||||
return ap_if.config('mac')
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>RGB Slider Tabs</title>
|
|
||||||
<link rel="stylesheet" href="styles.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="tabs"></div>
|
|
||||||
<div class="tab-content"></div>
|
|
||||||
|
|
||||||
<script type="module" src="main.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
import "./rgb-slider.js";
|
|
||||||
|
|
||||||
const ws = new WebSocket("ws://localhost:8000/ws");
|
|
||||||
|
|
||||||
ws.onopen = () => {
|
|
||||||
console.log("WebSocket connection established");
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
console.log("WebSocket connection closed");
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onerror = (error) => {
|
|
||||||
console.error("WebSocket error:", error);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Number of sliders (tabs) you want to create
|
|
||||||
const numTabs = 3;
|
|
||||||
|
|
||||||
// Select the container for tabs and content
|
|
||||||
const tabsContainer = document.querySelector(".tabs");
|
|
||||||
const tabContentContainer = document.querySelector(".tab-content");
|
|
||||||
|
|
||||||
// Create tabs dynamically
|
|
||||||
for (let i = 1; i <= numTabs; i++) {
|
|
||||||
// Create the tab button
|
|
||||||
const tabButton = document.createElement("button");
|
|
||||||
tabButton.classList.add("tab");
|
|
||||||
tabButton.id = `tab${i}`;
|
|
||||||
tabButton.textContent = `Tab ${i}`;
|
|
||||||
|
|
||||||
// Add the tab button to the container
|
|
||||||
tabsContainer.appendChild(tabButton);
|
|
||||||
|
|
||||||
// Create the corresponding tab content (RGB slider)
|
|
||||||
const tabContent = document.createElement("div");
|
|
||||||
tabContent.classList.add("tab-pane");
|
|
||||||
tabContent.id = `content${i}`;
|
|
||||||
const slider = document.createElement("rgb-slider");
|
|
||||||
slider.id = i;
|
|
||||||
tabContent.appendChild(slider);
|
|
||||||
|
|
||||||
// Add the tab content to the container
|
|
||||||
tabContentContainer.appendChild(tabContent);
|
|
||||||
|
|
||||||
// Listen for color change on each RGB slider
|
|
||||||
slider.addEventListener("color-change", (e) => {
|
|
||||||
const { r, g, b } = e.detail;
|
|
||||||
console.log(`Color changed in tab ${i}:`, e.detail);
|
|
||||||
// Send RGB data to WebSocket server
|
|
||||||
if (ws.readyState === WebSocket.OPEN) {
|
|
||||||
const colorData = { r, g, b };
|
|
||||||
ws.send(JSON.stringify(colorData));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to switch tabs
|
|
||||||
function switchTab(tabId) {
|
|
||||||
const tabs = document.querySelectorAll(".tab");
|
|
||||||
const tabContents = document.querySelectorAll(".tab-pane");
|
|
||||||
|
|
||||||
tabs.forEach((tab) => tab.classList.remove("active"));
|
|
||||||
tabContents.forEach((content) => content.classList.remove("active"));
|
|
||||||
|
|
||||||
// Activate the clicked tab and corresponding content
|
|
||||||
document.getElementById(tabId).classList.add("active");
|
|
||||||
document
|
|
||||||
.getElementById("content" + tabId.replace("tab", ""))
|
|
||||||
.classList.add("active");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add event listeners to tabs
|
|
||||||
tabsContainer.addEventListener("click", (e) => {
|
|
||||||
if (e.target.classList.contains("tab")) {
|
|
||||||
switchTab(e.target.id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initially set the first tab as active
|
|
||||||
switchTab("tab1");
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
// rgb-slider.js
|
|
||||||
|
|
||||||
export class RGBSlider extends HTMLElement {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
const shadow = this.attachShadow({ mode: "open" });
|
|
||||||
|
|
||||||
shadow.innerHTML = `
|
|
||||||
<style>
|
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
padding: 1em;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 50%;
|
|
||||||
|
|
||||||
font-family: sans-serif;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview {
|
|
||||||
width: 50%;
|
|
||||||
height: 60px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #000;
|
|
||||||
background-color: rgb(0, 0, 0);
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sliders {
|
|
||||||
display: flex;
|
|
||||||
gap: 50px;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-group input[type="range"] {
|
|
||||||
writing-mode: vertical-lr;
|
|
||||||
direction: rtl;
|
|
||||||
|
|
||||||
width: 10px;
|
|
||||||
height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-group label {
|
|
||||||
margin-top: 8px;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rgb-inputs {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rgb-inputs input {
|
|
||||||
width: 6ch;
|
|
||||||
padding: 2px;
|
|
||||||
font-family: monospace;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rgb-inputs label {
|
|
||||||
font-size: 0.8em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rgb-input-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile styles */
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.preview {
|
|
||||||
height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-group input[type="range"] {
|
|
||||||
height: 180px;
|
|
||||||
width: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rgb-inputs input {
|
|
||||||
font-size: 1em;
|
|
||||||
padding: 4px;
|
|
||||||
width: 7ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider-group label,
|
|
||||||
.rgb-inputs label {
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 1.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<div class="preview" id="preview"></div>
|
|
||||||
|
|
||||||
<div class="sliders">
|
|
||||||
<div class="slider-group">
|
|
||||||
<input type="range" min="0" max="255" value="0" id="r">
|
|
||||||
<label>R</label>
|
|
||||||
</div>
|
|
||||||
<div class="slider-group">
|
|
||||||
<input type="range" min="0" max="255" value="0" id="g">
|
|
||||||
<label>G</label>
|
|
||||||
</div>
|
|
||||||
<div class="slider-group">
|
|
||||||
<input type="range" min="0" max="255" value="0" id="b">
|
|
||||||
<label>B</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rgb-inputs">
|
|
||||||
<div class="rgb-input-group">
|
|
||||||
<label for="rInput">R</label>
|
|
||||||
<input type="number" min="0" max="255" id="rInput" value="0">
|
|
||||||
</div>
|
|
||||||
<div class="rgb-input-group">
|
|
||||||
<label for="gInput">G</label>
|
|
||||||
<input type="number" min="0" max="255" id="gInput" value="0">
|
|
||||||
</div>
|
|
||||||
<div class="rgb-input-group">
|
|
||||||
<label for="bInput">B</label>
|
|
||||||
<input type="number" min="0" max="255" id="bInput" value="0">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const get = (id) => shadow.querySelector(id);
|
|
||||||
this.r = get("#r");
|
|
||||||
this.g = get("#g");
|
|
||||||
this.b = get("#b");
|
|
||||||
this.rInput = get("#rInput");
|
|
||||||
this.gInput = get("#gInput");
|
|
||||||
this.bInput = get("#bInput");
|
|
||||||
this.preview = get("#preview");
|
|
||||||
|
|
||||||
const updateColor = (r, g, b) => {
|
|
||||||
this.preview.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
|
|
||||||
this.rInput.value = r;
|
|
||||||
this.gInput.value = g;
|
|
||||||
this.bInput.value = b;
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent("color-change", {
|
|
||||||
detail: { r, g, b },
|
|
||||||
bubbles: true,
|
|
||||||
composed: true,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const syncFromSliders = () => {
|
|
||||||
const r = +this.r.value;
|
|
||||||
const g = +this.g.value;
|
|
||||||
const b = +this.b.value;
|
|
||||||
updateColor(r, g, b);
|
|
||||||
};
|
|
||||||
|
|
||||||
const syncFromInputs = () => {
|
|
||||||
const r = Math.min(255, Math.max(0, +this.rInput.value));
|
|
||||||
const g = Math.min(255, Math.max(0, +this.gInput.value));
|
|
||||||
const b = Math.min(255, Math.max(0, +this.bInput.value));
|
|
||||||
this.r.value = r;
|
|
||||||
this.g.value = g;
|
|
||||||
this.b.value = b;
|
|
||||||
updateColor(r, g, b);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.r.addEventListener("input", syncFromSliders);
|
|
||||||
this.g.addEventListener("input", syncFromSliders);
|
|
||||||
this.b.addEventListener("input", syncFromSliders);
|
|
||||||
|
|
||||||
this.rInput.addEventListener("change", syncFromInputs);
|
|
||||||
this.gInput.addEventListener("change", syncFromInputs);
|
|
||||||
this.bInput.addEventListener("change", syncFromInputs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("rgb-slider", RGBSlider);
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/* General tab styles */
|
|
||||||
.tabs {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
padding: 10px 20px;
|
|
||||||
margin: 0 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab:hover {
|
|
||||||
background-color: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab.active {
|
|
||||||
background-color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-pane {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-pane.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user