@@ -141,57 +141,190 @@ <h2 class="text-xl font-semibold text-gray-700 dark:text-gray-200">Applications<
141
141
}
142
142
</ script >
143
143
< script >
144
- const apps = [
145
- { title : 'ECG with Heart Rate' , icon : 'fa-heartbeat' , color : 'cyan' , script : 'heartbeat_ecg' } ,
146
- { title : 'EMG with Envelope' , icon : 'fa-wave-square' , color : 'red' , script : 'emgenvelope' } ,
147
- { title : 'EOG with Blinks' , icon : 'fa-eye' , color : 'purple' , script : 'eog' } ,
148
- { title : 'EEG with FFT' , icon : 'fa-brain' , color : 'green' , script : 'ffteeg' } ,
149
- { title : 'EEG Tug of War game' , icon : 'fa-gamepad' , color : 'yellow' , script : 'game' } ,
150
- { title : 'EEG Beetle Game' , icon : 'fa-bug' , color : 'pink' , script : 'beetle' } ,
151
- { title : 'EOG Keystroke Emulator' , icon : 'fa-keyboard' , color : 'blue' , script : 'keystroke' } ,
152
- { title : 'GUI Visualization' , icon : 'fa-chart-bar' , color : 'orange' , script : 'gui' } ,
153
- { title : 'CSV Plotter' , icon : 'fa-chart-line' , color : 'teal' , script : 'csvplotter' } ,
154
- ] ;
155
-
156
- const appGrid = document . getElementById ( 'app-grid' ) ;
157
- apps . forEach ( app => {
158
- const card = document . createElement ( 'div' ) ;
159
- card . className = 'bg-gradient-to-b from-white to-gray-50 dark:from-gray-700 dark:to-gray-800 rounded-xl shadow border hover:shadow-lg transition duration-300 dark:border-gray-700 cursor-pointer' ;
160
- card . innerHTML = `
161
- <div class="p-4">
162
- <div class="w-full h-32 bg-${ app . color } -100 dark:bg-${ app . color } -900 rounded-lg flex items-center justify-center mb-4">
163
- <i class="fas ${ app . icon } text-4xl text-${ app . color } -600 dark:text-${ app . color } -300"></i>
144
+ async function loadApps ( ) {
145
+ try {
146
+ const appGrid = document . getElementById ( 'app-grid' ) ;
147
+ appGrid . innerHTML = `
148
+ <div class="col-span-full flex flex-col items-center justify-center py-12">
149
+ <i class="fas fa-circle-notch fa-spin text-cyan-500 text-4xl mb-4"></i>
150
+ <p class="text-gray-600 dark:text-gray-400">Loading applications...</p>
164
151
</div>
165
- <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-1">${ app . title } </h3>
166
- <p class="text-sm text-gray-600 dark:text-gray-400">Click to launch ${ app . title } </p>
167
- </div>
168
- ` ;
169
-
170
- card . addEventListener ( 'click' , async ( ) => {
171
- if ( ! isConnected ) {
172
- showAlert ( 'Please start LSL Stream first by any protocol (USB/WIFI/BLE)' ) ;
173
- return ;
152
+ ` ;
153
+ const response = await fetch ( '/get_apps_config' ) ;
154
+
155
+ if ( ! response . ok ) {
156
+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
157
+ }
158
+
159
+ const config = await response . json ( ) ;
160
+
161
+ if ( ! config ?. apps || ! Array . isArray ( config . apps ) ) {
162
+ throw new Error ( 'Invalid apps configuration format' ) ;
174
163
}
164
+
165
+ return config . apps ;
166
+ } catch ( error ) {
167
+ console . error ( 'Error loading apps config:' , error ) ;
175
168
176
- // First checking if app is already running
177
- try {
178
- const response = await fetch ( `/check_app_status/${ app . script } ` ) ;
179
- const data = await response . json ( ) ;
180
-
181
- if ( data . status === 'running' ) {
182
- showAlert ( `${ app . title } is already running!` ) ;
169
+ // Show error state to user
170
+ const appGrid = document . getElementById ( 'app-grid' ) ;
171
+ appGrid . innerHTML = `
172
+ <div class="col-span-full flex flex-col items-center justify-center py-12 text-center">
173
+ <i class="fas fa-exclamation-triangle text-red-500 text-4xl mb-4"></i>
174
+ <h3 class="text-lg font-medium text-gray-800 dark:text-gray-200 mb-2">
175
+ Failed to load applications
176
+ </h3>
177
+ <p class="text-gray-600 dark:text-gray-400 max-w-md">
178
+ Could not load application configuration. Please try refreshing the page.
179
+ </p>
180
+ <button onclick="window.location.reload()" class="mt-4 px-4 py-2 bg-cyan-500 text-white rounded-lg hover:bg-cyan-600 transition-colors">
181
+ <i class="fas fa-sync-alt mr-2"></i> Refresh
182
+ </button>
183
+ </div>
184
+ ` ;
185
+ }
186
+ }
187
+
188
+ // Initialize the application with proper error handling
189
+ async function initializeApplication ( ) {
190
+ try {
191
+ const apps = await loadApps ( ) ;
192
+ renderApps ( apps ) ;
193
+ setupCategoryFilter ( apps ) ;
194
+ } catch ( error ) {
195
+ console . error ( 'Application initialization failed:' , error ) ;
196
+ }
197
+ }
198
+
199
+ // Render apps to the grid with improved card design
200
+ function renderApps ( apps ) {
201
+ const appGrid = document . getElementById ( 'app-grid' ) ;
202
+
203
+ if ( ! apps || apps . length === 0 ) {
204
+ appGrid . innerHTML = `
205
+ <div class="col-span-full flex flex-col items-center justify-center py-12">
206
+ <i class="fas fa-folder-open text-gray-400 text-4xl mb-4"></i>
207
+ <p class="text-gray-600 dark:text-gray-400">No applications available</p>
208
+ </div>
209
+ ` ;
210
+ return ;
211
+ }
212
+
213
+ appGrid . innerHTML = '' ;
214
+
215
+ apps . forEach ( app => {
216
+ const card = document . createElement ( 'div' ) ;
217
+ card . className = `group bg-gradient-to-b from-white to-gray-50 dark:from-gray-700 dark:to-gray-800 rounded-xl shadow border hover:shadow-lg transition-all duration-300 dark:border-gray-700 cursor-pointer overflow-hidden` ;
218
+
219
+ card . innerHTML = `
220
+ <div class="relative h-full flex flex-col">
221
+ <div class="w-full h-32 bg-${ app . color } -100 dark:bg-${ app . color } -900 rounded-t-lg flex items-center justify-center transition-all duration-300 group-hover:opacity-90">
222
+ <i class="fas ${ app . icon } text-4xl text-${ app . color } -600 dark:text-${ app . color } -300"></i>
223
+ </div>
224
+ <div class="p-4 flex-1 flex flex-col">
225
+ <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-1">${ app . title } </h3>
226
+ <p class="text-sm text-gray-600 dark:text-gray-400 mb-3 flex-1">${ app . description } </p>
227
+ </div>
228
+ </div>
229
+ ` ;
230
+
231
+ card . addEventListener ( 'click' , async ( ) => {
232
+ if ( ! isConnected ) {
233
+ showAlert ( 'Please connect to a device first using USB, WiFi or Bluetooth' ) ;
183
234
return ;
184
235
}
185
- } catch ( error ) {
186
- console . error ( 'Error checking app status:' , error ) ;
236
+
237
+ // Add loading state to the clicked card
238
+ const originalContent = card . innerHTML ;
239
+ card . innerHTML = `
240
+ <div class="h-full flex items-center justify-center p-4">
241
+ <i class="fas fa-circle-notch fa-spin text-${ app . color } -500 text-xl mr-2"></i>
242
+ <span>Launching ${ app . title } ...</span>
243
+ </div>
244
+ ` ;
245
+
246
+ try {
247
+ const response = await fetch ( `/check_app_status/${ app . script } ` ) ;
248
+
249
+ if ( ! response . ok ) {
250
+ throw new Error ( 'Failed to check app status' ) ;
251
+ }
252
+
253
+ const data = await response . json ( ) ;
254
+
255
+ if ( data . status === 'running' ) {
256
+ showAlert ( `${ app . title } is already running!` ) ;
257
+ card . innerHTML = originalContent ;
258
+ return ;
259
+ }
260
+
261
+ await launchApplication ( app . script ) ;
262
+ card . innerHTML = originalContent ;
263
+ } catch ( error ) {
264
+ console . error ( 'Error launching app:' , error ) ;
265
+ showAlert ( `Failed to launch ${ app . title } : ${ error . message } ` ) ;
266
+ card . innerHTML = originalContent ;
267
+ }
268
+ } ) ;
269
+
270
+ appGrid . appendChild ( card ) ;
271
+ } ) ;
272
+ }
273
+
274
+ // Set up category filter with fixed options
275
+ function setupCategoryFilter ( apps ) {
276
+ const categorySelect = document . querySelector ( 'select' ) ;
277
+
278
+ const fixedCategories = [ 'All' , 'ECG' , 'EMG' , 'EOG' , 'EEG' , 'Tools' ] ; // Fixed filter options
279
+ categorySelect . innerHTML = '' ; // Clear existing options
280
+
281
+ // Add fixed options
282
+ fixedCategories . forEach ( category => {
283
+ const option = document . createElement ( 'option' ) ;
284
+ option . value = category ;
285
+ option . textContent = category ;
286
+
287
+ // Disable option if no apps exist for this category (except 'All')
288
+ if ( category !== 'All' ) {
289
+ const hasApps = apps . some ( app => app . category === category ) ;
290
+ option . disabled = ! hasApps ;
291
+ if ( ! hasApps ) {
292
+ option . textContent += ' (0)' ;
293
+ }
187
294
}
188
295
189
- // If not running, launch the app
190
- launchApplication ( app . script ) ;
296
+ categorySelect . appendChild ( option ) ;
297
+ } ) ;
298
+
299
+ // Add event listener for filtering
300
+ categorySelect . addEventListener ( 'change' , ( e ) => {
301
+ const selectedCategory = e . target . value ;
302
+ filterAppsByCategory ( selectedCategory , apps ) ;
191
303
} ) ;
304
+ }
305
+
306
+ // Filter apps by category with smooth transition
307
+ function filterAppsByCategory ( category , allApps ) {
308
+ const appGrid = document . getElementById ( 'app-grid' ) ;
192
309
193
- appGrid . appendChild ( card ) ;
194
- } ) ;
310
+ // Add fade-out effect
311
+ appGrid . style . opacity = '0.5' ;
312
+ appGrid . style . transition = 'opacity 0.3s ease' ;
313
+
314
+ setTimeout ( ( ) => {
315
+ const filteredApps = category === 'All' ?
316
+ allApps :
317
+ allApps . filter ( app => app . category === category ) ;
318
+
319
+ renderApps ( filteredApps ) ;
320
+
321
+ // Add fade-in effect
322
+ appGrid . style . opacity = '0' ;
323
+ setTimeout ( ( ) => {
324
+ appGrid . style . opacity = '1' ;
325
+ } , 10 ) ;
326
+ } , 300 ) ;
327
+ }
195
328
196
329
// Theme toggle
197
330
const themeToggle = document . getElementById ( 'theme-toggle' ) ;
@@ -270,7 +403,6 @@ <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-1">${app.ti
270
403
}
271
404
} else {
272
405
btn . classList . remove ( 'cursor-not-allowed' ) ;
273
- // Restore hover effects
274
406
btn . classList . add ( 'hover:bg-cyan-500' , 'hover:text-white' ) ;
275
407
btn . classList . remove ( 'bg-cyan-600' , 'dark:bg-cyan-700' ) ;
276
408
}
@@ -769,13 +901,18 @@ <h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-1">${app.ti
769
901
// Call it initially when the page loads
770
902
checkStreamStatus ( ) ;
771
903
904
+ // Initialize the app when DOM is loaded
905
+ document . addEventListener ( 'DOMContentLoaded' , ( ) => {
906
+ initializeApplication ( ) ;
907
+
772
908
document . getElementById ( 'github-btn' ) . addEventListener ( 'click' , ( ) => {
773
909
window . open ( 'https://github.com/upsidedownlabs/Chords-Python' , '_blank' ) ;
774
910
} ) ;
775
911
776
912
document . getElementById ( 'info-btn' ) . addEventListener ( 'click' , ( ) => {
777
913
alert ( 'Chords Python - Biopotential Data Acquisition System\nVersion 2.1.0' ) ;
778
914
} ) ;
915
+ } ) ;
779
916
</ script >
780
917
</ body >
781
918
</ html >
0 commit comments