Skip to content

Commit

Permalink
readme and demo-page updates (first ready-like version)
Browse files Browse the repository at this point in the history
  • Loading branch information
StigNygaard committed Sep 22, 2024
1 parent 563f0ca commit ebde620
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 67 deletions.
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# 🔴 lastfm-widgets

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

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

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

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

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

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

#### /demo/ folder

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

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

## Future updates?

What could future updates bring? *Maybe*:
- A layout that adapt nicer to wider display dimensions of widget
- Dark mode
- Refactoring code *if* I'm in the mood for that kind of thing 🙂
- More documentation
- Generally fixing and tuning...
- Another widget
Binary file added demo/GitHub-Mark-Light-32px.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 63 additions & 15 deletions demo/demo.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,65 +6,113 @@

.demo {
display: flex;
gap: 2rem;
padding: 2rem;
flex-wrap: wrap;
column-gap: 1.5rem;
padding: 1.8rem;
}

.info {
min-width: 22rem;
max-width: 40rem;
flex: 0 1 38rem;
}

.widget {
}
.options pre {
overflow: auto;
font-size: 85%;
padding: 0 0 1em 0;
border-bottom: 1px #eee solid;
}
.options pre.style {
padding-top: 1em;
border-top: 1px #eee solid;

.options {
flex: 0 1 16rem;
}


h1 {
font-size: 1.85rem;
}

h2 {
font-size: 1.25rem;
}

h3 {
font-size: 1.05rem;
}


a[href^='https://github.com/StigNygaard/']::after {
content: '';
background-image: url(./GitHub-Mark-Light-32px.png);
background-size: 1lh 1lh;
background-repeat: no-repeat;
background-position: right;
display: inline-block;
width: 1.2lh;
height: 1lh;
filter: invert(1);
vertical-align: bottom;
}

ul {
margin-inline-start: 0;
padding-inline-start: 20px;
}

ul ul {
margin-block-start: .2em;
}

.keyshot summary {
cursor: pointer;
}

.keyshot a {
display: block;
}

.keyshot img {
display: block;
width: calc(100% - 2px);
max-width: max-content;
border: 1px solid #000;
}


.resizeable {
border: none;
padding: 1.2rem;
padding: 1rem;
width: 18em;
min-width: 14em;
height: 70vh;
height: min(714px, 75svh);
min-height: 7em;
overflow: auto;
resize: both;
}

lastfm-tracks {
box-sizing: border-box;
border: 1px solid #000;
height: 100%;
}

button {
margin: 1em 0;
}


#show-mode {
font-weight: bold;
}

.textinput {
display: flex;
flex-direction: column;
}

.options pre {
overflow: auto;
font-size: 85%;
padding: 0 0 1em 0;
border-bottom: 1px #eee solid;
}

.options pre.style {
padding-top: 1em;
border-top: 1px #eee solid;
}
109 changes: 98 additions & 11 deletions demo/demo.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@

function styleDefString(width, height) {
return `lastfm-tracks {\n box-sizing: border-box;\n border: 1px solid #000;\n width: ${width}px;\n height: ${height}px;\n}`;
/**
* Creates an HTML element with the specified tag name, attributes, and content.
*
* @param {string} tagName - The tag name of the element to create.
* @param {object} attributes - An object containing the attributes to set on the element.
* @param {...(string | Node)} content - Content to be added to the element. Can be strings and Node objects.
* @returns {HTMLElement} - The created HTML element.
*/
function create(tagName, attributes = {}, ...content) {
const element = document.createElement(tagName);
for (const [attr, value] of Object.entries(attributes)) {
if (value === false) {
// Ignore - Don't create attribute (the attribute is "disabled")
} else if (value === true) {
element.setAttribute(attr, attr); // xhtml-style "enabled" attribute
} else {
element.setAttribute(attr, String(value));
}
}
if (content?.length) {
element.append(...content);
}
return element;
}

/**
* Throttles the execution of a given function by a specified interval.
*
* @param {Function} func - The function to throttle.
* @param {function} func - The function to throttle.
* @param {number} interval - The interval in milliseconds.
* @returns {Function} - The throttled function.
* @returns {function} - The throttled function.
*/
function throttle(func, interval) {
let timeout = null;
Expand All @@ -25,9 +46,9 @@ function throttle(func, interval) {
/**
* Debounces a function, ensuring that it is only called after a certain delay has passed since the last invocation.
*
* @param {Function} func - The function to be debounced.
* @param {function} func - The function to be debounced.
* @param {number} delay - The delay time in milliseconds.
* @returns {Function} - The debounced function.
* @returns {function} - The debounced function.
*/
function debounce(func, delay) {
let timer;
Expand All @@ -39,8 +60,20 @@ function debounce(func, delay) {
};
}

/**
* Make style definition for custom "lastfm-tracks" element.
*
* @param {number} width - The width of the element in pixels.
* @param {number} height - The height of the element in pixels.
* @return {string} - Style definition with the specified width and height.
*/
function styleDefString(width, height) {
return `lastfm-tracks {\n box-sizing: border-box;\n border: 1px solid #000;\n width: ${width}px;\n height: ${height}px;\n}`;
}

/**
* ResizeObserverCallback function
*
* @param {ResizeObserverEntry[]} [roea] - ResizeObserverEntry Array
*/
function updateStyleDef(roea) {
Expand All @@ -51,6 +84,7 @@ function updateStyleDef(roea) {
styleDef.textContent = styleDefString(offsetWidth, offsetHeight);
}
}

/**
* A "throttled" ResizeObserverCallback function
*/
Expand All @@ -69,30 +103,83 @@ function updateTagDef() {
tagDef.textContent = `<lastfm-tracks ${attribs.join(' ')}>\n</lastfm-tracks>`;
}

const stateChangeHandler = debounce(
function(ev) {
const showMode = document.getElementById('show-mode');
if (ev.detail) {
let widgetMode = ev.detail.widgetMode.trim();
if (showMode) {
widgetMode = widgetMode.charAt(0).toUpperCase() + widgetMode.slice(1);
showMode.textContent = widgetMode.replace('Backend', 'Backend-supported');
}
updateTagDef();
}
},
500
);

window.addEventListener(
'DOMContentLoaded',
function () { // TODO run when widget is inserted...
const widget = document.querySelector('lastfm-tracks');
const stopButton = document.querySelector('button');
function () {
const widgetContainer = document.querySelector('div.widget');
const widgetResizeable = widgetContainer.querySelector('div.resizeable');
widgetContainer.addEventListener('stateChange', stateChangeHandler);
/**
* Tracks widget
* @type {Tracks}
*/
const widget = create('lastfm-tracks', { backend: '/proxy-api', interval: 35 });
widgetResizeable.appendChild(widget);
const stopButton = document.querySelector('button#stopBtn');
const incIntervalButton = document.querySelector('button#incIntervalBtn');
const toggleDynaHeader = document.querySelector('input.dynaheader');
const toggleHideAlbums = document.querySelector('input.hidealbums');
const usernameInput = document.querySelector('input.username');
const apiKeyInput = document.querySelector('input.apikey');
const dynaHeaderChanged = () => {
widget.classList.toggle('dynaheader', toggleDynaHeader.checked);
updateTagDef();
}
};
const hideAlbumsChanged = () => {
widget.classList.toggle('no-albums', toggleHideAlbums.checked);
updateTagDef();
}
};
const userChanged = () => {
const username = usernameInput.value?.trim();
if (username.length) {
widget.removeAttribute('backend');
widget.setAttribute('user', username);
} else {
widget.removeAttribute('user');
widget.setAttribute('backend', '/proxy-api');
}
// updateTagDef() will be called from stateChangeHandler()
};
const apiKeyChanged = () => {
let apiKey = apiKeyInput.value?.trim();
if (apiKey?.length) {
widget.setAttribute('apikey', apiKey);
} else {
widget.removeAttribute('apikey');
}
// updateTagDef() will be called from stateChangeHandler()
};
if (widget) {
stopButton?.addEventListener('click', () => {
widget.stopUpdating()
});
incIntervalButton?.addEventListener('click', () => {
widget.setAttribute('interval', Number(widget.state.interval) + 5);
});
toggleDynaHeader?.addEventListener('change', dynaHeaderChanged);
toggleHideAlbums?.addEventListener('change', hideAlbumsChanged);
usernameInput?.addEventListener('change', userChanged);
apiKeyInput?.addEventListener('change', apiKeyChanged);
new ResizeObserver(handleResizedWidget).observe(widget);

// init
userChanged();
apiKeyChanged();
dynaHeaderChanged();
hideAlbumsChanged();
}
Expand Down
Loading

0 comments on commit ebde620

Please sign in to comment.