mirror of
https://github.com/glanceapp/glance.git
synced 2025-06-22 02:41:23 +02:00
Refactor, again
Make collapsible components reusable Add ability to collapse video rows in videos-grid style Lose sanity while dealing with all the intricacies
This commit is contained in:
parent
8977114806
commit
b0018c3f06
@ -125,22 +125,22 @@
|
|||||||
padding-top: var(--list-half-gap);
|
padding-top: var(--list-half-gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-collapsible:not(.list-collapsible-expanded) > .list-collapsible-item {
|
.collapsible-container:not(.container-expanded) > .collapsible-item {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-collapsible-item {
|
.collapsible-item {
|
||||||
animation: listItemReveal 0.3s backwards;
|
animation: collapsibleItemReveal .25s backwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes listItemReveal {
|
@keyframes collapsibleItemReveal {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(10px);
|
transform: translateY(10px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-collapsible-label {
|
.expand-toggle-button {
|
||||||
font: inherit;
|
font: inherit;
|
||||||
border: 0;
|
border: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -154,19 +154,20 @@
|
|||||||
background: var(--color-widget-background);
|
background: var(--color-widget-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-collapsible-label-expanded {
|
.expand-toggle-button.container-expanded {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
bottom: 0;
|
/* -1px to hide 1px gap on chrome */
|
||||||
|
bottom: -1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-collapsible-label-icon {
|
.expand-toggle-button-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -.2rem;
|
top: -.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-collapsible-label-icon::before {
|
.expand-toggle-button-icon::before {
|
||||||
content: '';
|
content: '';
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
@ -175,14 +176,25 @@
|
|||||||
transition: transform 0.3s;
|
transition: transform 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-collapsible-label-expanded .list-collapsible-label-icon::before {
|
.expand-toggle-button.container-expanded .expand-toggle-button-icon::before {
|
||||||
transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.widget-content:has(.list-collapsible-label:last-child) {
|
.widget-content:has(.expand-toggle-button:last-child) {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cards-grid.collapsible-container + .expand-toggle-button {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* required to prevent collapsed lazy images from being loaded while the container is being setup */
|
||||||
|
.collapsible-container:not(.ready) img[loading=lazy] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
background-color: hsl(var(--bghs), calc(var(--scheme) (var(--scheme) var(--bgl) + 20%)));
|
background-color: hsl(var(--bghs), calc(var(--scheme) (var(--scheme) var(--bgl) + 20%)));
|
||||||
color: var(--color-text-highlight);
|
color: var(--color-text-highlight);
|
||||||
@ -270,6 +282,7 @@ body {
|
|||||||
gap: var(--widget-gap);
|
gap: var(--widget-gap);
|
||||||
margin: var(--widget-gap) 0;
|
margin: var(--widget-gap) 0;
|
||||||
animation: pageColumnsEntrance .3s cubic-bezier(0.25, 1, 0.5, 1) backwards;
|
animation: pageColumnsEntrance .3s cubic-bezier(0.25, 1, 0.5, 1) backwards;
|
||||||
|
animation-delay: 3ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pageColumnsEntrance {
|
@keyframes pageColumnsEntrance {
|
||||||
@ -1109,8 +1122,10 @@ body {
|
|||||||
box-shadow: 0 calc(var(--spacing) * -1) 0 0 currentColor, 0 var(--spacing) 0 0 currentColor;
|
box-shadow: 0 calc(var(--spacing) * -1) 0 0 currentColor, 0 var(--spacing) 0 0 currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-collapsible-label-expanded {
|
.expand-toggle-button.container-expanded {
|
||||||
bottom: var(--mobile-navigation-height);
|
bottom: var(--mobile-navigation-height);
|
||||||
|
/* hides content that peeks through the rounded borders of the mobile navigation */
|
||||||
|
box-shadow: 0 var(--border-radius) 0 0 var(--color-background);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,74 +178,158 @@ function setupLazyImages() {
|
|||||||
}, 5);
|
}, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupCollapsibleLists() {
|
function attachExpandToggleButton(collapsibleContainer) {
|
||||||
const collapsibleListElements = document.getElementsByClassName("list-collapsible");
|
|
||||||
|
|
||||||
if (collapsibleListElements.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const showMoreText = "Show more";
|
const showMoreText = "Show more";
|
||||||
const showLessText = "Show less";
|
const showLessText = "Show less";
|
||||||
|
|
||||||
const attachExpandToggleButton = (listElement) => {
|
let expanded = false;
|
||||||
let expanded = false;
|
const button = document.createElement("button");
|
||||||
const button = document.createElement("button");
|
const icon = document.createElement("span");
|
||||||
const arrowElement = document.createElement("span");
|
icon.classList.add("expand-toggle-button-icon");
|
||||||
arrowElement.classList.add("list-collapsible-label-icon");
|
const textNode = document.createTextNode(showMoreText);
|
||||||
const textNode = document.createTextNode(showMoreText);
|
button.classList.add("expand-toggle-button");
|
||||||
button.classList.add("list-collapsible-label");
|
button.append(textNode, icon);
|
||||||
button.append(textNode, arrowElement);
|
button.addEventListener("click", () => {
|
||||||
button.addEventListener("click", () => {
|
expanded = !expanded;
|
||||||
expanded = !expanded;
|
|
||||||
|
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
listElement.classList.add("list-collapsible-expanded");
|
collapsibleContainer.classList.add("container-expanded");
|
||||||
button.classList.add("list-collapsible-label-expanded");
|
button.classList.add("container-expanded");
|
||||||
textNode.nodeValue = showLessText;
|
textNode.nodeValue = showLessText;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const topBefore = button.getClientRects()[0].top;
|
||||||
|
|
||||||
|
collapsibleContainer.classList.remove("container-expanded");
|
||||||
|
button.classList.remove("container-expanded");
|
||||||
|
textNode.nodeValue = showMoreText;
|
||||||
|
|
||||||
|
const topAfter = button.getClientRects()[0].top;
|
||||||
|
|
||||||
|
if (topAfter > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
window.scrollBy({
|
||||||
|
top: topAfter - topBefore,
|
||||||
|
behavior: "instant"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
collapsibleContainer.after(button);
|
||||||
|
|
||||||
|
return button;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function setupCollapsibleLists() {
|
||||||
|
const collapsibleLists = document.querySelectorAll(".list.collapsible-container");
|
||||||
|
|
||||||
|
if (collapsibleLists.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < collapsibleLists.length; i++) {
|
||||||
|
const list = collapsibleLists[i];
|
||||||
|
|
||||||
|
if (list.dataset.collapseAfter === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collapseAfter = parseInt(list.dataset.collapseAfter);
|
||||||
|
|
||||||
|
if (collapseAfter == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (list.children.length <= collapseAfter) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
attachExpandToggleButton(list);
|
||||||
|
|
||||||
|
for (let c = collapseAfter; c < list.children.length; c++) {
|
||||||
|
const child = list.children[c];
|
||||||
|
child.classList.add("collapsible-item");
|
||||||
|
child.style.animationDelay = ((c - collapseAfter) * 20).toString() + "ms";
|
||||||
|
}
|
||||||
|
|
||||||
|
list.classList.add("ready");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupCollapsibleGrids() {
|
||||||
|
const collapsibleGridElements = document.querySelectorAll(".cards-grid.collapsible-container");
|
||||||
|
|
||||||
|
if (collapsibleGridElements.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < collapsibleGridElements.length; i++) {
|
||||||
|
const gridElement = collapsibleGridElements[i];
|
||||||
|
|
||||||
|
if (gridElement.dataset.collapseAfterRows === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collapseAfterRows = parseInt(gridElement.dataset.collapseAfterRows);
|
||||||
|
|
||||||
|
if (collapseAfterRows == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCardsPerRow = () => {
|
||||||
|
return parseInt(getComputedStyle(gridElement).getPropertyValue('--cards-per-row'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const button = attachExpandToggleButton(gridElement);
|
||||||
|
|
||||||
|
let cardsPerRow = 2;
|
||||||
|
|
||||||
|
const resolveCollapsibleItems = () => {
|
||||||
|
const hideItemsAfterIndex = cardsPerRow * collapseAfterRows;
|
||||||
|
|
||||||
|
if (hideItemsAfterIndex >= gridElement.children.length) {
|
||||||
|
button.style.display = "none";
|
||||||
|
} else {
|
||||||
|
button.style.removeProperty("display");
|
||||||
|
}
|
||||||
|
|
||||||
|
let row = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < gridElement.children.length; i++) {
|
||||||
|
const child = gridElement.children[i];
|
||||||
|
|
||||||
|
if (i >= hideItemsAfterIndex) {
|
||||||
|
child.classList.add("collapsible-item");
|
||||||
|
child.style.animationDelay = (row * 40).toString() + "ms";
|
||||||
|
|
||||||
|
if (i % cardsPerRow + 1 == cardsPerRow) {
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
child.classList.remove("collapsible-item");
|
||||||
|
child.style.removeProperty("animation-delay");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
cardsPerRow = getCardsPerRow();
|
||||||
|
resolveCollapsibleItems();
|
||||||
|
gridElement.classList.add("ready");
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
const newCardsPerRow = getCardsPerRow();
|
||||||
|
|
||||||
|
if (cardsPerRow == newCardsPerRow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const topBefore = button.getClientRects()[0].top;
|
cardsPerRow = newCardsPerRow;
|
||||||
|
resolveCollapsibleItems();
|
||||||
listElement.classList.remove("list-collapsible-expanded");
|
|
||||||
button.classList.remove("list-collapsible-label-expanded");
|
|
||||||
textNode.nodeValue = showMoreText;
|
|
||||||
|
|
||||||
const topAfter = button.getClientRects()[0].top;
|
|
||||||
|
|
||||||
if (topAfter > 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
window.scrollBy({
|
|
||||||
top: topAfter - topBefore,
|
|
||||||
behavior: "instant"
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
listElement.after(button);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < collapsibleListElements.length; i++) {
|
|
||||||
const listElement = collapsibleListElements[i];
|
|
||||||
|
|
||||||
if (listElement.dataset.collapseAfter === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const collapseAfter = parseInt(listElement.dataset.collapseAfter);
|
|
||||||
|
|
||||||
if (listElement.children.length <= collapseAfter) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
attachExpandToggleButton(listElement);
|
|
||||||
|
|
||||||
for (let c = collapseAfter; c < listElement.children.length; c++) {
|
|
||||||
const child = listElement.children[c];
|
|
||||||
child.classList.add("list-collapsible-item");
|
|
||||||
child.style.animationDelay = ((c - collapseAfter) * 20).toString() + "ms";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,6 +348,7 @@ async function setupPage() {
|
|||||||
setupLazyImages();
|
setupLazyImages();
|
||||||
setupCarousels();
|
setupCarousels();
|
||||||
setupCollapsibleLists();
|
setupCollapsibleLists();
|
||||||
|
setupCollapsibleGrids();
|
||||||
setupDynamicRelativeTime();
|
setupDynamicRelativeTime();
|
||||||
} finally {
|
} finally {
|
||||||
pageElement.classList.add("content-ready");
|
pageElement.classList.add("content-ready");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{{ template "widget-base.html" . }}
|
{{ template "widget-base.html" . }}
|
||||||
|
|
||||||
{{ define "widget-content" }}
|
{{ define "widget-content" }}
|
||||||
<ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
|
<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
|
||||||
{{ range .Posts }}
|
{{ range .Posts }}
|
||||||
<li>
|
<li>
|
||||||
<div class="forum-post-list-item thumbnail-container">
|
<div class="forum-post-list-item thumbnail-container">
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
{{ template "widget-base.html" . }}
|
{{ template "widget-base.html" . }}
|
||||||
|
|
||||||
{{ define "widget-content" }}
|
{{ define "widget-content" }}
|
||||||
<ul class="list list-gap-10 list-collapsible">
|
<ul class="list list-gap-10 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
|
||||||
{{ range $i, $release := .Releases }}
|
{{ range $i, $release := .Releases }}
|
||||||
<li {{ if shouldCollapse $i $.CollapseAfter }}class="list-collapsible-item" style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}>
|
<li>
|
||||||
<a class="size-h4 block text-truncate color-primary-if-not-visited" href="{{ $release.NotesUrl }}" target="_blank" rel="noreferrer">{{ .Name }}</a>
|
<a class="size-h4 block text-truncate color-primary-if-not-visited" href="{{ $release.NotesUrl }}" target="_blank" rel="noreferrer">{{ .Name }}</a>
|
||||||
<ul class="list-horizontal-text">
|
<ul class="list-horizontal-text">
|
||||||
<li {{ dynamicRelativeTimeAttrs $release.TimeReleased }}></li>
|
<li {{ dynamicRelativeTimeAttrs $release.TimeReleased }}></li>
|
||||||
@ -15,7 +15,4 @@
|
|||||||
</li>
|
</li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</ul>
|
</ul>
|
||||||
{{ if gt (len .Releases) $.CollapseAfter }}
|
|
||||||
<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{{ template "widget-base.html" . }}
|
{{ template "widget-base.html" . }}
|
||||||
|
|
||||||
{{ define "widget-content" }}
|
{{ define "widget-content" }}
|
||||||
<ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
|
<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
|
||||||
{{ range .Items }}
|
{{ range .Items }}
|
||||||
<li>
|
<li>
|
||||||
<a class="size-title-dynamic color-primary-if-not-visited" href="{{ .Link }}" target="_blank" rel="noreferrer">{{ .Title }}</a>
|
<a class="size-title-dynamic color-primary-if-not-visited" href="{{ .Link }}" target="_blank" rel="noreferrer">{{ .Title }}</a>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{{ template "widget-base.html" . }}
|
{{ template "widget-base.html" . }}
|
||||||
|
|
||||||
{{ define "widget-content" }}
|
{{ define "widget-content" }}
|
||||||
<ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
|
<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
|
||||||
{{ range .Channels }}
|
{{ range .Channels }}
|
||||||
<li>
|
<li>
|
||||||
<div class="{{ if .IsLive }}twitch-channel-live {{ end }}flex gap-10 items-start thumbnail-container">
|
<div class="{{ if .IsLive }}twitch-channel-live {{ end }}flex gap-10 items-start thumbnail-container">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{{ template "widget-base.html" . }}
|
{{ template "widget-base.html" . }}
|
||||||
|
|
||||||
{{ define "widget-content" }}
|
{{ define "widget-content" }}
|
||||||
<ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
|
<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
|
||||||
{{ range .Categories }}
|
{{ range .Categories }}
|
||||||
<li class="twitch-category thumbnail-container">
|
<li class="twitch-category thumbnail-container">
|
||||||
<div class="flex gap-10 items-center">
|
<div class="flex gap-10 items-center">
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
{{ define "widget-content-classes" }}widget-content-frameless{{ end }}
|
{{ define "widget-content-classes" }}widget-content-frameless{{ end }}
|
||||||
|
|
||||||
{{ define "widget-content" }}
|
{{ define "widget-content" }}
|
||||||
<div class="cards-grid">
|
<div class="cards-grid collapsible-container" data-collapse-after-rows="{{ .CollapseAfterRows }}">
|
||||||
{{ range .Videos }}
|
{{ range .Videos }}
|
||||||
<div class="card widget-content-frame thumbnail-container">
|
<div class="card widget-content-frame thumbnail-container">
|
||||||
{{ template "video-card-contents" . }}
|
{{ template "video-card-contents" . }}
|
||||||
|
@ -10,12 +10,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Videos struct {
|
type Videos struct {
|
||||||
widgetBase `yaml:",inline"`
|
widgetBase `yaml:",inline"`
|
||||||
Videos feed.Videos `yaml:"-"`
|
Videos feed.Videos `yaml:"-"`
|
||||||
VideoUrlTemplate string `yaml:"video-url-template"`
|
VideoUrlTemplate string `yaml:"video-url-template"`
|
||||||
Style string `yaml:"style"`
|
Style string `yaml:"style"`
|
||||||
Channels []string `yaml:"channels"`
|
CollapseAfterRows int `yaml:"collapse-after-rows"`
|
||||||
Limit int `yaml:"limit"`
|
Channels []string `yaml:"channels"`
|
||||||
|
Limit int `yaml:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (widget *Videos) Initialize() error {
|
func (widget *Videos) Initialize() error {
|
||||||
@ -25,6 +26,10 @@ func (widget *Videos) Initialize() error {
|
|||||||
widget.Limit = 25
|
widget.Limit = 25
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if widget.CollapseAfterRows == 0 || widget.CollapseAfterRows < -1 {
|
||||||
|
widget.CollapseAfterRows = 4
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user