Skip to content

Commit

Permalink
Merge pull request #66 from mbaraa/feat/handle-pull-down-to-refresh
Browse files Browse the repository at this point in the history
Feat: Handle Pulldown to Refresh with htmx
  • Loading branch information
mbaraa authored Jun 4, 2024
2 parents 97e02db + 07e2808 commit 1166961
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 15 deletions.
80 changes: 80 additions & 0 deletions app/static/css/refresher.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/* I herby admit that this code is a copy-pasta from https://developer.chrome.com/blog/overscroll-behavior/ */

body.refreshing #main-contents,
body.refreshing header {
filter: blur(1px);
touch-action: none;
}

body.refreshing .refresher {
transform: translate3d(0, 150%, 0) scale(1);
z-index: 1;
visibility: visible;
}

.refresher {
pointer-events: none;
--refresh-width: 55px;
background: var(--secondary-color);
width: var(--refresh-width);
height: var(--refresh-width);
border-radius: 50%;
position: absolute;
left: calc(50% - var(--refresh-width) / 2);
padding: 8px;
box-shadow:
0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12),
0 3px 1px -2px rgba(0, 0, 0, 0.2);
transition: all 300ms cubic-bezier(0, 0, 0.2, 1);
will-change: transform, opacity;
display: inline-flex;
justify-content: space-evenly;
align-items: center;
visibility: hidden;
}

body.refreshing .refresher.shrink {
transform: translate3d(0, 150%, 0) scale(0);
opacity: 0;
}

.refresher.done {
transition: none;
}

.loading-bar {
width: 4px;
height: 18px;
border-radius: 4px;
animation: loading 1s ease-in-out infinite;
}

.loading-bar:nth-child(1) {
background-color: var(--primary-color);
animation-delay: 0;
}
.loading-bar:nth-child(2) {
background-color: var(--primary-color);
animation-delay: 0.09s;
}
.loading-bar:nth-child(3) {
background-color: var(--primary-color);
animation-delay: 0.18s;
}
.loading-bar:nth-child(4) {
background-color: var(--primary-color);
animation-delay: 0.27s;
}

@keyframes loading {
0% {
transform: scale(1);
}
20% {
transform: scale(1, 2.2);
}
40% {
transform: scale(1);
}
}
62 changes: 62 additions & 0 deletions app/static/js/refresher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* I herby admit that this code is a copy-pasta from https://developer.chrome.com/blog/overscroll-behavior/ */

"use strict";

const mainContentsEl = document.getElementById("main-contents");
let _startY = 0;

async function simulateRefreshAction() {
const sleep = (timeout) =>
new Promise((resolve) => setTimeout(resolve, timeout));

const transitionEnd = function (propertyName, node) {
return new Promise((resolve) => {
function callback(e) {
e.stopPropagation();
if (e.propertyName === propertyName) {
node.removeEventListener("transitionend", callback);
resolve(e);
}
}
node.addEventListener("transitionend", callback);
});
};

const refresher = document.querySelector(".refresher");

document.body.classList.add("refreshing");
await sleep(2000);

refresher.classList.add("shrink");
await transitionEnd("transform", refresher);
refresher.classList.add("done");

refresher.classList.remove("shrink");
document.body.classList.remove("refreshing");
await sleep(0); // let new styles settle.
refresher.classList.remove("done");
}

mainContentsEl.addEventListener(
"touchstart",
(e) => {
_startY = e.touches[0].pageY;
},
{ passive: true },
);

mainContentsEl.addEventListener(
"touchmove",
async (e) => {
const y = e.touches[0].pageY;
if (
document.scrollingElement.scrollTop === 0 &&
y > _startY &&
!document.body.classList.contains("refreshing")
) {
await simulateRefreshAction();
await updateMainContent(window.location.pathname);
}
},
{ passive: true },
);
35 changes: 21 additions & 14 deletions app/static/js/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,31 @@ window.addEventListener("load", () => {
updateActiveNavLink();
});

/**
* @param {string} path the requested path to update.
*/
async function updateMainContent(path) {
Utils.showLoading();
await fetch(path + "?no_layout=true")
.then((res) => res.text())
.then((page) => {
mainContentsEl.innerHTML = page;
})
.catch(() => {
window.location.reload();
})
.finally(() => {
Utils.hideLoading();
updateActiveNavLink();
});
}

window.addEventListener("popstate", async (e) => {
const mainContentsEl = document.getElementById("main-contents");
if (!!mainContentsEl && !!e.target.location.pathname) {
e.stopImmediatePropagation();
e.preventDefault();
Utils.showLoading();
await fetch(e.target.location.pathname + "?no_layout=true")
.then((res) => res.text())
.then((page) => {
mainContentsEl.innerHTML = page;
})
.catch(() => {
window.location.reload();
})
.finally(() => {
Utils.hideLoading();
updateActiveNavLink();
});
await updateMainContent(e.target.location.pathname);
return;
}
});
Expand All @@ -58,4 +65,4 @@ document.addEventListener("htmx:afterRequest", function (e) {
}
});

window.Router = { updateActiveNavLink };
window.Router = { updateActiveNavLink, updateMainContent };
10 changes: 9 additions & 1 deletion app/views/layouts/default.templ
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ templ Default(title string, children ...templ.Component) {
type="text/css"
href="/static/css/ubuntu-font.css"
/>
<link defer href="/static/css/refresher.css" rel="stylesheet"/>
<link href="/static/css/tailwind.css" rel="stylesheet"/>
<link href={ fmt.Sprintf("/static/css/themes/%s.css", ctx.Value("theme-name").(string)) } rel="stylesheet"/>
<script src="/static/js/htmx/htmx.min.js"></script>
Expand All @@ -50,7 +51,13 @@ templ Default(title string, children ...templ.Component) {
class={ "min-w-screen", "min-h-screen", "p-0", "m-0", "font-Ubuntu" }
>
@header.Header()
<div id="main-contents" style="display: contents">
<div class="refresher">
<div class="loading-bar"></div>
<div class="loading-bar"></div>
<div class="loading-bar"></div>
<div class="loading-bar"></div>
</div>
<div id="main-contents" class={ "contents" }>
for _, child := range children {
@child
}
Expand All @@ -71,6 +78,7 @@ templ Default(title string, children ...templ.Component) {
//
<script src="/static/js/utils.js"></script>
<script src="/static/js/router.js"></script>
<script src="/static/js/refresher.js"></script>
<script type="module">
function registerServiceWorkers() {
if (!("serviceWorker" in navigator)) {
Expand Down

0 comments on commit 1166961

Please sign in to comment.