From 21b7b0a252c5063f1d9774233b8c954d791b329a Mon Sep 17 00:00:00 2001 From: metacryst Date: Sat, 21 Mar 2026 03:10:50 -0500 Subject: [PATCH] Sidebar fully functional --- src/Home/AuthPage/Login.js | 2 +- src/Home/AuthPage/Signup.js | 2 +- src/Home/Home.js | 135 ++++++++++++++++++++---------------- src/Profile/Profile.js | 35 ++++++++-- src/_/code/shared.css | 2 +- src/components/Sidebar.js | 18 +++-- src/components/TopBar.js | 2 +- src/index.js | 14 ++-- src/public/_/code/quill.js | 3 + 9 files changed, 129 insertions(+), 84 deletions(-) diff --git a/src/Home/AuthPage/Login.js b/src/Home/AuthPage/Login.js index e4c69e0..e08b8d3 100644 --- a/src/Home/AuthPage/Login.js +++ b/src/Home/AuthPage/Login.js @@ -130,7 +130,7 @@ class Login extends Shadow { if (res.ok) { const { token } = await res.json(); await Preferences.set({ key: 'auth_token', value: token }); - global.onLogin(); + global.renderHome(); } else { const { error } = await res.json(); this.errorMessage = error; diff --git a/src/Home/AuthPage/Signup.js b/src/Home/AuthPage/Signup.js index f34829c..f9fb3a6 100644 --- a/src/Home/AuthPage/Signup.js +++ b/src/Home/AuthPage/Signup.js @@ -102,7 +102,7 @@ class Signup extends Shadow { if (res.ok) { const { token } = await res.json(); await Preferences.set({ key: 'auth_token', value: token }); - global.onLogin(); + global.renderHome(); } else { const { error } = await res.json(); this.errorMessage = error; diff --git a/src/Home/Home.js b/src/Home/Home.js index 9b99b8d..2cee363 100644 --- a/src/Home/Home.js +++ b/src/Home/Home.js @@ -2,31 +2,45 @@ import "../components/Sidebar.js" import "../components/AppMenu.js" import "../components/AppWindowContainer.js" +css(` + #homeContainer { + -webkit-user-select: none; + } +`) + +/* +Sidebar Functionality Checklist: +- Open on Top left network logo touch (WITH transition) +- Follow finger on swipe from left side of the screen +- Open if finger swipw travels far enough to the right (WITH velocity-based transition) +- Re-close if not opened enough of the way (WITH transition) + +- Close on touch of home contents (WITH transition) +- Follow finger on swipe beginning near or anywhere on right of divider between sidebar and home contents +- Close if finger swipe travels far enough to the left (WITH velocity-based transition) +- Re-open if not closed enough of the way (WITH transition) +*/ + class Home extends Shadow { dragStartX = null sidebarOpen = false + SIDEBAR_FULL_OPEN = (window.outerWidth * 5) / 6 + SIDEBAR_START_THRESHOLD = window.outerWidth / 10 + SIDEBAR_CLOSE_DECISION = (window.outerWidth * 2) / 3 + SIDEBAR_OPEN_DECISION = (window.outerWidth / 3) + + constructor() { + super() + } + render() { ZStack(() => { - Sidebar() + Sidebar(this.SIDEBAR_FULL_OPEN) ZStack(() => { - // img("/_/icons/hamburger.svg", "3em") - // .position("absolute") - // .zIndex(2) - // .left(1.5, em) - // .top(1, em) - // .onTouch(function (start) { - // if(start) { - // this.style.scale = "0.8" - // } else if(start === false) { - // this.style.scale = "" - // $("sidebar-").toggle() - // } - // }) - VStack(() => { AppWindowContainer() @@ -35,106 +49,105 @@ class Home extends Shadow { .height(100, pct) .minHeight(0) }) + .left(0, px) .backgroundColor("var(--main)") .overflowX("hidden") - .height(window.visualViewport.height - 20, px) + .height(window.visualViewport.height, px) .position("fixed") - .top(20, px) .borderLeft("1px solid var(--accent)") .onTouch((start, e) => { - // console.log(e) if(this.sidebarOpen) { - this.sidebarOpenDrag(start, e) + this.sidebarOpenTouch(start, e) } else { - this.sidebarClosedDrag(start, e) + this.sidebarClosedTouch(start, e) } }) .attr({ id: "homeContainer" }) }) + .userSelect("none") } - openSidebar() { + openSidebar(duration = 200) { const home = this.$("#homeContainer"); - const oneThird = window.outerWidth / 3; - home.style.transition = "left .2s"; - home.style.left = `${oneThird * 2}px`; + home.style.transition = `left ${duration}ms`; + home.style.left = `${this.SIDEBAR_FULL_OPEN}px`; this.sidebarOpen = true; + this.dragStartX = null + setTimeout(() => home.style.transition = "", duration); } - closeSidebar() { + closeSidebar(duration = 200) { const home = this.$("#homeContainer"); - home.style.transition = "left .2s"; + home.style.transition = `left ${duration}ms`; home.style.left = "0px"; this.sidebarOpen = false; + this.dragStartX = null + setTimeout(() => home.style.transition = "", duration); } - sidebarOpenDrag(start, e) { - // console.log("sidebaropendrag") + sidebarOpenTouch(start, e) { if(start) { - let fiveSixths = (window.outerWidth * 5) / 6 let amount = e.targetTouches[0].clientX - if(amount < fiveSixths) { - this.$("#homeContainer").dragStartX = e.touches[0].clientX + if(amount > (this.SIDEBAR_FULL_OPEN - this.SIDEBAR_START_THRESHOLD)) { + this.dragStartX = e.touches[0].clientX + this.dragStartTime = Date.now(); // ⬅ track start time document.addEventListener("touchmove", this.moveSidebar) } } else { - if(!this.$("#homeContainer").dragStartX) return; + if(!this.dragStartX) return; - let oneThird = window.outerWidth / 3 let endX = e.changedTouches[0].clientX - if(endX < oneThird) { - this.$("#homeContainer").style.transition = "left .2s" - this.$("#homeContainer").style.left = `0px` - this.sidebarOpen = false + let duration = this.getDuration(this.dragStartX, endX); + if(Math.abs(this.dragStartX - endX) < 5) { // 2 conditions are separated so this one doesn't close instantly + this.closeSidebar() + } else if(endX < this.SIDEBAR_CLOSE_DECISION) { + this.closeSidebar(duration) } else { - this.$("#homeContainer").style.transition = "left .2s" - this.$("#homeContainer").style.left = `${oneThird * 2}px` - this.sidebarOpen = true + this.openSidebar(duration) } - this.$("#homeContainer").style.transition = "" - this.$("#homeContainer").dragStartX = null document.removeEventListener("touchmove", this.moveSidebar) } } - sidebarClosedDrag(start, e) { - // console.log(e) + sidebarClosedTouch(start, e) { if(start) { - let oneTenth = window.outerWidth / 10 let amount = e.targetTouches[0].clientX - if(amount < oneTenth) { - this.$("#homeContainer").dragStartX = e.touches[0].clientX + if(amount < this.SIDEBAR_START_THRESHOLD) { + this.dragStartX = e.touches[0].clientX + this.dragStartTime = Date.now(); document.addEventListener("touchmove", this.moveSidebar) } } else { - if(!this.$("#homeContainer").dragStartX) return; - - let oneThird = window.outerWidth / 3 + if(!this.dragStartX) return; + let endX = e.changedTouches[0].clientX - if(endX > oneThird) { - this.$("#homeContainer").style.transition = "left .2s" - this.$("#homeContainer").style.left = `${oneThird * 2}px` - this.sidebarOpen = true + let duration = this.getDuration(this.dragStartX, endX); + if(endX > this.SIDEBAR_OPEN_DECISION) { + this.openSidebar(duration) } else { - this.sidebarOpen = false - this.$("#homeContainer").style.left = "0px" + this.closeSidebar(duration) } - this.$("#homeContainer").style.transition = "" - this.$("#homeContainer").dragStartX = null document.removeEventListener("touchmove", this.moveSidebar) } } moveSidebar = (e) => { - let twoThirds = window.outerWidth * .66 let amount = e.targetTouches[0].clientX - if(e.targetTouches[0] && amount < twoThirds) { - console.log("dragtivated: ", amount, twoThirds) + if(e.targetTouches[0] && amount < this.SIDEBAR_FULL_OPEN) { this.$("#homeContainer").style.left = `${amount}px` } } + + getDuration(startX, endX) { + const distance = Math.abs(endX - startX); + const elapsed = Date.now() - this.dragStartTime; + const velocity = distance / elapsed; // px per ms + + const duration = Math.round(distance / velocity); // time to cover remaining distance + return Math.min(Math.max(duration, 50), 200); // clamp between 50ms and 200ms + } } register(Home) \ No newline at end of file diff --git a/src/Profile/Profile.js b/src/Profile/Profile.js index 62c0592..d034c0c 100644 --- a/src/Profile/Profile.js +++ b/src/Profile/Profile.js @@ -26,14 +26,39 @@ class Profile extends Shadow { render() { ZStack(() => { - p("< Back") - .color("var(--darkred)") - .fontSize(1.2, em) + div("➩") + .width(3, rem) + .height(3, rem) + .borderRadius(50, pct) + .border("1.5px solid var(--divider)") .position("absolute") - .fontFamily("Arial") + .fontSize(2, em) + .transform("rotate(180deg)") .top(3, rem) .left(2, rem) .zIndex(1001) + .display("flex") + .alignItems("center") + .justifyContent("center") + .transition("scale .2s") + .state("touched", function (touched) { + if(touched) { + this.scale("1.5") + this.color("var(--darkaccent)") + this.backgroundColor("var(--divider)") + } else { + this.scale("") + this.color("var(--divider)") + this.backgroundColor("var(--darkaccent)") + } + }) + .onTouch(function (start) { + if(start) { + this.attr({touched: "true"}) + } else { + this.attr({touched: ""}) + } + }) .onClick((done) => { if(done) $("appwindowcontainer-").closeProfile() @@ -142,7 +167,7 @@ class Profile extends Shadow { .position("fixed") .top(100, vh) .zIndex(5) - .transition("top .3s") + .transition("top .4s ") .pointerEvents("none") } diff --git a/src/_/code/shared.css b/src/_/code/shared.css index 23452cf..ba588ae 100644 --- a/src/_/code/shared.css +++ b/src/_/code/shared.css @@ -55,7 +55,7 @@ --sidebar: #240609; --divider: #523636; --darktext: #62473E; - --headertext: #ffd8bbe2; + --headertext: #ffd8bb; --darkred: #6b2c1d; --searchbackground: #260F0C; --loginButton: var(--darkaccent); diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js index da67df7..7376a02 100644 --- a/src/components/Sidebar.js +++ b/src/components/Sidebar.js @@ -1,6 +1,12 @@ import util from "../util" class Sidebar extends Shadow { + SIDEBAR_WIDTH + + constructor(width) { + super() + this.SIDEBAR_WIDTH = width + } SidebarItem(text) { return p(text) @@ -35,8 +41,9 @@ class Sidebar extends Shadow { .borderRadius(100, pct) .background("var(--darkaccent)") .alignSelf("center") - .onTap(() => { - this.openProfile() + .onClick((done) => { + if(done) + this.openProfile() }) h2(global.profile.first_name + " " + global.profile.last_name) @@ -50,8 +57,9 @@ class Sidebar extends Shadow { .wordBreak("break-word") .width(100, pct) .borderBottom("2px solid var(--divider)") - .onTap(() => { - this.openProfile() + .onClick((done) => { + if(done) + this.openProfile() }) this.SidebarItem("Logout") @@ -63,7 +71,7 @@ class Sidebar extends Shadow { .top(-5, vh) .minWidth(0) .boxSizing("border-box") - .width((window.outerWidth / 3) * 2, px) + .width(this.SIDEBAR_WIDTH, px) .borderLeft("1px solid var(--divider)") .color("var(--text)") .position("fixed") diff --git a/src/components/TopBar.js b/src/components/TopBar.js index 256c105..27699e8 100644 --- a/src/components/TopBar.js +++ b/src/components/TopBar.js @@ -39,7 +39,7 @@ class TopBar extends Shadow { .paddingBottom(1.5, em) .verticalAlign("center") .gap(0.5, em) - .marginTop(3, em) + .marginTop(4, em) .onNavigate(() => { this.$("p").innerText = global.currentApp() }) diff --git a/src/index.js b/src/index.js index c0bd76f..a16e970 100644 --- a/src/index.js +++ b/src/index.js @@ -140,14 +140,8 @@ let Global = class { } } - async onLogin() { - if(!this.profile) { - await this.getProfile() - } - await this.Socket.init() - await this.onNavigate() - Home() - // navigateTo("/") + async renderHome() { + location.reload() } async onLogout() { @@ -169,7 +163,9 @@ let Global = class { } else if(status === 500) { ConnectionError() } else { - this.onLogin() + await this.Socket.init() + await this.onNavigate() + Home() } }) } diff --git a/src/public/_/code/quill.js b/src/public/_/code/quill.js index 5a05602..756916e 100644 --- a/src/public/_/code/quill.js +++ b/src/public/_/code/quill.js @@ -1,6 +1,7 @@ /* Sam Russell Captured Sun + 2.21.26 - Making state() be called on initial definition 2.20.26 - Adding state() 2.19.26 - Adding dynamicText() 2.16.26 - Adding event objects to the onTouch callbacks @@ -704,6 +705,8 @@ HTMLElement.prototype.state = function(attr, cb) { }) .observe(this, { attributes: true, attributeFilter: [attr] }); + const value = this.getAttribute(attr); + cb.call(this, value) return this }