Inital slider

This commit is contained in:
jimmy 2025-05-04 18:06:21 +12:00
parent e0a0e083be
commit 29d7a5bcfc
4 changed files with 327 additions and 0 deletions

14
static/index.html Normal file
View File

@ -0,0 +1,14 @@
<!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>

81
static/main.js Normal file
View File

@ -0,0 +1,81 @@
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");

195
static/rgb-slider.js Normal file
View File

@ -0,0 +1,195 @@
// 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);

37
static/styles.css Normal file
View File

@ -0,0 +1,37 @@
/* 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;
}