diff --git a/src/snek/app.py b/src/snek/app.py index 24af03b..227c23a 100644 --- a/src/snek/app.py +++ b/src/snek/app.py @@ -55,6 +55,7 @@ from snek.view.upload import UploadView from snek.view.user import UserView from snek.view.web import WebView from snek.view.channel import ChannelAttachmentView +from snek.view.settings.containers import ContainersIndexView, ContainersCreateView, ContainersUpdateView, ContainersDeleteView from snek.webdav import WebdavApplication from snek.sgit import GitApplication SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34" @@ -208,6 +209,10 @@ class Application(BaseApplication): self.router.add_view("/settings/repositories/create.html", RepositoriesCreateView) self.router.add_view("/settings/repositories/repository/{name}/update.html", RepositoriesUpdateView) self.router.add_view("/settings/repositories/repository/{name}/delete.html", RepositoriesDeleteView) + self.router.add_view("/settings/containers/index.html", ContainersIndexView) + self.router.add_view("/settings/containers/create.html", ContainersCreateView) + self.router.add_view("/settings/containers/container/{uid}/update.html", ContainersUpdateView) + self.router.add_view("/settings/containers/container/{uid}/delete.html", ContainersDeleteView) self.webdav = WebdavApplication(self) self.git = GitApplication(self) self.add_subapp("/webdav", self.webdav) diff --git a/src/snek/static/chat-input.js b/src/snek/static/chat-input.js index 2d0914e..cec3d57 100644 --- a/src/snek/static/chat-input.js +++ b/src/snek/static/chat-input.js @@ -53,7 +53,8 @@ class ChatInputComponent extends HTMLElement { this.textarea.focus(); } - connectedCallback() { + async connectedCallback() { + this.user = await app.rpc.getUser(null); this.liveType = this.getAttribute("live-type") === "true"; this.liveTypeInterval = parseInt(this.getAttribute("live-type-interval")) || 3; this.channelUid = this.getAttribute("channel"); @@ -193,7 +194,7 @@ class ChatInputComponent extends HTMLElement { if (this.trackSecondsBetweenEvents(this.lastUpdateEvent, new Date()) > 1) { this.lastUpdateEvent = new Date(); if (typeof app !== "undefined" && app.rpc && typeof app.rpc.set_typing === "function") { - app.rpc.set_typing(this.channelUid); + app.rpc.set_typing(this.channelUid,this.user.color); } } } diff --git a/src/snek/static/sandbox.css b/src/snek/static/sandbox.css index 1419fe4..c2ffffb 100644 --- a/src/snek/static/sandbox.css +++ b/src/snek/static/sandbox.css @@ -1,28 +1,42 @@ -/* each star */ - .star { - position: absolute; - width: 2px; - height: 2px; - background: #fff; - border-radius: 50%; - opacity: 0; - /* flicker animation */ - animation: twinkle ease-in-out infinite; - } - @keyframes twinkle { - 0%, 100% { opacity: 0; } - 50% { opacity: 1; } - } +.star { + position: absolute; + width: 2px; + height: 2px; + background: var(--star-color, #fff); + border-radius: 50%; + opacity: 0; + transition: background 0.5s ease; + animation: twinkle ease-in-out infinite; +} - /* optional page content */ - .content { - position: relative; - z-index: 1; - color: #eee; - font-family: sans-serif; - text-align: center; - top: 40%; - transform: translateY(-40%); - } +@keyframes twinkle { + 0%, 100% { opacity: 0; } + 50% { opacity: 1; } +} +@keyframes star-glow-frames { + 0% { + box-shadow: 0 0 5px --star-color; + } + 50% { + box-shadow: 0 0 20px --star-color, 0 0 30px --star-color; + } + 100% { + box-shadow: 0 0 5px --star-color; + } +} + +.star-glow { + animation: star-glow-frames 1s; +} + +.content { + position: relative; + z-index: 1; + color: var(--star-content-color, #eee); + font-family: sans-serif; + text-align: center; + top: 40%; + transform: translateY(-40%); +} diff --git a/src/snek/templates/sandbox.html b/src/snek/templates/sandbox.html index 6fd9b3f..0322b00 100644 --- a/src/snek/templates/sandbox.html +++ b/src/snek/templates/sandbox.html @@ -1,31 +1,56 @@ - <script> - - // number of stars you want - const STAR_COUNT = 200; - const body = document.body; +<script type="module"> +import { app } from "/app.js"; - for (let i = 0; i < STAR_COUNT; i++) { - const star = document.createElement('div'); - star.classList.add('star'); +const STAR_COUNT = 200; +const body = document.body; - // random position within the viewport - star.style.left = Math.random() * 100 + '%'; - star.style.top = Math.random() * 100 + '%'; +function createStar() { + const star = document.createElement('div'); + star.classList.add('star'); + star.style.left = `${Math.random() * 100}%`; + star.style.top = `${Math.random() * 100}%`; + const size = Math.random() * 2 + 1; + star.style.width = `${size}px`; + star.style.height = `${size}px`; + const duration = Math.random() * 3 + 2; + const delay = Math.random() * 5; + star.style.animationDuration = `${duration}s`; + star.style.animationDelay = `${delay}s`; + body.appendChild(star); +} - // random size (optional) - const size = Math.random() * 2 + 1; // between 1px and 3px - star.style.width = size + 'px'; - star.style.height = size + 'px'; +Array.from({ length: STAR_COUNT }, createStar); - // random animation timing for natural flicker - const duration = Math.random() * 3 + 2; // 2s–5s - const delay = Math.random() * 5; // 0s–5s - star.style.animationDuration = duration + 's'; - star.style.animationDelay = delay + 's'; +function lightenColor(hex, percent) { + const num = parseInt(hex.replace("#", ""), 16); + let r = (num >> 16) + Math.round(255 * percent / 100); + let g = ((num >> 8) & 0x00FF) + Math.round(255 * percent / 100); + let b = (num & 0x0000FF) + Math.round(255 * percent / 100); + r = Math.min(255, r); + g = Math.min(255, g); + b = Math.min(255, b); + return `#${(1 << 24 | r << 16 | g << 8 | b).toString(16).slice(1)}`; +} - body.appendChild(star); - } - +const originalColor = document.documentElement.style.getPropertyValue("--star-color").trim(); - </script> +function glowCSSVariable(varName, glowColor, duration = 500) { + const root = document.documentElement; + + //igetComputedStyle(root).getPropertyValue(varName).trim(); + glowColor = lightenColor(glowColor, 20); + root.style.setProperty(varName, glowColor); + setTimeout(() => { + root.style.setProperty(varName, originalColor); + }, duration); +} + +function updateStarColorDelayed(color) { + glowCSSVariable('--star-color', color, 2500); +} + +app.ws.addEventListener("set_typing", (data) => { + updateStarColorDelayed(data.data.color); +}); +</script> diff --git a/src/snek/view/rpc.py b/src/snek/view/rpc.py index de94633..fd48124 100644 --- a/src/snek/view/rpc.py +++ b/src/snek/view/rpc.py @@ -36,9 +36,11 @@ class RPCView(BaseView): async def db_update(self, table_name, record): self._require_login() return await self.services.db.update(self.user_uid, table_name, record) - async def set_typing(self,channel_uid): + async def set_typing(self,channel_uid,color=None): self._require_login() user = await self.services.user.get(self.user_uid) + if not color: + color = user["color"] return await self.services.socket.broadcast(channel_uid, { "channel_uid": "293ecf12-08c9-494b-b423-48ba1a2d12c2", "event": "set_typing", @@ -47,7 +49,8 @@ class RPCView(BaseView): "user_uid": user['uid'], "username": user["username"], "nick": user["nick"], - "channel_uid": channel_uid + "channel_uid": channel_uid, + "color": color } })