Skip to content

Commit ebde620

Browse files
committed
readme and demo-page updates (first ready-like version)
1 parent 563f0ca commit ebde620

File tree

5 files changed

+248
-67
lines changed

5 files changed

+248
-67
lines changed

README.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# 🔴 lastfm-widgets
22

3-
*Tracks* is a web-widget to see "scrobbles" (tracked play history) from a Last.fm account. See *Tracks* in action
4-
on https://lastfm-widgets.deno.dev/ and https://www.rockland.dk/. At the first site, the plan is you should be able
5-
to see the widget showing scrobbles from your own (or any) [Last-fm](https://www.last.fm/) account, and try various
6-
other configuration options. But that is not fully ready yet.
3+
*Tracks* is a web-widget to show "scrobbles" (play history) from a [Last-fm](https://www.last.fm/) account.
4+
See *Tracks* in action on https://www.rockland.dk/ and https://lastfm-widgets.deno.dev/. At the latter site, you
5+
can not only find some more information and instructions for use, you can also play with some of the customization
6+
options, including setting the user to show scrobbles from.
77

88
[![Tracks screenshot](Tracks.png "Tracks widget example")](https://www.last.fm/user/rockland)
99

@@ -19,10 +19,10 @@ The *Tracks* widget itself is made as a *webcomponent* using pure standard web c
1919
Audioscrobbler v2 API, or it can be supported by a custom backend "proxy-api". The latter is encouraged
2020
because it makes it possible to implement throttling of requests to Last.fm's API.
2121

22-
This repository not only holds the widget itself, but also a demo page and an example backend proxy-api. The
23-
proxy-api is made in [Deno](https://deno.com/) (server-side javascript/typescript). Also, this repository is set
24-
up as a [Deno Deploy](https://deno.com/deploy) project. Any updates in main-branch are immediately
25-
deployed to the demo-site at https://lastfm-widgets.deno.dev/.
22+
This repository not only holds the widget itself, but also the demo page (https://lastfm-widgets.deno.dev/) and an
23+
example backend proxy-api. The proxy-api is made in [Deno](https://deno.com/) (server-side javascript/typescript).
24+
Also, this repository is set up as a [Deno Deploy](https://deno.com/deploy) project. Any updates in main-branch are
25+
immediately deployed to the demo-site at https://lastfm-widgets.deno.dev/.
2626

2727
The widget (frontend code) should be compatible back to at least Firefox 115 and Chromium 109 based web-browsers
2828
(which are the last versions of these running on Windows 7/8 installations). It also runs in Safari, but unsure
@@ -34,7 +34,8 @@ versions of Deno 2.
3434

3535
The widget frontend code. *All* that is needed for widget to work in *Demo* or *Basic* mode. See
3636
[Releases](https://github.com/StigNygaard/lastfm-widgets/releases) to get latest "release-version" of this folder's
37-
content. And see https://lastfm-widgets.deno.dev/ for an explanation of the modes the widget supports.
37+
content. And see https://lastfm-widgets.deno.dev/ for an explanation of the modes and customizations the widget
38+
supports.
3839

3940
#### /demo/ folder
4041

@@ -49,3 +50,12 @@ widget is in *Backend-supported* mode, but also used by widget on [rockland.dk](
4950

5051
Basically the "web-server" or "router" for https://lastfm-widgets.deno.dev/, serving above-mentioned content.
5152

53+
## Future updates?
54+
55+
What could future updates bring? *Maybe*:
56+
- A layout that adapt nicer to wider display dimensions of widget
57+
- Dark mode
58+
- Refactoring code *if* I'm in the mood for that kind of thing 🙂
59+
- More documentation
60+
- Generally fixing and tuning...
61+
- Another widget

demo/GitHub-Mark-Light-32px.png

1.53 KB
Loading

demo/demo.css

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,65 +6,113 @@
66

77
.demo {
88
display: flex;
9-
gap: 2rem;
10-
padding: 2rem;
9+
flex-wrap: wrap;
10+
column-gap: 1.5rem;
11+
padding: 1.8rem;
1112
}
1213

1314
.info {
14-
min-width: 22rem;
15-
max-width: 40rem;
15+
flex: 0 1 38rem;
1616
}
17+
1718
.widget {
1819
}
19-
.options pre {
20-
overflow: auto;
21-
font-size: 85%;
22-
padding: 0 0 1em 0;
23-
border-bottom: 1px #eee solid;
24-
}
25-
.options pre.style {
26-
padding-top: 1em;
27-
border-top: 1px #eee solid;
20+
21+
.options {
22+
flex: 0 1 16rem;
2823
}
2924

25+
3026
h1 {
3127
font-size: 1.85rem;
3228
}
29+
3330
h2 {
3431
font-size: 1.25rem;
3532
}
33+
3634
h3 {
3735
font-size: 1.05rem;
3836
}
3937

38+
39+
a[href^='https://github.com/StigNygaard/']::after {
40+
content: '';
41+
background-image: url(./GitHub-Mark-Light-32px.png);
42+
background-size: 1lh 1lh;
43+
background-repeat: no-repeat;
44+
background-position: right;
45+
display: inline-block;
46+
width: 1.2lh;
47+
height: 1lh;
48+
filter: invert(1);
49+
vertical-align: bottom;
50+
}
51+
52+
ul {
53+
margin-inline-start: 0;
54+
padding-inline-start: 20px;
55+
}
56+
57+
ul ul {
58+
margin-block-start: .2em;
59+
}
60+
4061
.keyshot summary {
4162
cursor: pointer;
4263
}
64+
4365
.keyshot a {
4466
display: block;
4567
}
68+
4669
.keyshot img {
4770
display: block;
4871
width: calc(100% - 2px);
4972
max-width: max-content;
5073
border: 1px solid #000;
5174
}
5275

76+
5377
.resizeable {
5478
border: none;
55-
padding: 1.2rem;
79+
padding: 1rem;
5680
width: 18em;
5781
min-width: 14em;
58-
height: 70vh;
82+
height: min(714px, 75svh);
5983
min-height: 7em;
6084
overflow: auto;
6185
resize: both;
6286
}
87+
6388
lastfm-tracks {
6489
box-sizing: border-box;
6590
border: 1px solid #000;
6691
height: 100%;
6792
}
93+
6894
button {
6995
margin: 1em 0;
7096
}
97+
98+
99+
#show-mode {
100+
font-weight: bold;
101+
}
102+
103+
.textinput {
104+
display: flex;
105+
flex-direction: column;
106+
}
107+
108+
.options pre {
109+
overflow: auto;
110+
font-size: 85%;
111+
padding: 0 0 1em 0;
112+
border-bottom: 1px #eee solid;
113+
}
114+
115+
.options pre.style {
116+
padding-top: 1em;
117+
border-top: 1px #eee solid;
118+
}

demo/demo.js

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
11

2-
function styleDefString(width, height) {
3-
return `lastfm-tracks {\n box-sizing: border-box;\n border: 1px solid #000;\n width: ${width}px;\n height: ${height}px;\n}`;
2+
/**
3+
* Creates an HTML element with the specified tag name, attributes, and content.
4+
*
5+
* @param {string} tagName - The tag name of the element to create.
6+
* @param {object} attributes - An object containing the attributes to set on the element.
7+
* @param {...(string | Node)} content - Content to be added to the element. Can be strings and Node objects.
8+
* @returns {HTMLElement} - The created HTML element.
9+
*/
10+
function create(tagName, attributes = {}, ...content) {
11+
const element = document.createElement(tagName);
12+
for (const [attr, value] of Object.entries(attributes)) {
13+
if (value === false) {
14+
// Ignore - Don't create attribute (the attribute is "disabled")
15+
} else if (value === true) {
16+
element.setAttribute(attr, attr); // xhtml-style "enabled" attribute
17+
} else {
18+
element.setAttribute(attr, String(value));
19+
}
20+
}
21+
if (content?.length) {
22+
element.append(...content);
23+
}
24+
return element;
425
}
526

627
/**
728
* Throttles the execution of a given function by a specified interval.
829
*
9-
* @param {Function} func - The function to throttle.
30+
* @param {function} func - The function to throttle.
1031
* @param {number} interval - The interval in milliseconds.
11-
* @returns {Function} - The throttled function.
32+
* @returns {function} - The throttled function.
1233
*/
1334
function throttle(func, interval) {
1435
let timeout = null;
@@ -25,9 +46,9 @@ function throttle(func, interval) {
2546
/**
2647
* Debounces a function, ensuring that it is only called after a certain delay has passed since the last invocation.
2748
*
28-
* @param {Function} func - The function to be debounced.
49+
* @param {function} func - The function to be debounced.
2950
* @param {number} delay - The delay time in milliseconds.
30-
* @returns {Function} - The debounced function.
51+
* @returns {function} - The debounced function.
3152
*/
3253
function debounce(func, delay) {
3354
let timer;
@@ -39,8 +60,20 @@ function debounce(func, delay) {
3960
};
4061
}
4162

63+
/**
64+
* Make style definition for custom "lastfm-tracks" element.
65+
*
66+
* @param {number} width - The width of the element in pixels.
67+
* @param {number} height - The height of the element in pixels.
68+
* @return {string} - Style definition with the specified width and height.
69+
*/
70+
function styleDefString(width, height) {
71+
return `lastfm-tracks {\n box-sizing: border-box;\n border: 1px solid #000;\n width: ${width}px;\n height: ${height}px;\n}`;
72+
}
73+
4274
/**
4375
* ResizeObserverCallback function
76+
*
4477
* @param {ResizeObserverEntry[]} [roea] - ResizeObserverEntry Array
4578
*/
4679
function updateStyleDef(roea) {
@@ -51,6 +84,7 @@ function updateStyleDef(roea) {
5184
styleDef.textContent = styleDefString(offsetWidth, offsetHeight);
5285
}
5386
}
87+
5488
/**
5589
* A "throttled" ResizeObserverCallback function
5690
*/
@@ -69,30 +103,83 @@ function updateTagDef() {
69103
tagDef.textContent = `<lastfm-tracks ${attribs.join(' ')}>\n</lastfm-tracks>`;
70104
}
71105

106+
const stateChangeHandler = debounce(
107+
function(ev) {
108+
const showMode = document.getElementById('show-mode');
109+
if (ev.detail) {
110+
let widgetMode = ev.detail.widgetMode.trim();
111+
if (showMode) {
112+
widgetMode = widgetMode.charAt(0).toUpperCase() + widgetMode.slice(1);
113+
showMode.textContent = widgetMode.replace('Backend', 'Backend-supported');
114+
}
115+
updateTagDef();
116+
}
117+
},
118+
500
119+
);
120+
72121
window.addEventListener(
73122
'DOMContentLoaded',
74-
function () { // TODO run when widget is inserted...
75-
const widget = document.querySelector('lastfm-tracks');
76-
const stopButton = document.querySelector('button');
123+
function () {
124+
const widgetContainer = document.querySelector('div.widget');
125+
const widgetResizeable = widgetContainer.querySelector('div.resizeable');
126+
widgetContainer.addEventListener('stateChange', stateChangeHandler);
127+
/**
128+
* Tracks widget
129+
* @type {Tracks}
130+
*/
131+
const widget = create('lastfm-tracks', { backend: '/proxy-api', interval: 35 });
132+
widgetResizeable.appendChild(widget);
133+
const stopButton = document.querySelector('button#stopBtn');
134+
const incIntervalButton = document.querySelector('button#incIntervalBtn');
77135
const toggleDynaHeader = document.querySelector('input.dynaheader');
78136
const toggleHideAlbums = document.querySelector('input.hidealbums');
137+
const usernameInput = document.querySelector('input.username');
138+
const apiKeyInput = document.querySelector('input.apikey');
79139
const dynaHeaderChanged = () => {
80140
widget.classList.toggle('dynaheader', toggleDynaHeader.checked);
81141
updateTagDef();
82-
}
142+
};
83143
const hideAlbumsChanged = () => {
84144
widget.classList.toggle('no-albums', toggleHideAlbums.checked);
85145
updateTagDef();
86-
}
146+
};
147+
const userChanged = () => {
148+
const username = usernameInput.value?.trim();
149+
if (username.length) {
150+
widget.removeAttribute('backend');
151+
widget.setAttribute('user', username);
152+
} else {
153+
widget.removeAttribute('user');
154+
widget.setAttribute('backend', '/proxy-api');
155+
}
156+
// updateTagDef() will be called from stateChangeHandler()
157+
};
158+
const apiKeyChanged = () => {
159+
let apiKey = apiKeyInput.value?.trim();
160+
if (apiKey?.length) {
161+
widget.setAttribute('apikey', apiKey);
162+
} else {
163+
widget.removeAttribute('apikey');
164+
}
165+
// updateTagDef() will be called from stateChangeHandler()
166+
};
87167
if (widget) {
88168
stopButton?.addEventListener('click', () => {
89169
widget.stopUpdating()
90170
});
171+
incIntervalButton?.addEventListener('click', () => {
172+
widget.setAttribute('interval', Number(widget.state.interval) + 5);
173+
});
91174
toggleDynaHeader?.addEventListener('change', dynaHeaderChanged);
92175
toggleHideAlbums?.addEventListener('change', hideAlbumsChanged);
176+
usernameInput?.addEventListener('change', userChanged);
177+
apiKeyInput?.addEventListener('change', apiKeyChanged);
93178
new ResizeObserver(handleResizedWidget).observe(widget);
94179

95180
// init
181+
userChanged();
182+
apiKeyChanged();
96183
dynaHeaderChanged();
97184
hideAlbumsChanged();
98185
}

0 commit comments

Comments
 (0)