Update definitions and readme
This commit is contained in:
parent
029c9bbf0e
commit
c10d8b5407
8 changed files with 7581 additions and 7755 deletions
56
README.md
56
README.md
|
|
@ -1,5 +1,55 @@
|
||||||
# Adafruit_Wippersnapper_Offline_Configurator
|
# Adafruit Wippersnapper Offline Configurator
|
||||||
A single web page for the creation / amending of config.json files to support Wippersnapper Offline Data Logger firmware
|
This web page is for the creation / amending of `config.json` files to support the free and open-source Adafruit "Wippersnapper" Offline Data Logger firmware.
|
||||||
|
|
||||||
|
It allows users to select their microcontroller board, automatically (or manully) setup the Real Time Clock (RTC) and an SD card Chip Select pin (uses default SPI bus), or any companion boards with SD cards and/or RTCs, and then the attached components (sensors, analog pins, etc) for data logging.
|
||||||
|
|
||||||
|
The page can also be used offline by including the javascript (.js) files, ideally minified (180k > 60k), and index.html (i.e. copy to your device or SD card)
|
||||||
|
|
||||||
|
|
||||||
Visit the site here:
|
Visit the site here:
|
||||||
### [https://tyeth.github.io/Adafruit_Wippersnapper_Offline_Configurator/](https://tyeth.github.io/Adafruit_Wippersnapper_Offline_Configurator/)
|
[https://adafruit.github.io/Adafruit_Wippersnapper_Offline_Configurator/](https://adafruit.github.io/Adafruit_Wippersnapper_Offline_Configurator/)
|
||||||
|
|
||||||
|
See this Learn Guide for more info on using Adafruit Wippersnapper Firmware (offline mode) as a Data Logger, which also has the **Supported Hardware** page:
|
||||||
|
[No-Code Offline Data Logger with WipperSnapper](https://learn.adafruit.com/no-code-offline-data-logging-with-wippersnapper/)
|
||||||
|
|
||||||
|
## Development
|
||||||
|
We gratefully accept pull-requests and issues (open-source ❤️) although the main [Wippersnapper repository](https://github.com/adafruit/Adafruit_Wippersnapper_Arduino/issues) (or [boards](https://github.com/adafruit/Wippersnapper_Boards) or [components](https://github.com/adafruit/Wippersnapper_Components)) is better suited for issues., as this is a stop-gap solution until the main Adafruit IO website performs the desired functionality (Wippersnapper v2), but it has proven useful so maybe will continue to do so.
|
||||||
|
|
||||||
|
If you wish to play with the website design / functionality then the main files to edit are:
|
||||||
|
* index.html
|
||||||
|
* load-wippersnapper-data.js
|
||||||
|
* wippersnapper-config-builder.js
|
||||||
|
|
||||||
|
The remaining files are involved in updating automatically generated board and component definitions.
|
||||||
|
|
||||||
|
If you wish to add companion boards then those are manually defined (search featherwing), but boards and components should be added to Wippersnapper to be picked up automatically.
|
||||||
|
If you wish to add RTCs, they must first be added to the offline firmware, and then we'll add (or a merge PR) the RTC to the web interface. The repositories are linked above.
|
||||||
|
|
||||||
|
To recreate the build process, which processes the boards+component definitions and fetches images + firmware versions, you'll need python installed (and pip) and then to install the requirements:
|
||||||
|
```shell
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
Then before running you'll need to initialise the submodules with git (or download the submodules and unzip manually)
|
||||||
|
```shell
|
||||||
|
git submodule update --init
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally run the convert script:
|
||||||
|
```shell
|
||||||
|
python ./convert_all_wippersnapper_definitions.py
|
||||||
|
```
|
||||||
|
|
||||||
|
And you should see output like this:
|
||||||
|
```
|
||||||
|
=== Conversion Complete ===
|
||||||
|
Converted 23 boards and 98 components
|
||||||
|
Time taken: 24.39 seconds
|
||||||
|
Output files:
|
||||||
|
- C:\dev\js\Adafruit_Wippersnapper_Offline_Configurator\wippersnapper_boards.json
|
||||||
|
- C:\dev\js\Adafruit_Wippersnapper_Offline_Configurator\wippersnapper_components.json
|
||||||
|
```
|
||||||
|
|
||||||
|
That will have replaced the following files:
|
||||||
|
wippersnapper_boards.js + .json
|
||||||
|
wippersnapper_components.js + .json
|
||||||
|
firmware-data.js
|
||||||
|
|
@ -10,20 +10,20 @@ def main():
|
||||||
"""
|
"""
|
||||||
print("=== Wippersnapper Definitions Converter ===")
|
print("=== Wippersnapper Definitions Converter ===")
|
||||||
print("Converting all Wippersnapper definitions to JSON...")
|
print("Converting all Wippersnapper definitions to JSON...")
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
# Convert boards
|
# Convert boards
|
||||||
print("\n--- Converting Boards ---")
|
print("\n--- Converting Boards ---")
|
||||||
boards = convert_boards_to_json()
|
boards = convert_boards_to_json()
|
||||||
|
|
||||||
# Convert components
|
# Convert components
|
||||||
print("\n--- Converting Components ---")
|
print("\n--- Converting Components ---")
|
||||||
components = convert_components_to_json()
|
components = convert_components_to_json()
|
||||||
|
|
||||||
# fetch latest release info and assets
|
# fetch latest release info and assets
|
||||||
release_info = fetch_latest_release_info_and_assets()
|
release_info = fetch_latest_release_info_and_assets()
|
||||||
|
|
||||||
# Print summary
|
# Print summary
|
||||||
elapsed_time = time.time() - start_time
|
elapsed_time = time.time() - start_time
|
||||||
print("\n=== Conversion Complete ===")
|
print("\n=== Conversion Complete ===")
|
||||||
|
|
@ -32,6 +32,7 @@ def main():
|
||||||
print(f"Output files:")
|
print(f"Output files:")
|
||||||
print(f" - {os.path.abspath(r'wippersnapper_boards.json')}")
|
print(f" - {os.path.abspath(r'wippersnapper_boards.json')}")
|
||||||
print(f" - {os.path.abspath(r'wippersnapper_components.json')}")
|
print(f" - {os.path.abspath(r'wippersnapper_components.json')}")
|
||||||
|
print(f" - {os.path.abspath(r'firmware-data.json')}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// Auto-generated on 2025-04-25 17:55:57
|
// Auto-generated on 2025-04-25 21:55:05
|
||||||
const FIRMWARE_DATA = {
|
const FIRMWARE_DATA = {
|
||||||
"releaseInfo": {
|
"releaseInfo": {
|
||||||
"version": "1.0.0-offline-beta.2",
|
"version": "1.0.0-offline-beta.2",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// Load Wippersnapper boards and components data
|
// Load Wippersnapper boards and components data
|
||||||
|
|
||||||
// Configuration
|
// Configuration - technically unused (instead ./ relative links) but useful for reference
|
||||||
const BOARDS_JSON_URL = 'https://raw.githubusercontent.com/tyeth/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/use_boards_sd_card/wippersnapper_boards.json'; //'wippersnapper_boards.json';
|
const BOARDS_JSON_URL = 'https://raw.githubusercontent.com/adafruit/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/use_boards_sd_card/wippersnapper_boards.json'; //'wippersnapper_boards.json';
|
||||||
const COMPONENTS_JSON_URL = 'https://raw.githubusercontent.com/tyeth/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/use_boards_sd_card/wippersnapper_components.json'; //'wippersnapper_components.json';
|
const COMPONENTS_JSON_URL = 'https://raw.githubusercontent.com/adafruit/Adafruit_Wippersnapper_Offline_Configurator/refs/heads/use_boards_sd_card/wippersnapper_components.json'; //'wippersnapper_components.json';
|
||||||
|
|
||||||
// Global app state
|
// Global app state
|
||||||
const appState = {
|
const appState = {
|
||||||
|
|
@ -11,7 +11,7 @@ const appState = {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
loadError: null,
|
loadError: null,
|
||||||
enableautoConfig: false,
|
enableautoConfig: false,
|
||||||
|
|
||||||
// Application state properties (from original code)
|
// Application state properties (from original code)
|
||||||
selectedBoard: null,
|
selectedBoard: null,
|
||||||
companionBoard: null,
|
companionBoard: null,
|
||||||
|
|
@ -60,7 +60,7 @@ async function loadWippersnapperData() {
|
||||||
appState.componentsData = componentsData.components;
|
appState.componentsData = componentsData.components;
|
||||||
document.body.removeChild(componentsObject);
|
document.body.removeChild(componentsObject);
|
||||||
document.body.removeChild(jsonObject);
|
document.body.removeChild(jsonObject);
|
||||||
|
|
||||||
// Add I2C multiplexer components manually since they're not in the JSON data
|
// Add I2C multiplexer components manually since they're not in the JSON data
|
||||||
if (appState.componentsData.i2c) {
|
if (appState.componentsData.i2c) {
|
||||||
// Add PCA9546 - 4-channel I2C multiplexer
|
// Add PCA9546 - 4-channel I2C multiplexer
|
||||||
|
|
@ -76,7 +76,7 @@ async function loadWippersnapperData() {
|
||||||
dataTypes: [],
|
dataTypes: [],
|
||||||
channels: 4
|
channels: 4
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add PCA9548 - 8-channel I2C multiplexer
|
// Add PCA9548 - 8-channel I2C multiplexer
|
||||||
appState.componentsData.i2c.push({
|
appState.componentsData.i2c.push({
|
||||||
id: 'pca9548',
|
id: 'pca9548',
|
||||||
|
|
@ -90,7 +90,7 @@ async function loadWippersnapperData() {
|
||||||
dataTypes: [],
|
dataTypes: [],
|
||||||
channels: 8
|
channels: 8
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add TCA9546 - 4-channel I2C multiplexer
|
// Add TCA9546 - 4-channel I2C multiplexer
|
||||||
appState.componentsData.i2c.push({
|
appState.componentsData.i2c.push({
|
||||||
id: 'tca9546',
|
id: 'tca9546',
|
||||||
|
|
@ -104,7 +104,7 @@ async function loadWippersnapperData() {
|
||||||
dataTypes: [],
|
dataTypes: [],
|
||||||
channels: 4
|
channels: 4
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add TCA9548 - 8-channel I2C multiplexer
|
// Add TCA9548 - 8-channel I2C multiplexer
|
||||||
appState.componentsData.i2c.push({
|
appState.componentsData.i2c.push({
|
||||||
id: 'tca9548',
|
id: 'tca9548',
|
||||||
|
|
@ -119,17 +119,17 @@ async function loadWippersnapperData() {
|
||||||
channels: 8
|
channels: 8
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the UI with the data
|
// Initialize the UI with the data
|
||||||
initializeUI();
|
initializeUI();
|
||||||
|
|
||||||
console.log('Successfully loaded Wippersnapper data', {
|
console.log('Successfully loaded Wippersnapper data', {
|
||||||
boards: Object.keys(appState.boardsData).length,
|
boards: Object.keys(appState.boardsData).length,
|
||||||
components: Object.keys(appState.componentsData)
|
components: Object.keys(appState.componentsData)
|
||||||
.filter(key => !key.endsWith('_metadata'))
|
.filter(key => !key.endsWith('_metadata'))
|
||||||
.reduce((acc, key) => acc + appState.componentsData[key].length, 0)
|
.reduce((acc, key) => acc + appState.componentsData[key].length, 0)
|
||||||
});
|
});
|
||||||
|
|
||||||
appState.isLoading = false;
|
appState.isLoading = false;
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -147,7 +147,7 @@ async function loadWippersnapperData() {
|
||||||
function initializeUI() {
|
function initializeUI() {
|
||||||
// Populate board select dropdown
|
// Populate board select dropdown
|
||||||
populateBoardSelect();
|
populateBoardSelect();
|
||||||
|
|
||||||
// Set up event listeners
|
// Set up event listeners
|
||||||
attachEventListeners();
|
attachEventListeners();
|
||||||
}
|
}
|
||||||
|
|
@ -158,11 +158,11 @@ function initializeUI() {
|
||||||
function populateBoardSelect() {
|
function populateBoardSelect() {
|
||||||
const boardSelect = document.getElementById('board-select');
|
const boardSelect = document.getElementById('board-select');
|
||||||
boardSelect.innerHTML = '<option value="">-- Select a Board --</option>';
|
boardSelect.innerHTML = '<option value="">-- Select a Board --</option>';
|
||||||
|
|
||||||
// Filter boards to only include those with UF2 install method
|
// Filter boards to only include those with UF2 install method
|
||||||
const filteredBoards = Object.entries(appState.boardsData)
|
const filteredBoards = Object.entries(appState.boardsData)
|
||||||
.filter(([boardId, board]) => board.installMethod === 'uf2'); //['uf2', 'web-native-usb'].includes(board.installMethod)); //funhouse
|
.filter(([boardId, board]) => board.installMethod === 'uf2'); //['uf2', 'web-native-usb'].includes(board.installMethod)); //funhouse
|
||||||
|
|
||||||
// Sort boards by vendor and name
|
// Sort boards by vendor and name
|
||||||
const sortedBoards = filteredBoards
|
const sortedBoards = filteredBoards
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
|
|
@ -176,11 +176,11 @@ function populateBoardSelect() {
|
||||||
// Sort by vendor first
|
// Sort by vendor first
|
||||||
return vendorA.localeCompare(vendorB);
|
return vendorA.localeCompare(vendorB);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then by display name
|
// Then by display name
|
||||||
return a[1].displayName.localeCompare(b[1].displayName);
|
return a[1].displayName.localeCompare(b[1].displayName);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Group boards by vendor
|
// Group boards by vendor
|
||||||
const boardsByVendor = {};
|
const boardsByVendor = {};
|
||||||
sortedBoards.forEach(([boardId, board]) => {
|
sortedBoards.forEach(([boardId, board]) => {
|
||||||
|
|
@ -190,19 +190,19 @@ function populateBoardSelect() {
|
||||||
}
|
}
|
||||||
boardsByVendor[vendor].push([boardId, board]);
|
boardsByVendor[vendor].push([boardId, board]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add boards to select, grouped by vendor
|
// Add boards to select, grouped by vendor
|
||||||
Object.entries(boardsByVendor).forEach(([vendor, boards]) => {
|
Object.entries(boardsByVendor).forEach(([vendor, boards]) => {
|
||||||
const optgroup = document.createElement('optgroup');
|
const optgroup = document.createElement('optgroup');
|
||||||
optgroup.label = vendor;
|
optgroup.label = vendor;
|
||||||
|
|
||||||
boards.forEach(([boardId, board]) => {
|
boards.forEach(([boardId, board]) => {
|
||||||
const option = document.createElement('option');
|
const option = document.createElement('option');
|
||||||
option.value = boardId;
|
option.value = boardId;
|
||||||
option.textContent = board.displayName;
|
option.textContent = board.displayName;
|
||||||
optgroup.appendChild(option);
|
optgroup.appendChild(option);
|
||||||
});
|
});
|
||||||
|
|
||||||
boardSelect.appendChild(optgroup);
|
boardSelect.appendChild(optgroup);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -215,10 +215,10 @@ function populateBoardSelect() {
|
||||||
function convertBoardDataToConfig(boardId) {
|
function convertBoardDataToConfig(boardId) {
|
||||||
const boardData = appState.boardsData[boardId];
|
const boardData = appState.boardsData[boardId];
|
||||||
if (!boardData) return null;
|
if (!boardData) return null;
|
||||||
|
|
||||||
// Extract pin numbers from board data
|
// Extract pin numbers from board data
|
||||||
const pins = boardData.pins.map(pin => pin.number).filter(num => !isNaN(num));
|
const pins = boardData.pins.map(pin => pin.number).filter(num => !isNaN(num));
|
||||||
|
|
||||||
boardConfig = boardData;
|
boardConfig = boardData;
|
||||||
// Create board config
|
// Create board config
|
||||||
boardConfig.totalAnalogPins= boardData.totalAnalogPins || 0;
|
boardConfig.totalAnalogPins= boardData.totalAnalogPins || 0;
|
||||||
|
|
@ -227,7 +227,7 @@ function convertBoardDataToConfig(boardId) {
|
||||||
SDA: boardData.defaultI2C.SDA
|
SDA: boardData.defaultI2C.SDA
|
||||||
};
|
};
|
||||||
boardConfig.pins= pins;
|
boardConfig.pins= pins;
|
||||||
|
|
||||||
return boardConfig;
|
return boardConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,7 +245,7 @@ function convertComponentsDataToConfig() {
|
||||||
servo: [],
|
servo: [],
|
||||||
uart: []
|
uart: []
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process I2C components
|
// Process I2C components
|
||||||
if (appState.componentsData.i2c) {
|
if (appState.componentsData.i2c) {
|
||||||
appState.componentsData.i2c.forEach(component => {
|
appState.componentsData.i2c.forEach(component => {
|
||||||
|
|
@ -260,7 +260,7 @@ function convertComponentsDataToConfig() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process DS18x20 components
|
// Process DS18x20 components
|
||||||
if (appState.componentsData.ds18x20) {
|
if (appState.componentsData.ds18x20) {
|
||||||
appState.componentsData.ds18x20.forEach(component => {
|
appState.componentsData.ds18x20.forEach(component => {
|
||||||
|
|
@ -272,7 +272,7 @@ function convertComponentsDataToConfig() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process Pin components
|
// Process Pin components
|
||||||
if (appState.componentsData.pin) {
|
if (appState.componentsData.pin) {
|
||||||
appState.componentsData.pin.forEach(component => {
|
appState.componentsData.pin.forEach(component => {
|
||||||
|
|
@ -284,7 +284,7 @@ function convertComponentsDataToConfig() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process Pixel components
|
// Process Pixel components
|
||||||
if (appState.componentsData.pixel) {
|
if (appState.componentsData.pixel) {
|
||||||
appState.componentsData.pixel.forEach(component => {
|
appState.componentsData.pixel.forEach(component => {
|
||||||
|
|
@ -296,7 +296,7 @@ function convertComponentsDataToConfig() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process PWM components
|
// Process PWM components
|
||||||
if (appState.componentsData.pwm) {
|
if (appState.componentsData.pwm) {
|
||||||
appState.componentsData.pwm.forEach(component => {
|
appState.componentsData.pwm.forEach(component => {
|
||||||
|
|
@ -308,7 +308,7 @@ function convertComponentsDataToConfig() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process Servo components
|
// Process Servo components
|
||||||
if (appState.componentsData.servo) {
|
if (appState.componentsData.servo) {
|
||||||
appState.componentsData.servo.forEach(component => {
|
appState.componentsData.servo.forEach(component => {
|
||||||
|
|
@ -320,7 +320,7 @@ function convertComponentsDataToConfig() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process UART components
|
// Process UART components
|
||||||
if (appState.componentsData.uart) {
|
if (appState.componentsData.uart) {
|
||||||
appState.componentsData.uart.forEach(component => {
|
appState.componentsData.uart.forEach(component => {
|
||||||
|
|
@ -332,7 +332,7 @@ function convertComponentsDataToConfig() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return componentsConfig;
|
return componentsConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -343,17 +343,17 @@ function attachEventListeners() {
|
||||||
// BOARD SELECTION HANDLER HAS BEEN REMOVED
|
// BOARD SELECTION HANDLER HAS BEEN REMOVED
|
||||||
// The duplicate event handler from load-wippersnapper-data.js has been removed
|
// The duplicate event handler from load-wippersnapper-data.js has been removed
|
||||||
// to prevent conflicts with the handler in wippersnapper-config-builder.js
|
// to prevent conflicts with the handler in wippersnapper-config-builder.js
|
||||||
|
|
||||||
// Instead, we'll prepare the data in the format expected by wippersnapper-config-builder.js
|
// Instead, we'll prepare the data in the format expected by wippersnapper-config-builder.js
|
||||||
console.log('Data loading complete, board selection handler is in wippersnapper-config-builder.js');
|
console.log('Data loading complete, board selection handler is in wippersnapper-config-builder.js');
|
||||||
|
|
||||||
// Convert component data to config format
|
// Convert component data to config format
|
||||||
const componentsConfig = convertComponentsDataToConfig();
|
const componentsConfig = convertComponentsDataToConfig();
|
||||||
console.log('not using Components data converted to config format:', componentsConfig);
|
console.log('not using Components data converted to config format:', componentsConfig);
|
||||||
// Update the components data in appState with the converted format
|
// Update the components data in appState with the converted format
|
||||||
// so it's ready for use in the other script
|
// so it's ready for use in the other script
|
||||||
// appState.componentsData = componentsConfig;
|
// appState.componentsData = componentsConfig;
|
||||||
|
|
||||||
// No other event listeners needed here as they are handled in wippersnapper-config-builder.js
|
// No other event listeners needed here as they are handled in wippersnapper-config-builder.js
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -364,7 +364,7 @@ function attachEventListeners() {
|
||||||
function showLoadError(message) {
|
function showLoadError(message) {
|
||||||
// Create or update an error message element
|
// Create or update an error message element
|
||||||
let errorElem = document.getElementById('load-error');
|
let errorElem = document.getElementById('load-error');
|
||||||
|
|
||||||
if (!errorElem) {
|
if (!errorElem) {
|
||||||
errorElem = document.createElement('div');
|
errorElem = document.createElement('div');
|
||||||
errorElem.id = 'load-error';
|
errorElem.id = 'load-error';
|
||||||
|
|
@ -373,11 +373,11 @@ function showLoadError(message) {
|
||||||
errorElem.style.padding = '15px';
|
errorElem.style.padding = '15px';
|
||||||
errorElem.style.margin = '15px 0';
|
errorElem.style.margin = '15px 0';
|
||||||
errorElem.style.borderRadius = '5px';
|
errorElem.style.borderRadius = '5px';
|
||||||
|
|
||||||
// Insert at the top of the body
|
// Insert at the top of the body
|
||||||
document.body.insertBefore(errorElem, document.body.firstChild);
|
document.body.insertBefore(errorElem, document.body.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
errorElem.innerHTML = `
|
errorElem.innerHTML = `
|
||||||
<h3>Error Loading Data</h3>
|
<h3>Error Loading Data</h3>
|
||||||
<p>${message}</p>
|
<p>${message}</p>
|
||||||
|
|
@ -395,7 +395,7 @@ function retryLoading() {
|
||||||
if (errorElem) {
|
if (errorElem) {
|
||||||
errorElem.remove();
|
errorElem.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try loading again
|
// Try loading again
|
||||||
loadWippersnapperData();
|
loadWippersnapperData();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue