lainlives před 3 dny
rodič
revize
1101ae26c7
5 změnil soubory, kde provedl 168 přidání a 48 odebrání
  1. 40 28
      README.md
  2. 16 12
      public/index.html
  3. 49 2
      public/js/canvas.js
  4. 26 2
      public/js/service.js
  5. 37 4
      public/styles.css

+ 40 - 28
README.md

@@ -1,39 +1,51 @@
-<h1>
-  <img src="public/favicon.png"> lainschatthing
-</h1>
+# yapcc Yet another PictoChat clone
 
-Yet Another PictoChat Clone
+A modernized web-based clone inspired by the classic DS PictoChat. This updated version features a beautiful glassmorphism UI, persistent database storage, live long-polling syncing, and a suite of powerful quality-of-life tools built directly into the client.
 
+## 🌟 Key Features
 
-## Features 
-- Landscape and Portrait view
-- Transparent background/floating OBS source mode
-- All the colors
-- Crops unpainted pixels off top/bottom maximizing chat backlog
-- No usercount limits
-- Basic username reservation
-- Admin accounts, specified at server launch, they can remove users
-- Can download the entire current database as a zip
+### Beautiful & Responsive UI
+* **Glassmorphism Design**: Sleek, transparent UI panels over dynamic background gradients.
+* **Light / Dark Mode**: Instantly toggle between a crisp, paper-esque light theme and a sleek dark theme (`☀`/`🌙`).
+* **Responsive Layouts**: Smart layouts that adapt to your device. On mobile/portrait displays, the color palette tucks away into a clean `🎨` dropdown submenu to save space. On desktop, tools sit seamlessly beside your canvas.
+* **Expandable View (`⤢`)**: Bring the drawing surface and tools into a massive, centered fullscreen overlay for serious sketching.
 
+### Advanced Canvas & Drawing Tools
+* **Expandable Canvas (`↕`)**: Need more vertical space to draw? Click the extend button to instantly double or triple your canvas height (capped at 3x) without losing any existing strokes!
+* **Auto-Cropping**: When you hit send, the app intelligently scans your canvas and crops out unused whitespace from the top and bottom to maximize space in the chat log. 
+* **Custom Color Swatches**: Includes 8 standard colors, a custom hex picker, and a **Randomize (`🎲`)** button that instantly generates a unique theme color for your strokes and chat name.
+* **Anti-Spam Cooldown**: Integrated 60-second cooldown timer displayed directly on the send button to prevent room spamming.
 
-## Missing Features I would like to implement
+### Streaming & Archives
+* **OBS Stream Mode**: Add `?stream=1` to the URL to instantly convert the page into a pure, UI-free, transparent scrolling chat feed—perfect for overlaying on Twitch/OBS streams!
+* **Image Archive Download (`💾`)**: Click the floppy disk icon to instantly generate and download a ZIP file containing every single drawing currently stored in the room's database.
 
-- Canvas size customizations at server creation.
+### Authentication & Moderation
+* **Persistent Sessions**: Usernames are tied to secure session cookies. If a name is used, it is reserved exclusively for that user until 1 hour of inactivity passes.
+* **Admin Accounts**: Launch the server with the `--admin` flag to register an administrator. The frontend will dynamically prompt for a password when attempting to join with an admin name.
+* **Moderation Commands**: Logged-in admins can simply type `/kick [username]` into the chat box and hit send to permanently disconnect a user, wipe their session, and broadcast a global kick notification to the room.
 
+## 🚀 Setup & Usage
 
-## Screenshots
-- Dark theme
- <img src="screenshots/dark.png">
-- Light theme
- <img src="screenshots/light.png">
-- Expandable canvas
- <img src="screenshots/large.png">
-- All the colors
- <img src="screenshots/colors.png">
-- OBS source mode
- <img src="screenshots/floatingobs.png">
- <img src="screenshots/obssource.png">
+### Requirements
+* Python 3.x
+* Flask (`pip install Flask`)
+* Waitress (`pip install waitress`)
 
+### Running the Server
 
+Start the server using standard options:
+```bash
+python main.py --port 2004
+```
 
-[Favicon created by Freepik](https://www.flaticon.com/free-icons/draw)
+**Registering an Administrator:**
+Append the `--admin` flag followed by `Username:Password` to lock a name and grant it moderation abilities. You can specify this multiple times for multiple admins:
+```bash
+python main.py --admin Owner:supersecret
+```
+
+### Accessing the Room
+1. Open your browser and navigate to `http://localhost:2004` (or whatever port you configured).
+2. Enter your desired handle.
+3. Draw, type, and chat! All history is permanently stored in a local SQLite database (`chat.db`) and safely pruned down to the 500 most recent messages automatically.

+ 16 - 12
public/index.html

@@ -25,18 +25,22 @@
                     <img src="resources/icons/medium_icon.png" alt="medium">
                 </button>
                 <div class="toolbar-divider"></div>
-                <div id="color-palette">
-                    <button class="swatch active-swatch" style="background-color: #000000;" data-color="#000000"></button>
-                    <button class="swatch" style="background-color: #ffffff;" data-color="#ffffff"></button>
-                    <button class="swatch" style="background-color: #ff0000;" data-color="#ff0000"></button>
-                    <button class="swatch" style="background-color: #ff8800;" data-color="#ff8800"></button>
-                    <button class="swatch" style="background-color: #ffff00;" data-color="#ffff00"></button>
-                    <button class="swatch" style="background-color: #00ff00;" data-color="#00ff00"></button>
-                    <button class="swatch" style="background-color: #0000ff;" data-color="#0000ff"></button>
-                    <button class="swatch" style="background-color: #8800ff;" data-color="#8800ff"></button>
-                    <div class="swatch-picker-container">
-                        <input type="color" id="user-color-picker" title="Custom color" value="#4f46e5">
-                        <button id="random-color-btn" title="Randomize Color" style="width: 24px; height: 24px; padding: 0; border-radius: 50%; font-size: 14px; margin-left: 5px; background: transparent; border: 1px solid var(--panel-border); cursor: pointer; display: flex; align-items: center; justify-content: center;">🎲</button>
+                <div class="palette-container">
+                    <button id="extend-canvas-btn" title="Extend Canvas Height" style="width: 40px; height: 40px; font-size: 20px;">↕</button>
+                    <button id="palette-toggle" title="Colors" style="width: 40px; height: 40px; font-size: 20px;">🎨</button>
+                    <div id="color-palette">
+                        <button class="swatch active-swatch" style="background-color: #000000;" data-color="#000000"></button>
+                        <button class="swatch" style="background-color: #ffffff;" data-color="#ffffff"></button>
+                        <button class="swatch" style="background-color: #ff0000;" data-color="#ff0000"></button>
+                        <button class="swatch" style="background-color: #ff8800;" data-color="#ff8800"></button>
+                        <button class="swatch" style="background-color: #ffff00;" data-color="#ffff00"></button>
+                        <button class="swatch" style="background-color: #00ff00;" data-color="#00ff00"></button>
+                        <button class="swatch" style="background-color: #0000ff;" data-color="#0000ff"></button>
+                        <button class="swatch" style="background-color: #8800ff;" data-color="#8800ff"></button>
+                        <div class="swatch-picker-container">
+                            <input type="color" id="user-color-picker" title="Custom color" value="#4f46e5">
+                            <button id="random-color-btn" title="Randomize Color" style="width: 24px; height: 24px; padding: 0; border-radius: 50%; font-size: 14px; margin-left: 5px; background: transparent; border: 1px solid var(--panel-border); cursor: pointer; display: flex; align-items: center; justify-content: center;">🎲</button>
+                        </div>
                     </div>
                 </div>
             </div>

+ 49 - 2
public/js/canvas.js

@@ -116,12 +116,49 @@ mediumButton.addEventListener("click", (e) => {
   smallButton.classList.remove("active-tool");
 });
 
-clearButton.addEventListener("click", (e) => {
-  e.preventDefault();
+window.resetCanvas = () => {
+  const BASE_HEIGHT = 312;
+  window.currentHeightMultiplier = 1;
+  canvas.height = BASE_HEIGHT;
+  canvas.parentElement.style.aspectRatio = `856 / 312`;
+  
   ctx.clearRect(0, 0, canvas.width, canvas.height);
+  
+  ctx.lineWidth = strokeSize;
+  ctx.lineCap = "round";
+  ctx.strokeStyle = strokeColor;
+};
+
+clearButton.addEventListener("click", () => {
+  window.resetCanvas();
   window.clearCurrentText();
 });
 
+// Extend Canvas Button
+const extendCanvasBtn = document.getElementById("extend-canvas-btn");
+if (extendCanvasBtn) {
+  extendCanvasBtn.addEventListener("click", (e) => {
+    e.preventDefault();
+    if ((window.currentHeightMultiplier || 1) >= 3) return;
+    const BASE_HEIGHT = 312;
+    window.currentHeightMultiplier = (window.currentHeightMultiplier || 1) + 1;
+    const newHeight = BASE_HEIGHT * window.currentHeightMultiplier;
+    
+    // Create temporary canvas to hold current drawing
+    const tempCanvas = document.createElement('canvas');
+    tempCanvas.width = canvas.width;
+    tempCanvas.height = canvas.height;
+    tempCanvas.getContext('2d').drawImage(canvas, 0, 0);
+    
+    canvas.height = newHeight;
+    canvas.parentElement.style.aspectRatio = `856 / ${newHeight}`;
+    
+    // Restore drawing
+    ctx.clearRect(0, 0, canvas.width, canvas.height);
+    ctx.drawImage(tempCanvas, 0, 0);
+  });
+}
+
 copyButton = document.querySelector("button#copy");
 copyButton.addEventListener("click", (e) => {
   e.preventDefault();
@@ -240,3 +277,13 @@ if (downloadBtn) {
     window.location.href = "/api/download_images";
   });
 }
+
+// Palette Toggle for portrait mode
+const paletteToggleBtn = document.getElementById("palette-toggle");
+const colorPalette = document.getElementById("color-palette");
+if (paletteToggleBtn && colorPalette) {
+  paletteToggleBtn.addEventListener("click", (e) => {
+    e.preventDefault();
+    colorPalette.classList.toggle("show-palette");
+  });
+}

+ 26 - 2
public/js/service.js

@@ -9,7 +9,11 @@ if (isStreamMode) {
 
 sendButton.addEventListener("click", send);
 
+let cooldownTime = 0;
+let cooldownInterval = null;
+
 function send() {
+  if (cooldownTime > 0) return;
   const messageText = window.getCurrentText ? window.getCurrentText() : "";
   let data = canvas.toDataURL("image/png");
 
@@ -82,8 +86,28 @@ function send() {
       }
     });
   // clear canvas after sending
-  canvas.width = canvas.width;
-  window.clearCurrentText();
+  if (window.resetCanvas) {
+    window.resetCanvas();
+  } else {
+    canvas.width = canvas.width;
+    window.clearCurrentText();
+  }
+
+  // start cooldown
+  cooldownTime = 60;
+  sendButton.disabled = true;
+  sendButton.innerHTML = `wait ${cooldownTime}s`;
+  
+  cooldownInterval = setInterval(() => {
+    cooldownTime--;
+    if (cooldownTime <= 0) {
+      clearInterval(cooldownInterval);
+      sendButton.disabled = false;
+      sendButton.innerHTML = "send";
+    } else {
+      sendButton.innerHTML = `wait ${cooldownTime}s`;
+    }
+  }, 1000);
 }
 
 function addToChatLog(msg) {

+ 37 - 4
public/styles.css

@@ -194,13 +194,17 @@ body.fullscreen-mode #chat-log {
   display: flex;
   flex-direction: row;
   justify-content: center;
+  align-items: center;
   gap: 10px;
   background: rgba(0,0,0,0.2);
   padding: 10px;
   border-radius: 12px;
+  flex-shrink: 0;
+  position: relative;
 }
 
 .toolbar-divider {
+  height: 24px;
   width: 1px;
   background: rgba(255, 255, 255, 0.2);
   margin: 0 5px;
@@ -248,19 +252,48 @@ button.active-tool {
   filter: invert(1);
 }
 
+.palette-container {
+  position: relative;
+  display: flex;
+  align-items: center;
+}
+
+#palette-toggle {
+  display: none;
+}
+
 #color-palette {
   display: flex;
   flex-wrap: wrap;
   gap: 5px;
-  max-width: 90px;
   justify-content: center;
   align-items: center;
 }
 
-@media (min-width: 768px) {
+@media (max-aspect-ratio: 1/1) {
+  #palette-toggle {
+    display: flex;
+  }
   #color-palette {
-    max-width: none;
-    flex-direction: row;
+    display: none;
+    position: absolute;
+    top: 110%;
+    left: 50%;
+    transform: translateX(-50%);
+    background: var(--panel-bg);
+    backdrop-filter: var(--glass-blur);
+    padding: 10px;
+    border-radius: 12px;
+    border: 1px solid var(--panel-border);
+    box-shadow: 0 10px 30px rgba(0,0,0,0.5);
+    z-index: 100;
+    width: 140px;
+  }
+  #color-palette.show-palette {
+    display: flex;
+  }
+  body.light-mode #color-palette {
+    background: rgba(255, 255, 255, 0.95);
   }
 }