lobsterboard 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- /* LobsterBoard v0.6.2 - Dashboard Styles */
1
+ /* LobsterBoard v0.6.3 - Dashboard Styles */
2
2
  /* LobsterBoard Dashboard - Generated Styles */
3
3
 
4
4
  :root {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.6.2
2
+ * LobsterBoard v0.6.3
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.6.2
2
+ * LobsterBoard v0.6.3
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.6.2
2
+ * LobsterBoard v0.6.3
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * LobsterBoard v0.6.2
2
+ * LobsterBoard v0.6.3
3
3
  * Dashboard builder with customizable widgets
4
4
  * https://github.com/curbob/LobsterBoard
5
5
  * @license MIT
package/js/widgets.js CHANGED
@@ -332,29 +332,35 @@ const WIDGETS = {
332
332
  </div>
333
333
  </div>`,
334
334
  generateJs: (props) => `
335
- // Weather Widget: ${props.id} (uses free wttr.in API - no key needed)
335
+ // Weather Widget: ${props.id} (uses free Open-Meteo API - no key needed)
336
+ const WMO_DESC = {0:'Clear sky',1:'Mainly clear',2:'Partly cloudy',3:'Overcast',45:'Fog',48:'Rime fog',51:'Light drizzle',53:'Drizzle',55:'Dense drizzle',61:'Slight rain',63:'Moderate rain',65:'Heavy rain',71:'Slight snow',73:'Moderate snow',75:'Heavy snow',80:'Slight showers',81:'Moderate showers',82:'Violent showers',95:'Thunderstorm',96:'Hail thunderstorm',99:'Heavy hail'};
337
+ function wmoIcon(code) {
338
+ if (code <= 1) return 'weather-sunny';
339
+ if (code <= 3) return 'weather-cloudy';
340
+ if (code >= 51 && code <= 82) return 'weather-rainy';
341
+ if (code >= 71 && code <= 77) return 'weather-snowy';
342
+ if (code >= 95) return 'weather-rainy';
343
+ return 'weather';
344
+ }
336
345
  async function update_${props.id.replace(/-/g, '_')}() {
337
346
  const valEl = document.getElementById('${props.id}-value');
338
347
  const labelEl = document.getElementById('${props.id}-label');
339
348
  const iconEl = document.getElementById('${props.id}-icon');
340
349
  try {
341
- const location = encodeURIComponent('${props.location || 'Atlanta'}');
342
- const res = await fetch('https://wttr.in/' + location + '?format=j1');
350
+ const loc = '${props.location || 'Atlanta'}';
351
+ const geoRes = await fetch('https://geocoding-api.open-meteo.com/v1/search?name=' + encodeURIComponent(loc) + '&count=1');
352
+ const geoData = await geoRes.json();
353
+ if (!geoData.results || !geoData.results.length) throw new Error('City not found');
354
+ const {latitude, longitude} = geoData.results[0];
355
+ const tempUnit = '${props.units}' === 'C' ? 'celsius' : 'fahrenheit';
356
+ const res = await fetch('https://api.open-meteo.com/v1/forecast?latitude=' + latitude + '&longitude=' + longitude + '&current=temperature_2m,weathercode,windspeed_10m&temperature_unit=' + tempUnit);
343
357
  const data = await res.json();
344
- const current = data.current_condition[0];
345
- const temp = '${props.units}' === 'C' ? current.temp_C : current.temp_F;
358
+ const c = data.current;
346
359
  const unit = '${props.units}' === 'C' ? '°C' : '°F';
347
- valEl.textContent = temp + unit;
348
- labelEl.textContent = current.weatherDesc[0].value;
349
- // Update icon based on condition
350
- const code = parseInt(current.weatherCode);
351
- let iconId = 'weather';
352
- if (code === 113) iconId = 'weather-sunny';
353
- else if (code === 116 || code === 119) iconId = 'weather-cloudy';
354
- else if (code >= 176 && code <= 359) iconId = 'weather-rainy';
355
- else if (code >= 368 && code <= 395) iconId = 'weather-snowy';
360
+ valEl.textContent = Math.round(c.temperature_2m) + unit;
361
+ labelEl.textContent = WMO_DESC[c.weathercode] || 'Unknown';
362
+ const iconId = wmoIcon(c.weathercode);
356
363
  iconEl.setAttribute('data-icon', iconId);
357
- // Update emoji fallback for non-themed views
358
364
  const icons = window.WIDGET_ICONS || {};
359
365
  iconEl.textContent = icons[iconId] ? icons[iconId].emoji : '🌡️';
360
366
  } catch (e) {
@@ -399,35 +405,42 @@ const WIDGETS = {
399
405
  </div>
400
406
  </div>`,
401
407
  generateJs: (props) => `
402
- // Multi Weather Widget: ${props.id} (uses free wttr.in API - no key needed)
408
+ // Multi Weather Widget: ${props.id} (uses free Open-Meteo API - no key needed)
409
+ const WMO_DESC2 = {0:'Clear',1:'Clear',2:'Partly cloudy',3:'Overcast',45:'Fog',48:'Rime fog',51:'Drizzle',53:'Drizzle',55:'Drizzle',61:'Rain',63:'Rain',65:'Heavy rain',71:'Snow',73:'Snow',75:'Heavy snow',80:'Showers',81:'Showers',82:'Showers',95:'Storm',96:'Hail',99:'Hail'};
410
+ function wmoIcon2(code) {
411
+ if (code <= 1) return 'weather-sunny';
412
+ if (code <= 3) return 'weather-cloudy';
413
+ if (code >= 51 && code <= 82) return 'weather-rainy';
414
+ if (code >= 71 && code <= 77) return 'weather-snowy';
415
+ if (code >= 95) return 'weather-rainy';
416
+ return 'weather';
417
+ }
403
418
  async function update_${props.id.replace(/-/g, '_')}() {
404
419
  const locations = '${props.locations || 'New York; London; Tokyo'}'.split(';').map(l => l.trim());
405
420
  const container = document.getElementById('${props.id}-list');
406
- const unit = '${props.units}' === 'C' ? 'C' : 'F';
407
- const unitSymbol = unit === 'C' ? '°C' : '°F';
421
+ const tempUnit = '${props.units}' === 'C' ? 'celsius' : 'fahrenheit';
422
+ const unitSymbol = '${props.units}' === 'C' ? '°C' : '°F';
408
423
 
409
424
  const results = await Promise.all(locations.map(async (loc) => {
410
425
  try {
411
- const res = await fetch('https://wttr.in/' + encodeURIComponent(loc) + '?format=j1');
426
+ const geoRes = await fetch('https://geocoding-api.open-meteo.com/v1/search?name=' + encodeURIComponent(loc) + '&count=1');
427
+ const geoData = await geoRes.json();
428
+ if (!geoData.results || !geoData.results.length) return { loc, temp: 'N/A', iconId: 'weather', emoji: '❓' };
429
+ const {latitude, longitude} = geoData.results[0];
430
+ const res = await fetch('https://api.open-meteo.com/v1/forecast?latitude=' + latitude + '&longitude=' + longitude + '&current=temperature_2m,weathercode&temperature_unit=' + tempUnit);
412
431
  const data = await res.json();
413
- const current = data.current_condition[0];
414
- const temp = unit === 'C' ? current.temp_C : current.temp_F;
415
- const code = parseInt(current.weatherCode);
416
- let iconId = 'weather';
417
- if (code === 113) iconId = 'weather-sunny';
418
- else if (code === 116 || code === 119) iconId = 'weather-cloudy';
419
- else if (code >= 176 && code <= 359) iconId = 'weather-rainy';
420
- else if (code >= 368 && code <= 395) iconId = 'weather-snowy';
432
+ const c = data.current;
433
+ const iconId = wmoIcon2(c.weathercode);
421
434
  const icons = window.WIDGET_ICONS || {};
422
435
  const emoji = icons[iconId] ? icons[iconId].emoji : '🌡️';
423
- return { loc, temp, iconId, emoji, desc: current.weatherDesc[0].value };
436
+ return { loc, temp: Math.round(c.temperature_2m), iconId, emoji };
424
437
  } catch (e) {
425
- return { loc, temp: 'N/A', iconId: 'weather', emoji: '❓', desc: 'Error' };
438
+ return { loc, temp: 'N/A', iconId: 'weather', emoji: '❓' };
426
439
  }
427
440
  }));
428
441
 
429
442
  container.innerHTML = results.map(r =>
430
- '<div class="weather-row"><span class="weather-icon lb-icon" data-icon="' + _esc(r.iconId) + '">' + _esc(r.emoji) + '</span><span class="weather-loc">' + _esc(r.loc) + '</span><span class="weather-temp">' + _esc(r.temp) + _esc(unitSymbol) + '</span></div>'
443
+ '<div class="weather-row"><span class="weather-icon lb-icon" data-icon="' + _esc(r.iconId) + '">' + _esc(r.emoji) + '</span><span class="weather-loc">' + _esc(r.loc) + '</span><span class="weather-temp">' + _esc(String(r.temp)) + _esc(unitSymbol) + '</span></div>'
431
444
  ).join('');
432
445
  }
433
446
  update_${props.id.replace(/-/g, '_')}();
@@ -3728,32 +3741,34 @@ const WIDGETS = {
3728
3741
  </div>
3729
3742
  </div>`,
3730
3743
  generateJs: (props) => `
3731
- // World Clock Widget: ${props.id} (uses wttr.in for timezone data)
3744
+ // World Clock Widget: ${props.id} (pure Intl.DateTimeFormat - no API needed)
3745
+ const CITY_TZ_MAP = {
3746
+ 'New York': 'America/New_York', 'Los Angeles': 'America/Los_Angeles', 'Chicago': 'America/Chicago',
3747
+ 'London': 'Europe/London', 'Paris': 'Europe/Paris', 'Berlin': 'Europe/Berlin',
3748
+ 'Tokyo': 'Asia/Tokyo', 'Sydney': 'Australia/Sydney', 'Dubai': 'Asia/Dubai',
3749
+ 'Singapore': 'Asia/Singapore', 'Hong Kong': 'Asia/Hong_Kong', 'Mumbai': 'Asia/Kolkata',
3750
+ 'Shanghai': 'Asia/Shanghai', 'Seoul': 'Asia/Seoul', 'Moscow': 'Europe/Moscow',
3751
+ 'Istanbul': 'Europe/Istanbul', 'Bangkok': 'Asia/Bangkok', 'Toronto': 'America/Toronto',
3752
+ 'Heidenheim': 'Europe/Berlin', 'Vienna': 'Europe/Vienna', 'Zurich': 'Europe/Zurich',
3753
+ 'Amsterdam': 'Europe/Amsterdam', 'Rome': 'Europe/Rome', 'Madrid': 'Europe/Madrid',
3754
+ 'São Paulo': 'America/Sao_Paulo', 'Mexico City': 'America/Mexico_City',
3755
+ 'Graz': 'Europe/Vienna', 'Munich': 'Europe/Berlin', 'Frankfurt': 'Europe/Berlin',
3756
+ 'Santiago': 'America/Santiago', 'Lima': 'America/Lima'
3757
+ };
3732
3758
  const locs_${props.id.replace(/-/g, '_')} = '${props.locations || 'New York; London; Tokyo'}'.split(';').map(s => s.trim());
3733
3759
  const hour12_${props.id.replace(/-/g, '_')} = ${!props.format24h};
3734
3760
 
3735
- async function update_${props.id.replace(/-/g, '_')}() {
3761
+ function update_${props.id.replace(/-/g, '_')}() {
3736
3762
  const container = document.getElementById('${props.id}-clocks');
3737
- const results = await Promise.all(locs_${props.id.replace(/-/g, '_')}.map(async (loc) => {
3763
+ const now = new Date();
3764
+ const results = locs_${props.id.replace(/-/g, '_')}.map(loc => {
3765
+ const tz = CITY_TZ_MAP[loc] || CITY_TZ_MAP[Object.keys(CITY_TZ_MAP).find(k => k.toLowerCase() === loc.toLowerCase())] || null;
3766
+ if (!tz) return { city: loc, time: '(unknown tz)' };
3738
3767
  try {
3739
- const res = await fetch('https://wttr.in/' + encodeURIComponent(loc) + '?format=j1');
3740
- const data = await res.json();
3741
- const area = data.nearest_area[0];
3742
- const city = area.areaName[0].value;
3743
- const localTime = data.current_condition[0].localObsDateTime;
3744
- // Parse the time from format "2026-02-07 12:30 AM"
3745
- const timePart = localTime.split(' ').slice(1).join(' ');
3746
- let displayTime = timePart;
3747
- if (!hour12_${props.id.replace(/-/g, '_')}) {
3748
- // Convert to 24h if needed
3749
- const d = new Date('2000-01-01 ' + timePart);
3750
- displayTime = d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' });
3751
- }
3752
- return { city, time: displayTime, ok: true };
3753
- } catch (e) {
3754
- return { city: loc, time: '—', ok: false };
3755
- }
3756
- }));
3768
+ const fmt = new Intl.DateTimeFormat('en-GB', { timeZone: tz, hour: '2-digit', minute: '2-digit', hour12: hour12_${props.id.replace(/-/g, '_')} });
3769
+ return { city: loc, time: fmt.format(now) };
3770
+ } catch(e) { return { city: loc, time: '—' }; }
3771
+ });
3757
3772
  container.innerHTML = results.map(r =>
3758
3773
  '<div class="tz-row"><span class="tz-city">' + r.city + '</span><span class="tz-time">' + r.time + '</span></div>'
3759
3774
  ).join('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lobsterboard",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "Self-hosted drag-and-drop dashboard builder with 50 widgets, template gallery, and custom pages. Works standalone or with OpenClaw.",
5
5
  "keywords": [
6
6
  "dashboard",