mirror of
https://github.com/glanceapp/glance.git
synced 2024-11-22 00:13:55 +01:00
Merge branch 'release/v0.5.0' into main
This commit is contained in:
commit
6d9ea27e9f
@ -647,6 +647,7 @@ Example:
|
||||
```yaml
|
||||
- type: weather
|
||||
units: metric
|
||||
hour-format: 12h
|
||||
location: London, United Kingdom
|
||||
```
|
||||
|
||||
@ -671,6 +672,7 @@ Each bar represents a 2 hour interval. The yellow background represents sunrise
|
||||
| ---- | ---- | -------- | ------- |
|
||||
| location | string | yes | |
|
||||
| units | string | no | metric |
|
||||
| hour-format | string | no | 12h |
|
||||
| hide-location | boolean | no | false |
|
||||
| show-area-name | boolean | no | false |
|
||||
|
||||
@ -680,6 +682,9 @@ The name of the city and country to fetch weather information for. Attempting to
|
||||
##### `units`
|
||||
Whether to show the temperature in celsius or fahrenheit, possible values are `metric` or `imperial`.
|
||||
|
||||
#### `hour-format`
|
||||
Whether to show the hours of the day in 12-hour format or 24-hour format. Possible values are `12h` and `24h`.
|
||||
|
||||
##### `hide-location`
|
||||
Optionally don't display the location name on the widget.
|
||||
|
||||
@ -1063,6 +1068,7 @@ Preview:
|
||||
| ---- | ---- | -------- | ------- |
|
||||
| channels | array | yes | |
|
||||
| collapse-after | integer | no | 5 |
|
||||
| sort-by | string | no | viewers |
|
||||
|
||||
##### `channels`
|
||||
A list of channels to display.
|
||||
@ -1070,6 +1076,9 @@ A list of channels to display.
|
||||
##### `collapse-after`
|
||||
How many channels are visible before the "SHOW MORE" button appears. Set to `-1` to never collapse.
|
||||
|
||||
##### `sort-by`
|
||||
Can be used to specify the order in which the channels are displayed. Possible values are `viewers` and `live`.
|
||||
|
||||
### Twitch top games
|
||||
Display a list of games with the most viewers on Twitch.
|
||||
|
||||
|
BIN
internal/assets/static/app-icon.png
Normal file
BIN
internal/assets/static/app-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.8 KiB |
@ -57,6 +57,14 @@
|
||||
font-size: var(--font-size-h4);
|
||||
}
|
||||
|
||||
.page-content, .page.content-ready .page-loading-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page.content-ready > .page-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page-column-full .size-title-dynamic {
|
||||
font-size: var(--font-size-h3);
|
||||
}
|
||||
@ -117,70 +125,71 @@
|
||||
padding-top: var(--list-half-gap);
|
||||
}
|
||||
|
||||
@keyframes listItemReveal {
|
||||
.collapsible-container:not(.container-expanded) > .collapsible-item {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapsible-item {
|
||||
animation: collapsibleItemReveal .25s backwards;
|
||||
}
|
||||
|
||||
@keyframes collapsibleItemReveal {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
}
|
||||
|
||||
.list-collapsible-item {
|
||||
display: none;
|
||||
animation: listItemReveal 0.3s backwards;
|
||||
animation-delay: var(--animation-delay);
|
||||
}
|
||||
|
||||
.list-collapsible-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
.expand-toggle-button {
|
||||
font: inherit;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
color: var(--color-text-base);
|
||||
text-transform: uppercase;
|
||||
font-size: var(--font-size-h4);
|
||||
padding: var(--widget-content-vertical-padding) 0;
|
||||
background: var(--color-widget-background);
|
||||
}
|
||||
|
||||
.list-collapsible-label:has(.list-collapsible-input:checked) {
|
||||
.expand-toggle-button.container-expanded {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
/* -1px to hide 1px gap on chrome */
|
||||
bottom: -1px;
|
||||
}
|
||||
|
||||
.list-collapsible:has(+ .list-collapsible-label > .list-collapsible-input:checked) .list-collapsible-item {
|
||||
display: block;
|
||||
.expand-toggle-button-icon {
|
||||
display: inline-block;
|
||||
margin-left: 1rem;
|
||||
position: relative;
|
||||
top: -.2rem;
|
||||
}
|
||||
|
||||
.list-collapsible-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list-collapsible-label::before, .list-collapsible-label::after {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.list-collapsible-label::before {
|
||||
content: 'SHOW MORE';
|
||||
font-size: var(--font-size-h4);
|
||||
}
|
||||
|
||||
.list-collapsible-label:has(.list-collapsible-input:checked)::before {
|
||||
content: 'SHOW LESS';
|
||||
}
|
||||
|
||||
.list-collapsible-label::after {
|
||||
.expand-toggle-button-icon::before {
|
||||
content: '';
|
||||
font-size: 0.8rem;
|
||||
transform: rotate(90deg);
|
||||
line-height: 1;
|
||||
display: inline-block;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.list-collapsible-label:has(.list-collapsible-input:checked)::after {
|
||||
.expand-toggle-button.container-expanded .expand-toggle-button-icon::before {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.widget-content:has(.list-collapsible-label:last-child) {
|
||||
.widget-content:has(.expand-toggle-button:last-child) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.cards-grid.collapsible-container + .expand-toggle-button {
|
||||
text-align: center;
|
||||
margin-top: 0.5rem;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: hsl(var(--bghs), calc(var(--scheme) (var(--scheme) var(--bgl) + 20%)));
|
||||
color: var(--color-text-highlight);
|
||||
@ -706,7 +715,7 @@ body {
|
||||
flex-direction: column;
|
||||
width: calc(100% / 12);
|
||||
padding-top: 3px;
|
||||
max-width: 3.5rem;
|
||||
max-width: 3rem;
|
||||
}
|
||||
|
||||
.weather-column-value, .weather-columns:hover .weather-column-value {
|
||||
@ -866,8 +875,8 @@ body {
|
||||
|
||||
.thumbnail {
|
||||
filter: grayscale(0.2) contrast(0.9);
|
||||
transition: all 0.2s;
|
||||
opacity: 0.8;
|
||||
transition: filter 0.2s, opacity .2s;
|
||||
}
|
||||
|
||||
.thumbnail-container:hover .thumbnail {
|
||||
@ -996,10 +1005,10 @@ body {
|
||||
|
||||
.page-column {
|
||||
display: none;
|
||||
animation: columnEntrance 0s cubic-bezier(0.25, 1, 0.5, 1) backwards;
|
||||
animation: columnEntrance .0s cubic-bezier(0.25, 1, 0.5, 1) backwards;
|
||||
}
|
||||
|
||||
.animate-element-transition .page-column {
|
||||
.page-columns-transitioned .page-column {
|
||||
animation-duration: .3s;
|
||||
}
|
||||
|
||||
@ -1107,9 +1116,44 @@ body {
|
||||
box-shadow: 0 calc(var(--spacing) * -1) 0 0 currentColor, 0 var(--spacing) 0 0 currentColor;
|
||||
}
|
||||
|
||||
.list-collapsible-label:has(.list-collapsible-input:checked) {
|
||||
.expand-toggle-button.container-expanded {
|
||||
bottom: var(--mobile-navigation-height);
|
||||
}
|
||||
|
||||
.cards-grid + .expand-toggle-button.container-expanded {
|
||||
/* hides content that peeks through the rounded borders of the mobile navigation */
|
||||
box-shadow: 0 var(--border-radius) 0 0 var(--color-background);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1190px) and (display-mode: standalone) {
|
||||
:root {
|
||||
--safe-area-inset-bottom: env(safe-area-inset-bottom, 0);
|
||||
}
|
||||
|
||||
.list-collapsible-label:has(.list-collapsible-input:checked) {
|
||||
bottom: calc(var(--mobile-navigation-height) + var(--safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.mobile-navigation {
|
||||
transform: translateY(calc(100% - var(--mobile-navigation-height) - var(--safe-area-inset-bottom)));
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.mobile-navigation-icons {
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
transition: padding-bottom .3s;
|
||||
}
|
||||
|
||||
.mobile-navigation-icons:has(.mobile-navigation-page-links-input:checked) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (display-mode: standalone) {
|
||||
body {
|
||||
padding-top: env(safe-area-inset-top, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 550px) {
|
||||
@ -1134,7 +1178,7 @@ body {
|
||||
.mobile-reachability-header {
|
||||
display: block;
|
||||
font-size: 3rem;
|
||||
padding: 10dvh 1rem;
|
||||
padding: 10vh 1rem;
|
||||
text-align: center;
|
||||
color: var(--color-text-highlight);
|
||||
animation: pageColumnsEntrance .3s cubic-bezier(0.25, 1, 0.5, 1) backwards;
|
||||
|
@ -21,7 +21,7 @@ function throttledDebounce(callback, maxDebounceTimes, debounceDelay) {
|
||||
};
|
||||
|
||||
|
||||
async function fetchPageContents (pageSlug) {
|
||||
async function fetchPageContent(pageSlug) {
|
||||
// TODO: handle non 200 status codes/time outs
|
||||
// TODO: add retries
|
||||
const response = await fetch(`/api/pages/${pageSlug}/content/`);
|
||||
@ -33,8 +33,13 @@ async function fetchPageContents (pageSlug) {
|
||||
function setupCarousels() {
|
||||
const carouselElements = document.getElementsByClassName("carousel-container");
|
||||
|
||||
if (carouselElements.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < carouselElements.length; i++) {
|
||||
const carousel = carouselElements[i];
|
||||
carousel.classList.add("show-right-cutoff");
|
||||
const itemsContainer = carousel.getElementsByClassName("carousel-items-container")[0];
|
||||
|
||||
const determineSideCutoffs = () => {
|
||||
@ -56,7 +61,7 @@ function setupCarousels() {
|
||||
itemsContainer.addEventListener("scroll", determineSideCutoffsRateLimited);
|
||||
document.addEventListener("resize", determineSideCutoffsRateLimited);
|
||||
|
||||
determineSideCutoffs();
|
||||
afterContentReady(determineSideCutoffs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,6 +112,8 @@ function setupDynamicRelativeTime() {
|
||||
const updateInterval = 60 * 1000;
|
||||
let lastUpdateTime = Date.now();
|
||||
|
||||
updateRelativeTimeForElements(elements);
|
||||
|
||||
const updateElementsAndTimestamp = () => {
|
||||
updateRelativeTimeForElements(elements);
|
||||
lastUpdateTime = Date.now();
|
||||
@ -153,35 +160,211 @@ function setupLazyImages() {
|
||||
image.classList.add("finished-transition");
|
||||
}
|
||||
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const image = images[i];
|
||||
afterContentReady(() => {
|
||||
setTimeout(() => {
|
||||
for (let i = 0; i < images.length; i++) {
|
||||
const image = images[i];
|
||||
|
||||
if (image.complete) {
|
||||
image.classList.add("cached");
|
||||
setTimeout(() => imageFinishedTransition(image), 5);
|
||||
} else {
|
||||
// TODO: also handle error event
|
||||
image.addEventListener("load", () => {
|
||||
image.classList.add("loaded");
|
||||
setTimeout(() => imageFinishedTransition(image), 500);
|
||||
});
|
||||
if (image.complete) {
|
||||
image.classList.add("cached");
|
||||
setTimeout(() => imageFinishedTransition(image), 1);
|
||||
} else {
|
||||
// TODO: also handle error event
|
||||
image.addEventListener("load", () => {
|
||||
image.classList.add("loaded");
|
||||
setTimeout(() => imageFinishedTransition(image), 400);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, 1);
|
||||
});
|
||||
}
|
||||
|
||||
function attachExpandToggleButton(collapsibleContainer) {
|
||||
const showMoreText = "Show more";
|
||||
const showLessText = "Show less";
|
||||
|
||||
let expanded = false;
|
||||
const button = document.createElement("button");
|
||||
const icon = document.createElement("span");
|
||||
icon.classList.add("expand-toggle-button-icon");
|
||||
const textNode = document.createTextNode(showMoreText);
|
||||
button.classList.add("expand-toggle-button");
|
||||
button.append(textNode, icon);
|
||||
button.addEventListener("click", () => {
|
||||
expanded = !expanded;
|
||||
|
||||
if (expanded) {
|
||||
collapsibleContainer.classList.add("container-expanded");
|
||||
button.classList.add("container-expanded");
|
||||
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");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
afterContentReady(() => {
|
||||
cardsPerRow = getCardsPerRow();
|
||||
resolveCollapsibleItems();
|
||||
gridElement.classList.add("ready");
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
const newCardsPerRow = getCardsPerRow();
|
||||
|
||||
if (cardsPerRow == newCardsPerRow) {
|
||||
return;
|
||||
}
|
||||
|
||||
cardsPerRow = newCardsPerRow;
|
||||
resolveCollapsibleItems();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const contentReadyCallbacks = [];
|
||||
|
||||
function afterContentReady(callback) {
|
||||
contentReadyCallbacks.push(callback);
|
||||
}
|
||||
|
||||
async function setupPage() {
|
||||
const pageElement = document.getElementById("page");
|
||||
const pageContents = await fetchPageContents(pageData.slug);
|
||||
const pageContentElement = document.getElementById("page-content");
|
||||
const pageContent = await fetchPageContent(pageData.slug);
|
||||
|
||||
pageElement.innerHTML = pageContents;
|
||||
pageContentElement.innerHTML = pageContent;
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.classList.add("animate-element-transition");
|
||||
}, 150);
|
||||
try {
|
||||
setupCarousels();
|
||||
setupCollapsibleLists();
|
||||
setupCollapsibleGrids();
|
||||
setupDynamicRelativeTime();
|
||||
setupLazyImages();
|
||||
} finally {
|
||||
pageElement.classList.add("content-ready");
|
||||
|
||||
setTimeout(setupLazyImages, 5);
|
||||
setupCarousels();
|
||||
setupDynamicRelativeTime();
|
||||
for (let i = 0; i < contentReadyCallbacks.length; i++) {
|
||||
contentReadyCallbacks[i]();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.classList.add("page-columns-transitioned");
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
|
13
internal/assets/static/manifest.json
Normal file
13
internal/assets/static/manifest.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "Glance",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/static/app-icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
]
|
||||
}
|
@ -5,7 +5,15 @@
|
||||
<title>{{ block "document-title" . }}{{ end }}</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="color-scheme" content="dark">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
<meta name="apple-mobile-web-app-title" content="Glance">
|
||||
<meta name="theme-color" content="{{ if ne nil .App.Config.Theme.BackgroundColor }}{{ .App.Config.Theme.BackgroundColor }}{{ else }}hsl(240, 8%, 9%){{ end }}">
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="/static/app-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="50x50" href="/static/favicon.png">
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
<link rel="icon" type="image/png" href="/static/favicon.png" />
|
||||
<link rel="stylesheet" href="/static/main.css?v={{ .App.Config.Server.StartedAt.Unix }}">
|
||||
<script async src="/static/main.js?v={{ .App.Config.Server.StartedAt.Unix }}"></script>
|
||||
|
@ -1,14 +1,14 @@
|
||||
{{ template "widget-base.html" . }}
|
||||
|
||||
{{ define "widget-content" }}
|
||||
<ul class="list list-gap-14 list-collapsible">
|
||||
{{ range $i, $post := .Posts }}
|
||||
<li {{ if shouldCollapse $i $.CollapseAfter }}class="list-collapsible-item" style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}>
|
||||
<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
|
||||
{{ range .Posts }}
|
||||
<li>
|
||||
<div class="forum-post-list-item thumbnail-container">
|
||||
{{ if $.ShowThumbnails }}
|
||||
{{ if ne $post.ThumbnailUrl "" }}
|
||||
<img class="forum-post-list-thumbnail thumbnail" src="{{ $post.ThumbnailUrl }}" alt="" loading="lazy">
|
||||
{{ else if $post.HasTargetUrl }}
|
||||
{{ if ne .ThumbnailUrl "" }}
|
||||
<img class="forum-post-list-thumbnail thumbnail" src="{{ .ThumbnailUrl }}" alt="" loading="lazy">
|
||||
{{ else if .HasTargetUrl }}
|
||||
<svg class="forum-post-list-thumbnail hide-on-mobile" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-9 -8 40 40" stroke-width="1.5" stroke="var(--color-text-subdue)">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
|
||||
</svg>
|
||||
@ -18,14 +18,15 @@
|
||||
</svg>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<div class="grow max-width-100">
|
||||
<a href="{{ $post.DiscussionUrl }}" class="size-h3 color-primary-if-not-visited" target="_blank" rel="noreferrer">{{ .Title }}</a>
|
||||
|
||||
<div class="grow min-width-0">
|
||||
<a href="{{ .DiscussionUrl }}" class="size-h3 color-primary-if-not-visited" target="_blank" rel="noreferrer">{{ .Title }}</a>
|
||||
<ul class="list-horizontal-text">
|
||||
<li title="{{ $post.TimePosted | formatTime }}" {{ dynamicRelativeTimeAttrs $post.TimePosted }}>{{ $post.TimePosted | relativeTime }}</li>
|
||||
<li>{{ $post.Score | formatNumber }} points</li>
|
||||
<li>{{ $post.CommentCount | formatNumber }} comments</li>
|
||||
{{ if $post.HasTargetUrl }}
|
||||
<li class="shrink min-width-0"><a class="visited-indicator text-truncate block" href="{{ .TargetUrl }}" target="_blank" rel="noreferrer">{{ $post.TargetUrlDomain }}</a></li>
|
||||
<li {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
|
||||
<li>{{ .Score | formatNumber }} points</li>
|
||||
<li>{{ .CommentCount | formatNumber }} comments</li>
|
||||
{{ if .HasTargetUrl }}
|
||||
<li class="shrink min-width-0"><a class="visited-indicator text-truncate block" href="{{ .TargetUrl }}" target="_blank" rel="noreferrer">{{ .TargetUrlDomain }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
@ -33,7 +34,4 @@
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ if gt (len .Posts) $.CollapseAfter }}
|
||||
<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
@ -50,6 +50,7 @@
|
||||
|
||||
<div class="content-bounds">
|
||||
<div class="page" id="page">
|
||||
<div class="page-content" id="page-content"></div>
|
||||
<div class="page-loading-container">
|
||||
<!-- TODO: add a bigger/better loading indicator -->
|
||||
<div class="loading-icon"></div>
|
||||
@ -59,11 +60,8 @@
|
||||
|
||||
<div class="footer flex items-center flex-column">
|
||||
<div>
|
||||
<span class="size-h3">Glance</span> ({{ .App.Version }})
|
||||
<a class="size-h3" href="https://github.com/glanceapp/glance" target="_blank" rel="noreferrer">Glance</a> {{ if ne "dev" .App.Version }}<a class="visited-indicator" title="Release notes" href="https://github.com/glanceapp/glance/releases/tag/{{ .App.Version }}" target="_blank" rel="noreferrer">{{ .App.Version }}</a>{{ else }}({{ .App.Version }}){{ end }}
|
||||
</div>
|
||||
<ul class="list-horizontal-text margin-top-5 size-h5 color-primary">
|
||||
<li><a href="https://github.com/glanceapp/glance/issues" target="_blank" rel="noreferrer">Report issue</a></li>
|
||||
<li><a href="https://github.com/glanceapp/glance/discussions" target="_blank" rel="noreferrer">Submit feedback</a></li>
|
||||
</ul>
|
||||
<a class="color-primary block margin-top-5 size-h5" href="https://github.com/glanceapp/glance/issues" target="_blank" rel="noreferrer">Report issue</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
@ -20,7 +20,7 @@
|
||||
{{ end }}
|
||||
<a href="{{ .DiscussionUrl }}" title="{{ .Title }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-7 margin-bottom-auto" target="_blank" rel="noreferrer">{{ .Title }}</a>
|
||||
<ul class="list-horizontal-text margin-top-7">
|
||||
<li title="{{ .TimePosted | formatTime }}" {{ dynamicRelativeTimeAttrs .TimePosted }}>{{ .TimePosted | relativeTime }}</li>
|
||||
<li {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
|
||||
<li>{{ .Score | formatNumber }} points</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -19,7 +19,7 @@
|
||||
{{ end }}
|
||||
<a href="{{ .DiscussionUrl }}" title="{{ .Title }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-7" target="_blank" rel="noreferrer">{{ .Title }}</a>
|
||||
<ul class="list-horizontal-text margin-top-7">
|
||||
<li title="{{ .TimePosted | formatTime }}" {{ dynamicRelativeTimeAttrs .TimePosted }}>{{ .TimePosted | relativeTime }}</li>
|
||||
<li {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
|
||||
<li>{{ .Score | formatNumber }} points</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -1,12 +1,12 @@
|
||||
{{ template "widget-base.html" . }}
|
||||
|
||||
{{ 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 }}
|
||||
<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>
|
||||
<ul class="list-horizontal-text">
|
||||
<li title="{{ $release.TimeReleased | formatTime }}" {{ dynamicRelativeTimeAttrs $release.TimeReleased }}>{{ $release.TimeReleased | relativeTime }}</li>
|
||||
<li {{ dynamicRelativeTimeAttrs $release.TimeReleased }}></li>
|
||||
<li>{{ $release.Version }}</li>
|
||||
{{ if gt $release.Downvotes 3 }}
|
||||
<li>{{ $release.Downvotes | formatNumber }} ⚠</li>
|
||||
@ -15,7 +15,4 @@
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ if gt (len .Releases) $.CollapseAfter }}
|
||||
<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
@ -13,7 +13,7 @@
|
||||
<div class="flex gap-7 size-h5 margin-top-3">
|
||||
<ul class="list list-gap-2">
|
||||
{{ range .RepositoryDetails.PullRequests }}
|
||||
<li title="{{ .CreatedAt | formatTime }}" {{ dynamicRelativeTimeAttrs .CreatedAt }}>{{ .CreatedAt | relativeTime }}</li>
|
||||
<li {{ dynamicRelativeTimeAttrs .CreatedAt }}></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
<ul class="list list-gap-2 min-width-0">
|
||||
@ -30,7 +30,7 @@
|
||||
<div class="flex gap-7 size-h5 margin-top-3">
|
||||
<ul class="list list-gap-2">
|
||||
{{ range .RepositoryDetails.Issues }}
|
||||
<li title="{{ .CreatedAt | formatTime }}" {{ dynamicRelativeTimeAttrs .CreatedAt }}>{{ .CreatedAt | relativeTime }}</li>
|
||||
<li {{ dynamicRelativeTimeAttrs .CreatedAt }}></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
<ul class="list list-gap-2 min-width-0">
|
||||
|
@ -17,7 +17,7 @@
|
||||
<div class="rss-card-2-content padding-inline-widget">
|
||||
<a href="{{ .Link }}" title="{{ .Title }}" class="block text-truncate color-primary-if-not-visited" target="_blank" rel="noreferrer">{{ .Title }}</a>
|
||||
<ul class="list-horizontal-text flex-nowrap margin-top-5">
|
||||
<li class="shrink-0" title="{{ .PublishedAt | formatTime }}" {{ dynamicRelativeTimeAttrs .PublishedAt }}>{{ .PublishedAt | relativeTime }}</li>
|
||||
<li class="shrink-0" {{ dynamicRelativeTimeAttrs .PublishedAt }}></li>
|
||||
<li class="shrink min-width-0 text-truncate">{{ .ChannelName }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<div class="margin-bottom-widget padding-inline-widget flex flex-column grow">
|
||||
<a href="{{ .Link }}" title="{{ .Title }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-10 margin-bottom-auto" target="_blank" rel="noreferrer">{{ .Title }}</a>
|
||||
<ul class="list-horizontal-text flex-nowrap margin-top-7">
|
||||
<li class="shrink-0" title="{{ .PublishedAt | formatTime }}" {{ dynamicRelativeTimeAttrs .PublishedAt }}>{{ .PublishedAt | relativeTime }}</li>
|
||||
<li class="shrink-0" {{ dynamicRelativeTimeAttrs .PublishedAt }}></li>
|
||||
<li class="shrink min-width-0 text-truncate">{{ .ChannelName }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -1,12 +1,12 @@
|
||||
{{ template "widget-base.html" . }}
|
||||
|
||||
{{ define "widget-content" }}
|
||||
<ul class="list list-gap-14 list-collapsible">
|
||||
{{ range $i, $item := .Items }}
|
||||
<li {{ if shouldCollapse $i $.CollapseAfter }}class="list-collapsible-item" style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}>
|
||||
<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
|
||||
{{ range .Items }}
|
||||
<li>
|
||||
<a class="size-title-dynamic color-primary-if-not-visited" href="{{ .Link }}" target="_blank" rel="noreferrer">{{ .Title }}</a>
|
||||
<ul class="list-horizontal-text">
|
||||
<li title="{{ $item.PublishedAt | formatTime }}" {{ dynamicRelativeTimeAttrs $item.PublishedAt }}>{{ .PublishedAt | relativeTime }}</li>
|
||||
<li {{ dynamicRelativeTimeAttrs .PublishedAt }}></li>
|
||||
{{ if gt (len $.FeedRequests) 1 }}
|
||||
<li><a href="{{ .ChannelURL }}" target="_blank" rel="noreferrer">{{ .ChannelName }}</a></li>
|
||||
{{ end }}
|
||||
@ -14,7 +14,4 @@
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ if gt (len .Items) $.CollapseAfter }}
|
||||
<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
@ -1,13 +1,13 @@
|
||||
{{ template "widget-base.html" . }}
|
||||
|
||||
{{ define "widget-content" }}
|
||||
<ul class="list list-gap-14 list-collapsible">
|
||||
{{ range $i, $channel := .Channels }}
|
||||
<li {{ if shouldCollapse $i $.CollapseAfter }}class="list-collapsible-item" style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}>
|
||||
<div class="{{ if $channel.IsLive }}twitch-channel-live {{ end }}flex gap-10 items-start thumbnail-container">
|
||||
<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
|
||||
{{ range .Channels }}
|
||||
<li>
|
||||
<div class="{{ if .IsLive }}twitch-channel-live {{ end }}flex gap-10 items-start thumbnail-container">
|
||||
<div class="twitch-channel-avatar-container">
|
||||
{{ if $channel.Exists }}
|
||||
<img class="twitch-channel-avatar thumbnail" src="{{ $channel.AvatarUrl }}" alt="" loading="lazy">
|
||||
{{ if .Exists }}
|
||||
<img class="twitch-channel-avatar thumbnail" src="{{ .AvatarUrl }}" alt="" loading="lazy">
|
||||
{{ else }}
|
||||
<svg class="twitch-channel-avatar thumbnail" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
|
||||
@ -15,13 +15,13 @@
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="shrink min-width-0">
|
||||
<a href="https://twitch.tv/{{ $channel.Login }}" class="size-h3{{ if $channel.IsLive }} color-highlight{{ end }} block text-truncate" target="_blank" rel="noreferrer">{{ $channel.Name }}</a>
|
||||
{{ if $channel.Exists }}
|
||||
{{ if $channel.IsLive }}
|
||||
<a class="text-truncate block" href="https://www.twitch.tv/directory/category/{{ $channel.CategorySlug }}" target="_blank" rel="noreferrer">{{ $channel.Category }}</a>
|
||||
<a href="https://twitch.tv/{{ .Login }}" class="size-h3{{ if .IsLive }} color-highlight{{ end }} block text-truncate" target="_blank" rel="noreferrer">{{ .Name }}</a>
|
||||
{{ if .Exists }}
|
||||
{{ if .IsLive }}
|
||||
<a class="text-truncate block" href="https://www.twitch.tv/directory/category/{{ .CategorySlug }}" target="_blank" rel="noreferrer">{{ .Category }}</a>
|
||||
<ul class="list-horizontal-text">
|
||||
<li title="{{ $channel.LiveSince | formatTime }}" {{ dynamicRelativeTimeAttrs $channel.LiveSince }}>{{ $channel.LiveSince | relativeTime }}</li>
|
||||
<li>{{ $channel.ViewersCount | formatViewerCount }} viewers</li>
|
||||
<li {{ dynamicRelativeTimeAttrs .LiveSince }}></li>
|
||||
<li>{{ .ViewersCount | formatViewerCount }} viewers</li>
|
||||
</ul>
|
||||
{{ else }}
|
||||
<div>Offline</div>
|
||||
@ -34,7 +34,4 @@
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ if gt (len .Channels) $.CollapseAfter }}
|
||||
<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
@ -1,22 +1,21 @@
|
||||
{{ template "widget-base.html" . }}
|
||||
|
||||
{{ define "widget-content" }}
|
||||
<ul class="list list-gap-14 list-collapsible">
|
||||
{{ range $i, $category := .Categories }}
|
||||
{{ $shouldCollapseItem := shouldCollapse $i $.CollapseAfter }}
|
||||
<li class="twitch-category thumbnail-container{{ if $shouldCollapseItem }} list-collapsible-item{{ end }}" {{ if $shouldCollapseItem }}style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}>
|
||||
<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
|
||||
{{ range .Categories }}
|
||||
<li class="twitch-category thumbnail-container">
|
||||
<div class="flex gap-10 items-center">
|
||||
<img class="twitch-category-thumbnail thumbnail" loading="lazy" src="{{ $category.AvatarUrl }}" alt="">
|
||||
<img class="twitch-category-thumbnail thumbnail" loading="lazy" src="{{ .AvatarUrl }}" alt="">
|
||||
<div class="shrink min-width-0">
|
||||
<a class="size-h3 color-highlight text-truncate block" href="https://www.twitch.tv/directory/category/{{ $category.Slug }}" target="_blank" rel="noreferrer">{{ $category.Name }}</a>
|
||||
<a class="size-h3 color-highlight text-truncate block" href="https://www.twitch.tv/directory/category/{{ .Slug }}" target="_blank" rel="noreferrer">{{ .Name }}</a>
|
||||
<ul class="list-horizontal-text">
|
||||
<li>{{ $category.ViewersCount | formatViewerCount }} viewers</li>
|
||||
{{ if $category.IsNew }}
|
||||
<li>{{ .ViewersCount | formatViewerCount }} viewers</li>
|
||||
{{ if .IsNew }}
|
||||
<li class="color-primary">NEW</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
<ul class="list-horizontal-text flex-nowrap">
|
||||
{{ range $i, $tag := $category.Tags }}
|
||||
{{ range $i, $tag := .Tags }}
|
||||
{{ if eq $i 0 }}
|
||||
<li class="shrink-0">{{ $tag.Name }}</li>
|
||||
{{ else }}
|
||||
@ -29,7 +28,4 @@
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ if gt (len .Categories) $.CollapseAfter }}
|
||||
<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div class="margin-top-10 margin-bottom-widget flex flex-column grow padding-inline-widget">
|
||||
<a class="video-title color-primary-if-not-visited" href="{{ .Url }}" target="_blank" rel="noreferrer" title="{{ .Title }}">{{ .Title }}</a>
|
||||
<ul class="list-horizontal-text flex-nowrap margin-top-7">
|
||||
<li class="shrink-0" title="{{ .TimePosted | formatTime }}" {{ dynamicRelativeTimeAttrs .TimePosted }}>{{ .TimePosted | relativeTime }}</li>
|
||||
<li class="shrink-0" {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
|
||||
<li class="shrink min-width-0">
|
||||
<a class="block text-truncate" href="{{ .AuthorUrl }}" target="_blank" rel="noreferrer">{{ .Author }}</a>
|
||||
</li>
|
||||
|
@ -3,7 +3,7 @@
|
||||
{{ define "widget-content-classes" }}widget-content-frameless{{ end }}
|
||||
|
||||
{{ define "widget-content" }}
|
||||
<div class="cards-grid">
|
||||
<div class="cards-grid collapsible-container" data-collapse-after-rows="{{ .CollapseAfterRows }}">
|
||||
{{ range .Videos }}
|
||||
<div class="card widget-content-frame thumbnail-container">
|
||||
{{ template "video-card-contents" . }}
|
||||
|
@ -44,6 +44,12 @@ func (channels TwitchChannels) SortByViewers() {
|
||||
})
|
||||
}
|
||||
|
||||
func (channels TwitchChannels) SortByLive() {
|
||||
sort.SliceStable(channels, func(i, j int) bool {
|
||||
return channels[i].IsLive && !channels[j].IsLive
|
||||
})
|
||||
}
|
||||
|
||||
type twitchOperationResponse struct {
|
||||
Data json.RawMessage
|
||||
Extensions struct {
|
||||
|
@ -36,7 +36,7 @@ func Main() int {
|
||||
return 1
|
||||
}
|
||||
|
||||
if app.Serve() != nil {
|
||||
if err := app.Serve(); err != nil {
|
||||
fmt.Printf("http server error: %v\n", err)
|
||||
return 1
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ type TwitchChannels struct {
|
||||
ChannelsRequest []string `yaml:"channels"`
|
||||
Channels []feed.TwitchChannel `yaml:"-"`
|
||||
CollapseAfter int `yaml:"collapse-after"`
|
||||
SortBy string `yaml:"sort-by"`
|
||||
}
|
||||
|
||||
func (widget *TwitchChannels) Initialize() error {
|
||||
@ -23,6 +24,10 @@ func (widget *TwitchChannels) Initialize() error {
|
||||
widget.CollapseAfter = 5
|
||||
}
|
||||
|
||||
if widget.SortBy != "viewers" && widget.SortBy != "live" {
|
||||
widget.SortBy = "viewers"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -33,7 +38,12 @@ func (widget *TwitchChannels) Update(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
channels.SortByViewers()
|
||||
if widget.SortBy == "viewers" {
|
||||
channels.SortByViewers()
|
||||
} else if widget.SortBy == "live" {
|
||||
channels.SortByLive()
|
||||
}
|
||||
|
||||
widget.Channels = channels
|
||||
}
|
||||
|
||||
|
@ -10,12 +10,13 @@ import (
|
||||
)
|
||||
|
||||
type Videos struct {
|
||||
widgetBase `yaml:",inline"`
|
||||
Videos feed.Videos `yaml:"-"`
|
||||
VideoUrlTemplate string `yaml:"video-url-template"`
|
||||
Style string `yaml:"style"`
|
||||
Channels []string `yaml:"channels"`
|
||||
Limit int `yaml:"limit"`
|
||||
widgetBase `yaml:",inline"`
|
||||
Videos feed.Videos `yaml:"-"`
|
||||
VideoUrlTemplate string `yaml:"video-url-template"`
|
||||
Style string `yaml:"style"`
|
||||
CollapseAfterRows int `yaml:"collapse-after-rows"`
|
||||
Channels []string `yaml:"channels"`
|
||||
Limit int `yaml:"limit"`
|
||||
}
|
||||
|
||||
func (widget *Videos) Initialize() error {
|
||||
@ -25,6 +26,10 @@ func (widget *Videos) Initialize() error {
|
||||
widget.Limit = 25
|
||||
}
|
||||
|
||||
if widget.CollapseAfterRows == 0 || widget.CollapseAfterRows < -1 {
|
||||
widget.CollapseAfterRows = 4
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -14,17 +14,26 @@ type Weather struct {
|
||||
Location string `yaml:"location"`
|
||||
ShowAreaName bool `yaml:"show-area-name"`
|
||||
HideLocation bool `yaml:"hide-location"`
|
||||
HourFormat string `yaml:"hour-format"`
|
||||
Units string `yaml:"units"`
|
||||
Place *feed.PlaceJson `yaml:"-"`
|
||||
Weather *feed.Weather `yaml:"-"`
|
||||
TimeLabels [12]string `yaml:"-"`
|
||||
}
|
||||
|
||||
var timeLabels = [12]string{"2am", "4am", "6am", "8am", "10am", "12pm", "2pm", "4pm", "6pm", "8pm", "10pm", "12am"}
|
||||
var timeLabels12h = [12]string{"2am", "4am", "6am", "8am", "10am", "12pm", "2pm", "4pm", "6pm", "8pm", "10pm", "12am"}
|
||||
var timeLabels24h = [12]string{"02:00", "04:00", "06:00", "08:00", "10:00", "12:00", "14:00", "16:00", "18:00", "20:00", "22:00", "00:00"}
|
||||
|
||||
func (widget *Weather) Initialize() error {
|
||||
widget.withTitle("Weather").withCacheOnTheHour()
|
||||
widget.TimeLabels = timeLabels
|
||||
|
||||
if widget.HourFormat == "" || widget.HourFormat == "12h" {
|
||||
widget.TimeLabels = timeLabels12h
|
||||
} else if widget.HourFormat == "24h" {
|
||||
widget.TimeLabels = timeLabels24h
|
||||
} else {
|
||||
return fmt.Errorf("invalid hour format '%s' for weather widget, must be either 12h or 24h", widget.HourFormat)
|
||||
}
|
||||
|
||||
if widget.Units == "" {
|
||||
widget.Units = "metric"
|
||||
|
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@ -77,7 +78,40 @@ var buildTargets = []buildTarget{
|
||||
},
|
||||
}
|
||||
|
||||
func hasUncommitedChanges() (bool, error) {
|
||||
output, err := exec.Command("git", "status", "--porcelain").CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(output) > 0, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flags := flag.NewFlagSet("", flag.ExitOnError)
|
||||
|
||||
specificTag := flags.String("tag", "", "Which tagged version to build")
|
||||
|
||||
err := flags.Parse(os.Args[1:])
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
uncommitedChanges, err := hasUncommitedChanges()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if uncommitedChanges {
|
||||
fmt.Println("There are uncommited changes - commit, stash or discard them first")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
|
||||
if err != nil {
|
||||
@ -95,10 +129,24 @@ func main() {
|
||||
os.Mkdir(buildPath, 0755)
|
||||
os.Mkdir(archivesPath, 0755)
|
||||
|
||||
version, err := getVersionFromGit()
|
||||
var version string
|
||||
|
||||
if *specificTag == "" {
|
||||
version, err := getVersionFromGit()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(version, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
version = *specificTag
|
||||
}
|
||||
|
||||
output, err := exec.Command("git", "checkout", "tags/"+version).CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(version, err)
|
||||
fmt.Println(string(output))
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -119,13 +167,19 @@ func main() {
|
||||
|
||||
fmt.Println("Building docker image")
|
||||
|
||||
output, err := exec.Command(
|
||||
"sudo", "docker", "build",
|
||||
var dockerBuildOptions = []string{
|
||||
"docker", "build",
|
||||
"--platform=linux/amd64,linux/arm64,linux/arm/v7",
|
||||
"-t", versionTag,
|
||||
"-t", latestTag,
|
||||
".",
|
||||
).CombinedOutput()
|
||||
}
|
||||
|
||||
if !strings.Contains(version, "beta") {
|
||||
dockerBuildOptions = append(dockerBuildOptions, "-t", latestTag)
|
||||
}
|
||||
|
||||
dockerBuildOptions = append(dockerBuildOptions, ".")
|
||||
|
||||
output, err = exec.Command("sudo", dockerBuildOptions...).CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(string(output))
|
||||
@ -152,6 +206,10 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if strings.Contains(version, "beta") {
|
||||
return
|
||||
}
|
||||
|
||||
output, err = exec.Command(
|
||||
"sudo", "docker", "push", latestTag,
|
||||
).CombinedOutput()
|
||||
|
Loading…
Reference in New Issue
Block a user