mirror of
https://github.com/usetrmnl/trmnl-firefox.git
synced 2026-08-13 23:18:13 -07:00
Merge pull request #1 from usetrmnl/catchup_to_chrome
Parity with Chrome extension
This commit is contained in:
Binary file not shown.
@@ -6,7 +6,7 @@ A Firefox-only browser extension that displays images from your TRMNL device in
|
|||||||
|
|
||||||
This extension brings TRMNL's calm, distraction-free environment directly to your Firefox new tab page. It connects to your TRMNL account and displays the current screen from your selected device, automatically refreshing at configurable intervals.
|
This extension brings TRMNL's calm, distraction-free environment directly to your Firefox new tab page. It connects to your TRMNL account and displays the current screen from your selected device, automatically refreshing at configurable intervals.
|
||||||
|
|
||||||
**Note**: This extension is specifically designed for Firefox and uses Firefox-native APIs. It will not work in Chrome or other browsers.
|
**Note**: This extension was originally designed for Chrome but adopts [these changes](https://github.com/usetrmnl/trmnl-chrome/pull/5) to work with Firefox-native APIs.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -78,26 +78,10 @@ This will create `trmnl-firefox.xpi` which can be installed in Firefox.
|
|||||||
- Use the "Logout" button in the bottom overlay of any new tab page
|
- Use the "Logout" button in the bottom overlay of any new tab page
|
||||||
- **Developer Mode**: Use the Firefox Developer Tools to access the TRMNL panel for environment switching
|
- **Developer Mode**: Use the Firefox Developer Tools to access the TRMNL panel for environment switching
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
trmnl-firefox/
|
|
||||||
├── code/ # Extension source code
|
|
||||||
│ ├── manifest.json # Firefox extension configuration
|
|
||||||
│ ├── newtab.html/js # New tab page implementation
|
|
||||||
│ ├── popup.html/js # Settings popup
|
|
||||||
│ ├── background.js # Background script for API calls
|
|
||||||
│ ├── devtools.html/js # Developer tools integration
|
|
||||||
│ ├── panel.html/js # DevTools panel
|
|
||||||
│ └── styles.css # Styling
|
|
||||||
├── pack.rb # Ruby script to package extension
|
|
||||||
└── README.md # This file
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
The extension uses:
|
The extension uses:
|
||||||
- Firefox WebExtensions APIs (Manifest V2)
|
- Firefox WebExtensions APIs (Manifest V3)
|
||||||
- Vanilla JavaScript
|
- Vanilla JavaScript
|
||||||
- Native Firefox storage and messaging APIs
|
- Native Firefox storage and messaging APIs
|
||||||
- CSS with dark mode support
|
- CSS with dark mode support
|
||||||
@@ -108,4 +92,4 @@ Pull requests are welcome. Please ensure all changes maintain Firefox compatibil
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[MIT](https://choosealicense.com/licenses/mit/)
|
[MIT](https://choosealicense.com/licenses/mit/)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
-110
@@ -1,110 +0,0 @@
|
|||||||
# TRMNL Firefox Extension
|
|
||||||
|
|
||||||
A Firefox extension that displays images from TRMNL's API in your new tab page with automatic refresh functionality. **Note**: requires a TRMNL account with a physical device or [BYOD](https://shop.usetrmnl.com/products/byod) license.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Displays TRMNL images in new tab pages
|
|
||||||
- Automatic image refresh with configurable intervals
|
|
||||||
- Device selection for users with multiple TRMNL devices
|
|
||||||
- Automatic login flow - no manual API key entry required
|
|
||||||
- Developer tools panel for environment switching
|
|
||||||
- Offline-friendly caching
|
|
||||||
- Logout & reset functionality to clear all extension data
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### From Source (Development)
|
|
||||||
|
|
||||||
1. Clone this repository:
|
|
||||||
```bash
|
|
||||||
git clone git@github.com:usetrmnl/trmnl-firefox.git
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Open Firefox and navigate to `about:debugging`
|
|
||||||
|
|
||||||
3. Click "This Firefox" in the left sidebar
|
|
||||||
|
|
||||||
4. Click "Load Temporary Add-on..." and select the `manifest.json` file from the `trmnl-firefox/code` directory
|
|
||||||
|
|
||||||
### Production Installation
|
|
||||||
|
|
||||||
This extension is designed for Firefox and uses Firefox-specific APIs. For production use, the extension would need to be signed by Mozilla and distributed through Firefox Add-ons.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
1. After installation, open a new tab or click the TRMNL extension icon
|
|
||||||
|
|
||||||
2. Click "Login to TRMNL" to open the TRMNL website in a new tab
|
|
||||||
|
|
||||||
3. Complete your login on the TRMNL website
|
|
||||||
|
|
||||||
4. The extension will automatically detect your login and fetch your devices
|
|
||||||
|
|
||||||
5. Your TRMNL device screen will appear in new tabs automatically
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### First Time Setup
|
|
||||||
1. **Login**: Click "Login to TRMNL" when prompted
|
|
||||||
2. **Authenticate**: Complete login on the TRMNL website
|
|
||||||
3. **Automatic Setup**: Extension automatically fetches your devices and API keys
|
|
||||||
|
|
||||||
### Daily Use
|
|
||||||
- **New Tab**: Open a new tab to see the current TRMNL image
|
|
||||||
- **Settings**: Click the TRMNL toolbar icon to access device settings and refresh options
|
|
||||||
- **Device Selection**: Choose between multiple devices if you have them
|
|
||||||
- **Manual Refresh**: Use the "Refresh Now" button in settings or on the new tab page
|
|
||||||
|
|
||||||
### Advanced Options
|
|
||||||
- **Manual API Key**: Advanced users can still enter API keys manually via the settings
|
|
||||||
- **Logout**: Clear all extension data and reset to initial state:
|
|
||||||
- Click the TRMNL toolbar icon and use the "Logout & Reset" button, or
|
|
||||||
- Use the "Logout" button in the bottom overlay of any new tab page
|
|
||||||
- **Developer Mode**: Use the Firefox Developer Tools to access the TRMNL panel for environment switching
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
The extension is built specifically for Firefox using:
|
|
||||||
- Vanilla JavaScript with Firefox WebExtensions APIs
|
|
||||||
- HTML/CSS for UI components
|
|
||||||
- Native Firefox storage and messaging APIs
|
|
||||||
- TRMNL API integration
|
|
||||||
|
|
||||||
### Key Files
|
|
||||||
|
|
||||||
- `manifest.json` - Firefox extension configuration (Manifest V2)
|
|
||||||
- `newtab.js/.html` - New tab page implementation
|
|
||||||
- `popup.js/.html` - Settings popup accessible from toolbar
|
|
||||||
- `background.js` - Background script for API calls, login flow, and data management
|
|
||||||
- `dashboard-content.js` - Content script for TRMNL website integration and login detection
|
|
||||||
- `devtools.js/.html` - Developer tools panel integration
|
|
||||||
- `panel.js/.html` - Developer tools panel implementation
|
|
||||||
- `styles.css` - Styling for new tab and popup interfaces
|
|
||||||
|
|
||||||
### Firefox-Specific Features
|
|
||||||
|
|
||||||
- Uses `browser.*` APIs natively (no polyfill required)
|
|
||||||
- Leverages Firefox's `chrome_url_overrides` for new tab functionality
|
|
||||||
- Integrated with Firefox Developer Tools
|
|
||||||
- Uses Firefox's persistent background script model
|
|
||||||
- Native Firefox storage management with automatic logout detection
|
|
||||||
- Automatic login detection via content scripts across TRMNL domains
|
|
||||||
|
|
||||||
### Login Flow Implementation
|
|
||||||
|
|
||||||
- **Automatic Device Fetching**: Connects to `https://usetrmnl.com/devices.json` to retrieve user devices and API keys
|
|
||||||
- **Login Detection**: Content script monitors TRMNL website for successful authentication
|
|
||||||
- **Seamless Integration**: No manual API key copying required
|
|
||||||
- **Fallback Support**: Manual API key entry still available for advanced users
|
|
||||||
- **Cross-Domain Support**: Works with all TRMNL subdomains and development environments
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
|
||||||
|
|
||||||
Please ensure all changes maintain Firefox compatibility and follow Firefox extension best practices.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT](https://choosealicense.com/licenses/mit/)
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
(function () {
|
|
||||||
// Function to check if user is authenticated
|
|
||||||
function checkAuthentication() {
|
|
||||||
// Check if we can access devices.json to verify authentication
|
|
||||||
fetch('/devices.json')
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
console.log("User is authenticated - devices.json accessible");
|
|
||||||
browser.runtime.sendMessage({ action: "loginSuccess" });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log("Authentication check failed:", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to detect login success indicators
|
|
||||||
function detectLoginSuccess() {
|
|
||||||
// Check for dashboard or authenticated pages
|
|
||||||
const authenticatedPaths = ['/dashboard', '/devices', '/settings', '/account'];
|
|
||||||
const currentPath = window.location.pathname;
|
|
||||||
|
|
||||||
if (authenticatedPaths.some(path => currentPath.startsWith(path))) {
|
|
||||||
console.log("User on authenticated page:", currentPath);
|
|
||||||
browser.runtime.sendMessage({ action: "loginSuccess" });
|
|
||||||
browser.runtime.sendMessage({ action: "refreshDevices" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for authentication cookies
|
|
||||||
const hasAuthCookie = document.cookie.includes('session') ||
|
|
||||||
document.cookie.includes('auth') ||
|
|
||||||
document.cookie.includes('_session') ||
|
|
||||||
document.cookie.includes('remember_user_token');
|
|
||||||
|
|
||||||
// Check for user navigation elements that appear when logged in
|
|
||||||
const userNavElements = document.querySelector('nav [href*="logout"], nav [href*="sign_out"], .user-menu, .account-menu');
|
|
||||||
|
|
||||||
if (hasAuthCookie || userNavElements) {
|
|
||||||
console.log("Login indicators found - cookies:", !!hasAuthCookie, "nav elements:", !!userNavElements);
|
|
||||||
checkAuthentication();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run detection immediately
|
|
||||||
detectLoginSuccess();
|
|
||||||
|
|
||||||
// Also run after DOM content loads in case elements load later
|
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
document.addEventListener('DOMContentLoaded', detectLoginSuccess);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch for navigation changes (SPA behavior)
|
|
||||||
let lastUrl = location.href;
|
|
||||||
new MutationObserver(() => {
|
|
||||||
const url = location.href;
|
|
||||||
if (url !== lastUrl) {
|
|
||||||
lastUrl = url;
|
|
||||||
setTimeout(detectLoginSuccess, 500); // Small delay for page to settle
|
|
||||||
}
|
|
||||||
}).observe(document, { subtree: true, childList: true });
|
|
||||||
})();
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"manifest_version": 2,
|
|
||||||
"name": "TRMNL New Tab - Firefox",
|
|
||||||
"version": "1.0",
|
|
||||||
"description": "Firefox extension that brings TRMNL's calm, distraction-free environment to your new tab page for seamless clarity and focus.",
|
|
||||||
"permissions": [
|
|
||||||
"storage",
|
|
||||||
"alarms",
|
|
||||||
"http://localhost:3000/*",
|
|
||||||
"https://usetrmnl.com/*"
|
|
||||||
],
|
|
||||||
"browser_action": {
|
|
||||||
"default_popup": "popup.html",
|
|
||||||
"default_icon": {
|
|
||||||
"16": "trmnl-icon.png",
|
|
||||||
"48": "trmnl-icon.png",
|
|
||||||
"128": "trmnl-icon.png"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"chrome_url_overrides": {
|
|
||||||
"newtab": "newtab.html"
|
|
||||||
},
|
|
||||||
"background": {
|
|
||||||
"scripts": ["background.js"],
|
|
||||||
"persistent": true
|
|
||||||
},
|
|
||||||
"content_scripts": [
|
|
||||||
{
|
|
||||||
"matches": [
|
|
||||||
"https://usetrmnl.com/*",
|
|
||||||
"https://www.usetrmnl.com/*",
|
|
||||||
"https://app.usetrmnl.com/*",
|
|
||||||
"http://localhost:3000/*"
|
|
||||||
],
|
|
||||||
"js": ["dashboard-content.js"]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"devtools_page": "devtools.html",
|
|
||||||
"icons": {
|
|
||||||
"16": "trmnl-icon.png",
|
|
||||||
"48": "trmnl-icon.pngp",
|
|
||||||
"128": "trmnl-icon.png"
|
|
||||||
},
|
|
||||||
"browser_specific_settings": {
|
|
||||||
"gecko": {
|
|
||||||
"id": "trmnl-newtab@usetrmnl.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-416
@@ -1,416 +0,0 @@
|
|||||||
// JavaScript for the new tab page
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", initNewTab);
|
|
||||||
|
|
||||||
// Listen for storage changes to detect logout
|
|
||||||
browser.storage.onChanged.addListener((changes, namespace) => {
|
|
||||||
if (namespace === "local") {
|
|
||||||
// Check if critical data was cleared (logout)
|
|
||||||
if (changes.devices && !changes.devices.newValue) {
|
|
||||||
console.log("Devices cleared - user logged out");
|
|
||||||
redirectToLogin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// DOM references
|
|
||||||
const imageElement = document.getElementById("trmnl-image");
|
|
||||||
const loadingElement = document.getElementById("loading");
|
|
||||||
const errorContainer = document.getElementById("error-container");
|
|
||||||
const infoOverlay = document.getElementById("info-overlay");
|
|
||||||
const nextRefreshElement = document.getElementById("next-refresh-time");
|
|
||||||
const refreshButton = document.getElementById("refresh-now");
|
|
||||||
const settingsButton = document.getElementById("open-settings");
|
|
||||||
const logoutButton = document.getElementById("logout-btn");
|
|
||||||
|
|
||||||
// State
|
|
||||||
let refreshTimeoutId = null;
|
|
||||||
let countdownIntervalId = null;
|
|
||||||
|
|
||||||
// Initialize the new tab page
|
|
||||||
async function initNewTab() {
|
|
||||||
try {
|
|
||||||
// Check if user is logged out
|
|
||||||
if (await isLoggedOut()) {
|
|
||||||
console.log("User is logged out, redirecting to login");
|
|
||||||
await redirectToLogin();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First try to get the environment setting
|
|
||||||
const { environment } = await browser.storage.local.get("environment");
|
|
||||||
const baseUrl =
|
|
||||||
environment === "development"
|
|
||||||
? "http://localhost:3000"
|
|
||||||
: "https://usetrmnl.com";
|
|
||||||
|
|
||||||
// Try to get devices from local storage first
|
|
||||||
const { devices: storedDevices } =
|
|
||||||
await browser.storage.local.get("devices");
|
|
||||||
|
|
||||||
// If we have devices in storage, use them
|
|
||||||
if (storedDevices && storedDevices.length > 0) {
|
|
||||||
console.log("Using devices from local storage:", storedDevices);
|
|
||||||
|
|
||||||
// Check if we have a selected device
|
|
||||||
const { selectedDevice } =
|
|
||||||
await browser.storage.local.get("selectedDevice");
|
|
||||||
if (!selectedDevice) {
|
|
||||||
await browser.storage.local.set({ selectedDevice: storedDevices[0] });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue with normal initialization
|
|
||||||
setupEventListeners();
|
|
||||||
await loadImage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have devices in storage, fetch from server
|
|
||||||
console.log("No devices in local storage, fetching from server");
|
|
||||||
const response = await fetch(`${baseUrl}/devices.json`);
|
|
||||||
|
|
||||||
// If unauthorized or forbidden, redirect to login
|
|
||||||
if (response.status === 401 || response.status === 403) {
|
|
||||||
await redirectToLogin();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If response is not OK for any other reason
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here, we have the devices, proceed with normal initialization
|
|
||||||
const devices = await response.json();
|
|
||||||
|
|
||||||
if (!devices || devices.length === 0) {
|
|
||||||
// No devices available, redirect to login
|
|
||||||
await redirectToLogin();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store devices and selected device if not already set
|
|
||||||
await browser.storage.local.set({ devices });
|
|
||||||
const { selectedDevice } =
|
|
||||||
await browser.storage.local.get("selectedDevice");
|
|
||||||
if (!selectedDevice) {
|
|
||||||
await browser.storage.local.set({ selectedDevice: devices[0] });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue with normal initialization
|
|
||||||
setupEventListeners();
|
|
||||||
await loadImage();
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error during initialization:", error);
|
|
||||||
// On any error, redirect to login
|
|
||||||
await redirectToLogin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up event listeners
|
|
||||||
function setupEventListeners() {
|
|
||||||
// Refresh now button
|
|
||||||
refreshButton.addEventListener("click", () => {
|
|
||||||
browser.runtime.sendMessage({ action: "forceRefresh" }).then(() => {
|
|
||||||
loadImage();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Settings button
|
|
||||||
settingsButton.addEventListener("click", () => {
|
|
||||||
// For Firefox, show an inline settings panel instead of popup
|
|
||||||
showInlineSettings();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Logout button
|
|
||||||
logoutButton.addEventListener("click", () => {
|
|
||||||
performLogout();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Login button - add event listener when it appears
|
|
||||||
document.addEventListener("click", (e) => {
|
|
||||||
if (e.target && e.target.id === "login-now") {
|
|
||||||
startLogin();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listen for messages from background
|
|
||||||
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
||||||
if (message.action === "imageUpdated") {
|
|
||||||
console.log("Received image update notification");
|
|
||||||
loadImage();
|
|
||||||
if (sendResponse) sendResponse({ received: true });
|
|
||||||
} else if (message.action === "loginSuccess") {
|
|
||||||
console.log("Login success detected, refreshing new tab");
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the image from storage
|
|
||||||
async function loadImage() {
|
|
||||||
loadingElement.classList.remove("hidden");
|
|
||||||
imageElement.classList.add("hidden"); // Hide the image while loading
|
|
||||||
|
|
||||||
browser.runtime
|
|
||||||
.sendMessage({ action: "getCurrentImage" })
|
|
||||||
.then((response) => {
|
|
||||||
if (!response || !response.currentImage) {
|
|
||||||
showLoginPrompt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide error container if it was showing
|
|
||||||
errorContainer.classList.add("hidden");
|
|
||||||
|
|
||||||
// Create a new image element to force a reload
|
|
||||||
const newImage = new Image();
|
|
||||||
newImage.onload = () => {
|
|
||||||
// Update the src of the actual image element
|
|
||||||
imageElement.src = newImage.src;
|
|
||||||
loadingElement.classList.add("hidden");
|
|
||||||
imageElement.classList.remove("hidden");
|
|
||||||
};
|
|
||||||
|
|
||||||
newImage.onerror = () => {
|
|
||||||
console.error("Error loading image data URL");
|
|
||||||
loadingElement.textContent = "Error loading image";
|
|
||||||
// Request a fresh image
|
|
||||||
browser.runtime.sendMessage({ action: "forceRefresh" });
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add a cache-busting parameter to force reload
|
|
||||||
newImage.src = `${response.currentImage.url}#t=${Date.now()}`;
|
|
||||||
|
|
||||||
// Update next refresh info
|
|
||||||
updateRefreshTimer(response.nextFetch);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start login flow
|
|
||||||
function startLogin() {
|
|
||||||
browser.runtime.sendMessage({ action: "startLogin" }).then((response) => {
|
|
||||||
if (response && response.success) {
|
|
||||||
const errorText = errorContainer.querySelector("p");
|
|
||||||
if (errorText) {
|
|
||||||
errorText.textContent = "Login page opened - please complete login in the new tab.";
|
|
||||||
}
|
|
||||||
// Check for login success
|
|
||||||
checkForLoginSuccess();
|
|
||||||
} else {
|
|
||||||
const errorText = errorContainer.querySelector("p");
|
|
||||||
if (errorText) {
|
|
||||||
errorText.textContent = "Error opening login page. Please try again.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error("Login error:", error);
|
|
||||||
const errorText = errorContainer.querySelector("p");
|
|
||||||
if (errorText) {
|
|
||||||
errorText.textContent = "Error opening login page. Please try again.";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for login success periodically
|
|
||||||
function checkForLoginSuccess() {
|
|
||||||
const checkInterval = setInterval(async () => {
|
|
||||||
const { devices } = await browser.storage.local.get(["devices"]);
|
|
||||||
|
|
||||||
if (devices && devices.length > 0) {
|
|
||||||
clearInterval(checkInterval);
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
// Stop checking after 2 minutes
|
|
||||||
setTimeout(() => {
|
|
||||||
clearInterval(checkInterval);
|
|
||||||
}, 120000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the login prompt
|
|
||||||
function showLoginPrompt() {
|
|
||||||
loadingElement.classList.add("hidden");
|
|
||||||
imageElement.classList.add("hidden");
|
|
||||||
errorContainer.classList.remove("hidden");
|
|
||||||
|
|
||||||
const errorText = errorContainer.querySelector("p");
|
|
||||||
if (errorText) {
|
|
||||||
errorText.textContent = "Please log in to your TRMNL account to view your devices.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the refresh countdown timer
|
|
||||||
function updateRefreshTimer(nextFetchTimestamp) {
|
|
||||||
if (!nextFetchTimestamp) {
|
|
||||||
nextRefreshElement.textContent = "Unknown";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear existing timeouts and intervals
|
|
||||||
if (refreshTimeoutId) clearTimeout(refreshTimeoutId);
|
|
||||||
if (countdownIntervalId) clearInterval(countdownIntervalId);
|
|
||||||
|
|
||||||
// Set timeout to load image at refresh time
|
|
||||||
const now = Date.now();
|
|
||||||
const timeToRefresh = Math.max(0, nextFetchTimestamp - now);
|
|
||||||
|
|
||||||
if (timeToRefresh > 0) {
|
|
||||||
// Add a small buffer (2 seconds) to ensure the background has time to fetch
|
|
||||||
refreshTimeoutId = setTimeout(() => {
|
|
||||||
// Check if the image has been updated in background
|
|
||||||
browser.runtime
|
|
||||||
.sendMessage({ action: "getCurrentImage" })
|
|
||||||
.then((response) => {
|
|
||||||
const currentTime = Date.now();
|
|
||||||
// Only reload if the last fetch time is recent (within last 10 seconds)
|
|
||||||
if (
|
|
||||||
response &&
|
|
||||||
response.lastFetch &&
|
|
||||||
currentTime - response.lastFetch < 10000
|
|
||||||
) {
|
|
||||||
loadImage();
|
|
||||||
} else {
|
|
||||||
// If not updated recently, the background refresh might have failed
|
|
||||||
// Request a refresh and then load the image
|
|
||||||
browser.runtime.sendMessage({ action: "forceRefresh" }).then(() => {
|
|
||||||
setTimeout(loadImage, 2000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, timeToRefresh);
|
|
||||||
|
|
||||||
// Update countdown display
|
|
||||||
updateCountdown(nextFetchTimestamp);
|
|
||||||
countdownIntervalId = setInterval(() => {
|
|
||||||
updateCountdown(nextFetchTimestamp);
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the countdown display
|
|
||||||
function updateCountdown(nextFetchTimestamp) {
|
|
||||||
const now = Date.now();
|
|
||||||
const timeRemaining = Math.max(0, nextFetchTimestamp - now);
|
|
||||||
|
|
||||||
if (timeRemaining <= 0) {
|
|
||||||
nextRefreshElement.textContent = "Now";
|
|
||||||
if (countdownIntervalId) {
|
|
||||||
clearInterval(countdownIntervalId);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the time remaining
|
|
||||||
const seconds = Math.floor((timeRemaining / 1000) % 60);
|
|
||||||
const minutes = Math.floor((timeRemaining / (1000 * 60)) % 60);
|
|
||||||
const hours = Math.floor(timeRemaining / (1000 * 60 * 60));
|
|
||||||
|
|
||||||
nextRefreshElement.textContent = `${padZero(hours)}:${padZero(minutes)}:${padZero(seconds)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show inline settings panel (Firefox-compatible)
|
|
||||||
function showInlineSettings() {
|
|
||||||
// Create or show a simple inline settings overlay
|
|
||||||
let settingsOverlay = document.getElementById("settings-overlay");
|
|
||||||
if (!settingsOverlay) {
|
|
||||||
settingsOverlay = document.createElement("div");
|
|
||||||
settingsOverlay.id = "settings-overlay";
|
|
||||||
settingsOverlay.style.cssText = `
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba(0,0,0,0.8);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1000;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const settingsPanel = document.createElement("div");
|
|
||||||
settingsPanel.style.cssText = `
|
|
||||||
background: #1a1a1a;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: white;
|
|
||||||
max-width: 400px;
|
|
||||||
width: 90%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
settingsPanel.innerHTML = `
|
|
||||||
<h3 style="margin-top: 0;">TRMNL Settings</h3>
|
|
||||||
<p>To access full settings, click the TRMNL icon in your browser toolbar.</p>
|
|
||||||
<button id="close-settings" style="background: #4a5568; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-top: 16px;">Close</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
settingsOverlay.appendChild(settingsPanel);
|
|
||||||
document.body.appendChild(settingsOverlay);
|
|
||||||
|
|
||||||
// Close button functionality
|
|
||||||
document.getElementById("close-settings").addEventListener("click", () => {
|
|
||||||
settingsOverlay.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close on overlay click
|
|
||||||
settingsOverlay.addEventListener("click", (e) => {
|
|
||||||
if (e.target === settingsOverlay) {
|
|
||||||
settingsOverlay.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
settingsOverlay.style.display = "flex";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to pad numbers with leading zeros
|
|
||||||
function padZero(num) {
|
|
||||||
return num.toString().padStart(2, "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user is logged out (no critical data)
|
|
||||||
async function isLoggedOut() {
|
|
||||||
const storage = await browser.storage.local.get([
|
|
||||||
"devices",
|
|
||||||
"selectedDevice",
|
|
||||||
"apiKey"
|
|
||||||
]);
|
|
||||||
|
|
||||||
// If we have no devices AND no API key, consider user logged out
|
|
||||||
return (!storage.devices || storage.devices.length === 0) && !storage.apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to login page
|
|
||||||
async function redirectToLogin() {
|
|
||||||
const { environment } = await browser.storage.local.get("environment");
|
|
||||||
const baseUrl =
|
|
||||||
environment === "development"
|
|
||||||
? "http://localhost:3000"
|
|
||||||
: "https://usetrmnl.com";
|
|
||||||
|
|
||||||
console.log("Redirecting to login:", `${baseUrl}/login`);
|
|
||||||
window.location.href = `${baseUrl}/login`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform logout
|
|
||||||
function performLogout() {
|
|
||||||
if (!confirm("Are you sure you want to logout? This will clear all extension data and you'll need to login again.")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
browser.runtime.sendMessage({ action: "logout" }).then((response) => {
|
|
||||||
if (response && response.success) {
|
|
||||||
console.log("Logout successful");
|
|
||||||
// The background script will redirect us to login
|
|
||||||
} else {
|
|
||||||
console.error("Logout failed:", response);
|
|
||||||
alert("Logout failed. Please try again.");
|
|
||||||
}
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error("Logout error:", error);
|
|
||||||
alert("Logout failed. Please try again.");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
-408
@@ -1,408 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>TRMNL Settings</title>
|
|
||||||
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
||||||
<link
|
|
||||||
href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
|
|
||||||
rel="stylesheet"
|
|
||||||
/>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
width: 350px;
|
|
||||||
padding: 20px;
|
|
||||||
font-family: Inter, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
color: #2c3e50;
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #555;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
background-color: #fff;
|
|
||||||
color: #333;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus,
|
|
||||||
select:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #007bff;
|
|
||||||
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px 16px;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background-color: #0056b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:disabled {
|
|
||||||
background-color: #6c757d;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group button {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-group button:only-child {
|
|
||||||
max-width: 200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#status {
|
|
||||||
margin-top: 15px;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
|
||||||
min-height: 20px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-success {
|
|
||||||
color: #155724 !important;
|
|
||||||
background-color: #d4edda !important;
|
|
||||||
border: 1px solid #c3e6cb !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-error {
|
|
||||||
color: #721c24 !important;
|
|
||||||
background-color: #f8d7da !important;
|
|
||||||
border: 1px solid #f5c6cb !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-info {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
margin-top: 15px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-section {
|
|
||||||
border-top: 1px solid #dee2e6;
|
|
||||||
padding-top: 20px;
|
|
||||||
margin-top: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-btn {
|
|
||||||
background-color: #dc3545 !important;
|
|
||||||
color: white !important;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-btn:hover {
|
|
||||||
background-color: #c82333 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-warning {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6c757d;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-prompt {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-prompt p {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
color: #6c757d;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-btn {
|
|
||||||
background-color: #28a745 !important;
|
|
||||||
color: white !important;
|
|
||||||
width: 100%;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-btn:hover {
|
|
||||||
background-color: #218838 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section {
|
|
||||||
margin-top: 20px;
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section summary {
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #555;
|
|
||||||
margin: 0;
|
|
||||||
padding: 15px;
|
|
||||||
outline: none;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section summary:hover {
|
|
||||||
color: #007bff;
|
|
||||||
background-color: #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section[open] summary {
|
|
||||||
border-bottom: 1px solid #dee2e6;
|
|
||||||
border-radius: 6px 6px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section > div {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section .button-group {
|
|
||||||
margin: 15px 0;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section .button-group button {
|
|
||||||
flex: 1;
|
|
||||||
max-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section .button-group button:only-child {
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section .toggle-manual-btn {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.manual-api-section {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-manual-btn {
|
|
||||||
background-color: #6c757d !important;
|
|
||||||
color: white !important;
|
|
||||||
font-size: 13px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
margin-top: 8px;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-weight: 500;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-manual-btn:hover {
|
|
||||||
background-color: #545b62 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
body {
|
|
||||||
background-color: #1a1a1a;
|
|
||||||
color: #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
color: #ced4da;
|
|
||||||
}
|
|
||||||
|
|
||||||
input,
|
|
||||||
select {
|
|
||||||
background-color: #333;
|
|
||||||
color: #fff;
|
|
||||||
border-color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus,
|
|
||||||
select:focus {
|
|
||||||
border-color: #0d6efd;
|
|
||||||
}
|
|
||||||
|
|
||||||
select option {
|
|
||||||
background-color: #333;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-prompt {
|
|
||||||
background-color: #2d3748;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-prompt p {
|
|
||||||
color: #a0aec0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-info {
|
|
||||||
color: #adb5bd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section {
|
|
||||||
background-color: #2d3748;
|
|
||||||
border-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section summary {
|
|
||||||
color: #ced4da;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section summary:hover {
|
|
||||||
color: #0d6efd;
|
|
||||||
background-color: #3a4a5c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-section[open] summary {
|
|
||||||
border-bottom-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-section {
|
|
||||||
border-top-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-warning {
|
|
||||||
color: #adb5bd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-success {
|
|
||||||
color: #d1e7dd !important;
|
|
||||||
background-color: #0f2419 !important;
|
|
||||||
border: 1px solid #1c4532 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-error {
|
|
||||||
color: #f8d7da !important;
|
|
||||||
background-color: #2c0b0e !important;
|
|
||||||
border: 1px solid #58151c !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="popup-body">
|
|
||||||
<div>
|
|
||||||
<h2 class="popup-title">TRMNL New Tab Settings</h2>
|
|
||||||
|
|
||||||
<div id="device-selector-group" class="form-group hidden">
|
|
||||||
<label for="device-select" class="popup-label"
|
|
||||||
>Select Device</label
|
|
||||||
>
|
|
||||||
<select id="device-select" class="popup-select">
|
|
||||||
<option value="">Loading devices...</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" id="login-section">
|
|
||||||
<div id="login-prompt" class="login-prompt">
|
|
||||||
<p>
|
|
||||||
Please log in to your TRMNL account to access your
|
|
||||||
devices.
|
|
||||||
</p>
|
|
||||||
<button id="login-btn" class="login-btn">
|
|
||||||
Login to TRMNL
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="status"></div>
|
|
||||||
|
|
||||||
<details id="advanced-section" class="advanced-section">
|
|
||||||
<summary>Advanced Settings</summary>
|
|
||||||
|
|
||||||
<div style="text-align: center; margin-bottom: 15px;">
|
|
||||||
<button id="toggle-manual" class="toggle-manual-btn">
|
|
||||||
Use Manual API Key
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="manual-api-section" class="manual-api-section hidden">
|
|
||||||
<label for="api-key">Manual API Key</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="api-key"
|
|
||||||
placeholder="Enter your API key manually"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="button-group" style="margin-bottom: 15px;">
|
|
||||||
<button id="save-settings" style="display: none;">Save Settings</button>
|
|
||||||
<button id="refresh-now" style="display: none;">Refresh Now</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-info">
|
|
||||||
<div id="last-updated"></div>
|
|
||||||
<div id="next-update"></div>
|
|
||||||
<div id="refresh-rate"></div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<div id="logout-section" class="logout-section hidden">
|
|
||||||
<div class="logout-warning">
|
|
||||||
This will clear all extension data and log you out.
|
|
||||||
</div>
|
|
||||||
<button id="logout-btn" class="logout-btn">
|
|
||||||
Logout & Reset
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="popup.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
-531
File diff suppressed because it is too large
Load Diff
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="512px" height="512px" viewBox="0 0 512 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<title>trmnl--fav-icon</title>
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
|
||||||
<g id="trmnl--fav-icon">
|
|
||||||
<path d="M101.273465,0 L410.726535,7.25467311e-15 C445.941525,-4.215174e-15 458.711331,3.66661164 471.585405,10.5517418 C484.459479,17.436872 494.563128,27.5405206 501.448258,40.4145947 C508.333388,53.2886688 512,66.0584752 512,101.273465 L512,410.726535 C512,445.941525 508.333388,458.711331 501.448258,471.585405 C494.563128,484.459479 484.459479,494.563128 471.585405,501.448258 C458.711331,508.333388 445.941525,512 410.726535,512 L101.273465,512 C66.0584752,512 53.2886688,508.333388 40.4145947,501.448258 C27.5405206,494.563128 17.436872,484.459479 10.5517418,471.585405 C3.66661164,458.711331 0,445.941525 0,410.726535 L0,101.273465 C0,66.0584752 3.66661164,53.2886688 10.5517418,40.4145947 C17.436872,27.5405206 27.5405206,17.436872 40.4145947,10.5517418 C53.2886688,3.66661164 66.0584752,0 101.273465,0 Z" id="Rectangle" fill="#F86527"></path>
|
|
||||||
<polygon id="Path" fill="#FFFFFF" points="173.227881 101 266 135.905584 248.772355 182 156 147.094416"></polygon>
|
|
||||||
<polygon id="Path" fill="#FFFFFF" points="321.477308 90 352 184.742164 305.523867 200 275 105.258073"></polygon>
|
|
||||||
<polygon id="Path" fill="#FFFFFF" points="423 199.08638 368.734902 282 328 254.91256 382.266271 172"></polygon>
|
|
||||||
<polygon id="Path" fill="#FFFFFF" points="401 347.057326 302.406877 356 298 306.942674 396.593123 298"></polygon>
|
|
||||||
<polygon id="Path" fill="#FFFFFF" points="272.526812 422 204 350.073199 239.474369 316 308 387.926801"></polygon>
|
|
||||||
<polygon id="Path" fill="#FFFFFF" points="133 367.412771 146.295792 269 195 275.587229 181.704208 374"></polygon>
|
|
||||||
<polygon id="Path" fill="#FFFFFF" points="89 224.865546 173.872813 174 199 216.133279 114.126739 267"></polygon>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
(function () {
|
||||||
|
// Check if we're on the dashboard page post-login
|
||||||
|
if (window.location.pathname === "/dashboard") {
|
||||||
|
// Send message to background script to refresh devices
|
||||||
|
chrome.runtime.sendMessage({ action: "refreshDevices" });
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
||||||
<script src="devtools.js"></script>
|
<script src="devtools.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
browser.devtools.panels.create(
|
chrome.devtools.panels.create(
|
||||||
"TRMNL",
|
"TRMNL",
|
||||||
"",
|
"",
|
||||||
"/panel.html", // Make sure this path is correct
|
"/panel.html", // Make sure this path is correct
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 683 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "TRMNL New Tab Display",
|
||||||
|
"version": "1.1",
|
||||||
|
"description": "Displays images from TRMNL API on new tab pages",
|
||||||
|
"permissions": ["storage", "alarms"],
|
||||||
|
"host_permissions": ["http://localhost:3000/*", "https://usetrmnl.com/*"],
|
||||||
|
"devtools_page": "devtools.html",
|
||||||
|
"chrome_url_overrides": {
|
||||||
|
"newtab": "newtab.html"
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": ["https://usetrmnl.com/*", "http://localhost:3000/*"],
|
||||||
|
"js": ["dashboard-content.js"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"scripts": ["background.js"],
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"default_popup": "popup.html",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "icons/icon16.png",
|
||||||
|
"48": "icons/icon48.png",
|
||||||
|
"128": "icons/icon128.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"16": "icons/icon16.png",
|
||||||
|
"48": "icons/icon48.png",
|
||||||
|
"128": "icons/icon128.png"
|
||||||
|
},
|
||||||
|
"browser_specific_settings": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "{fa015ea5-5b74-40d8-b4d5-75f5f0edbfda}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user