1
1
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 ;
4
25
}
5
26
6
27
/**
7
28
* Throttles the execution of a given function by a specified interval.
8
29
*
9
- * @param {Function } func - The function to throttle.
30
+ * @param {function } func - The function to throttle.
10
31
* @param {number } interval - The interval in milliseconds.
11
- * @returns {Function } - The throttled function.
32
+ * @returns {function } - The throttled function.
12
33
*/
13
34
function throttle ( func , interval ) {
14
35
let timeout = null ;
@@ -25,9 +46,9 @@ function throttle(func, interval) {
25
46
/**
26
47
* Debounces a function, ensuring that it is only called after a certain delay has passed since the last invocation.
27
48
*
28
- * @param {Function } func - The function to be debounced.
49
+ * @param {function } func - The function to be debounced.
29
50
* @param {number } delay - The delay time in milliseconds.
30
- * @returns {Function } - The debounced function.
51
+ * @returns {function } - The debounced function.
31
52
*/
32
53
function debounce ( func , delay ) {
33
54
let timer ;
@@ -39,8 +60,20 @@ function debounce(func, delay) {
39
60
} ;
40
61
}
41
62
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
+
42
74
/**
43
75
* ResizeObserverCallback function
76
+ *
44
77
* @param {ResizeObserverEntry[] } [roea] - ResizeObserverEntry Array
45
78
*/
46
79
function updateStyleDef ( roea ) {
@@ -51,6 +84,7 @@ function updateStyleDef(roea) {
51
84
styleDef . textContent = styleDefString ( offsetWidth , offsetHeight ) ;
52
85
}
53
86
}
87
+
54
88
/**
55
89
* A "throttled" ResizeObserverCallback function
56
90
*/
@@ -69,30 +103,83 @@ function updateTagDef() {
69
103
tagDef . textContent = `<lastfm-tracks ${ attribs . join ( ' ' ) } >\n</lastfm-tracks>` ;
70
104
}
71
105
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
+
72
121
window . addEventListener (
73
122
'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' ) ;
77
135
const toggleDynaHeader = document . querySelector ( 'input.dynaheader' ) ;
78
136
const toggleHideAlbums = document . querySelector ( 'input.hidealbums' ) ;
137
+ const usernameInput = document . querySelector ( 'input.username' ) ;
138
+ const apiKeyInput = document . querySelector ( 'input.apikey' ) ;
79
139
const dynaHeaderChanged = ( ) => {
80
140
widget . classList . toggle ( 'dynaheader' , toggleDynaHeader . checked ) ;
81
141
updateTagDef ( ) ;
82
- }
142
+ } ;
83
143
const hideAlbumsChanged = ( ) => {
84
144
widget . classList . toggle ( 'no-albums' , toggleHideAlbums . checked ) ;
85
145
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
+ } ;
87
167
if ( widget ) {
88
168
stopButton ?. addEventListener ( 'click' , ( ) => {
89
169
widget . stopUpdating ( )
90
170
} ) ;
171
+ incIntervalButton ?. addEventListener ( 'click' , ( ) => {
172
+ widget . setAttribute ( 'interval' , Number ( widget . state . interval ) + 5 ) ;
173
+ } ) ;
91
174
toggleDynaHeader ?. addEventListener ( 'change' , dynaHeaderChanged ) ;
92
175
toggleHideAlbums ?. addEventListener ( 'change' , hideAlbumsChanged ) ;
176
+ usernameInput ?. addEventListener ( 'change' , userChanged ) ;
177
+ apiKeyInput ?. addEventListener ( 'change' , apiKeyChanged ) ;
93
178
new ResizeObserver ( handleResizedWidget ) . observe ( widget ) ;
94
179
95
180
// init
181
+ userChanged ( ) ;
182
+ apiKeyChanged ( ) ;
96
183
dynaHeaderChanged ( ) ;
97
184
hideAlbumsChanged ( ) ;
98
185
}
0 commit comments