mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-04 22:35:08 +00:00
* fix(web-ui): Update All sends the live installed list and redraws the grid updateAll() preferred PluginStateManager.installedPlugins over window.installedPlugins. Only updateAll's own end-of-run refresh ever fills PluginStateManager, so from the second run on it sent the first run's plugins: one uninstalled since failed with "plugin not found" and one installed since was never updated. That refresh also only replaced window.installedPlugins, so the installed cards and the Updates badge kept offering "Update to vX" for what had just been updated. Read window.installedPlugins, the list plugins_manager.js republishes after every install, uninstall and refresh, keeping PluginStateManager as the fallback for a page without it, and refresh through pluginManager.loadInstalledPlugins(true), which redraws the grid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web-ui): list each plugin's display modes in /plugins/installed The on-demand modal fills its Display Mode select from plugin.display_modes, but /plugins/installed never sent the field. Every plugin offered one option, its own id, under "This plugin exposes a single display mode"; the display resolved that id to the plugin's first mode, so a multi-mode plugin could only be started, or pinned, there. Add display_modes to each entry, read from the plugin catalog (get_plugin_display_modes), the same declared list /display/modes and on-demand/start use, keeping only strings. Single-mode plugins still get one option and the same hint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web-ui): enable a store install by its installed id, and not on reinstall The store's Install button enabled the new plugin by the registry id it installed. Weather, Music, Stocks and Leaderboard install under the id their manifests declare (weather -> ledmatrix-weather); the plugin list, the config section and /plugins/toggle know only that id, so the toggle answered 404 "Plugin not found" and the plugin stayed disabled behind "installed, but enabling it failed". The same button on an installed plugin (Reinstall) enabled it too, switching a plugin the user had turned off back on. POST /plugins/install now names the installed plugin: plugin_id in the direct answer and in the queued operation's result, read from the installed manifest found the way the store's update and uninstall find it (_find_plugin_path: id, aliases, plugin_path name), else the requested id. The client reloads the list, then enables that id; from an answer without it, the installed entry the store entry matches (findInstalledStorePlugin, which isStorePluginInstalled now uses). A reinstall, decided by the same match that labelled the button, reloads the list and leaves the enabled state alone. test/js/plugins_manager_sandbox.js runs the whole of plugins_manager.js in a vm context against a fake DOM and API, for suites that drive its real flows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web-ui): wait for long store installs; on timeout reload, not fail pollOperationStatus gave a queued install 60 polls, a second apart, then reported "Install operation timed out" as an error and stopped. The server allows the plugin's dependency install 300 s on its own (install_requirements_file in store_install.py), after a download that fetches the plugin a file at a time, so installs that went on to succeed were reported as failed, never enabled, and left out of the installed list until the page was reloaded. Give installs INSTALL_POLL_MAX_ATTEMPTS (600, ten minutes). When even that runs out, reload the installed list and the store badges and warn that the install may still be running; nothing is enabled without the operation's answer. Uninstall keeps the default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web-ui): build the store's category filter from the store's plugins The #plugin-category select listed seven fixed categories while the registry uses about twenty (productivity, utility, transit, finance, ...), so roughly a third of the store could not be filtered to, and "Financial" missed the plugin filed under "finance". The template now ships only "All Categories"; syncStoreCategoryOptions, run by applyStoreFiltersAndSort, adds one option per category the cached store plugins have (case folded, as the filter compares), keeps the current choice, and rebuilds only when the set changes or the partial was swapped in afresh -- the way the Starlark section builds its own. The test sandbox gains window.addEventListener (initPluginsPage needs it) and quiets the script's "element not found" warnings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(web-ui): one handler for the GitHub-URL Install button #install-plugin-from-url had an inline onclick calling window.handleGitHubPluginInstall, and attachInstallButtonHandler also gave it a click listener that installs, so both ran on every click (and on Enter, which clicks it). The inline handler threw a ReferenceError -- it called isGithubUrl, which is local to the plugin-manager IIFE, from outside it -- so only the listener's request went out; correcting that scope alone would have sent every install twice. Remove the inline onclick and the window.handleGitHubPluginInstall it called, which nothing else uses. The listener, which already sent the only request, is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
652 lines
36 KiB
HTML
652 lines
36 KiB
HTML
<div class="bg-white rounded-lg shadow-md p-6" data-plugins-loaded="true">
|
|
<div class="section-header">
|
|
<h2 class="text-xl font-bold text-gray-900 mb-1">Plugin Management</h2>
|
|
<p class="text-sm text-gray-600">Manage installed plugins, configure settings, and browse the plugin store.</p>
|
|
</div>
|
|
|
|
<!-- Plugin Controls -->
|
|
<div class="flex flex-wrap items-center justify-between gap-4 mb-6">
|
|
<div class="flex flex-wrap items-center gap-2 sm:gap-4">
|
|
<button type="button" id="refresh-plugins-btn" class="btn bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md">
|
|
<i class="fas fa-sync-alt mr-2"></i>Refresh Plugins
|
|
</button>
|
|
<button type="button" id="update-all-plugins-btn" class="btn bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md flex items-center">
|
|
<i class="fas fa-cloud-download-alt mr-2"></i>Check & Update All
|
|
</button>
|
|
<button type="button" id="restart-display-btn" class="btn bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md">
|
|
<i class="fas fa-redo mr-2"></i>Restart Display
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Plugin Content Area -->
|
|
<div class="space-y-8">
|
|
<!-- Installed Plugins Section (Always visible at top) -->
|
|
<div id="installed-plugins-section" class="mb-8">
|
|
<div class="flex items-center justify-between mb-5 pb-3 border-b border-gray-200">
|
|
<div class="flex items-center gap-3">
|
|
<h3 class="text-lg font-bold text-gray-900">Installed Plugins</h3>
|
|
<span id="installed-count" class="text-sm text-gray-500 font-medium">0 installed</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search / Filter / Sort Bar -->
|
|
<div id="installed-filter-bar" class="flex flex-wrap items-center gap-3 mb-4 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
|
<!-- Search -->
|
|
<div class="relative flex-1 min-w-[12rem]">
|
|
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm pointer-events-none" aria-hidden="true"></i>
|
|
<input type="text"
|
|
id="installed-search"
|
|
class="form-control text-sm pl-9 pr-8 py-2"
|
|
placeholder="Search installed plugins…"
|
|
autocomplete="off"
|
|
aria-label="Search installed plugins">
|
|
<button type="button" id="installed-search-clear"
|
|
class="hidden absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
|
aria-label="Clear search">
|
|
<i class="fas fa-times-circle" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Status filter pills -->
|
|
<div id="installed-filter-pills" class="flex items-center gap-1.5" role="group" aria-label="Filter installed plugins by status">
|
|
<button type="button" class="filter-pill text-sm px-3 py-1.5 rounded-md border border-gray-300 bg-white"
|
|
data-installed-filter="all" data-active="true" aria-pressed="true">All</button>
|
|
<button type="button" class="filter-pill text-sm px-3 py-1.5 rounded-md border border-gray-300 bg-white"
|
|
data-installed-filter="enabled" data-active="false" aria-pressed="false">Enabled</button>
|
|
<button type="button" class="filter-pill text-sm px-3 py-1.5 rounded-md border border-gray-300 bg-white"
|
|
data-installed-filter="disabled" data-active="false" aria-pressed="false">Disabled</button>
|
|
<button type="button" class="filter-pill text-sm px-3 py-1.5 rounded-md border border-gray-300 bg-white"
|
|
data-installed-filter="updates" data-active="false" aria-pressed="false"
|
|
title="Show only plugins with a newer version available">
|
|
Updates<span id="installed-updates-count" class="badge badge-info ml-1.5 hidden">0</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Sort -->
|
|
<select id="installed-sort" class="text-sm px-3 py-1.5 border border-gray-300 rounded-md bg-white" aria-label="Sort installed plugins">
|
|
<option value="a-z">A → Z</option>
|
|
<option value="z-a">Z → A</option>
|
|
<option value="status">Updates first</option>
|
|
<option value="recent">Recently updated</option>
|
|
<option value="category">Category</option>
|
|
</select>
|
|
|
|
<div class="flex-1"></div>
|
|
|
|
<!-- Clear -->
|
|
<button type="button" id="installed-clear-filters"
|
|
class="hidden text-sm px-3 py-1.5 rounded-md border border-red-300 bg-white text-red-600 hover:bg-red-50 transition-colors">
|
|
<i class="fas fa-times mr-1"></i>Clear
|
|
</button>
|
|
</div>
|
|
|
|
<div id="installed-plugins-content" class="block">
|
|
<div id="installed-plugins-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6">
|
|
<!-- Skeleton cards shown while installed plugins load -->
|
|
<div class="installed-skeleton plugin-card animate-pulse">
|
|
<div class="flex items-start justify-between mb-4">
|
|
<div class="flex-1 min-w-0 space-y-2">
|
|
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-1/2"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-2/3"></div>
|
|
</div>
|
|
<div class="h-6 w-10 bg-gray-200 rounded-full ml-4 flex-shrink-0"></div>
|
|
</div>
|
|
<div class="space-y-2 mb-4">
|
|
<div class="h-3 bg-gray-200 rounded w-full"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-5/6"></div>
|
|
</div>
|
|
<div class="h-8 bg-gray-200 rounded w-full mt-auto"></div>
|
|
</div>
|
|
<div class="installed-skeleton plugin-card animate-pulse hidden md:block">
|
|
<div class="flex items-start justify-between mb-4">
|
|
<div class="flex-1 min-w-0 space-y-2">
|
|
<div class="h-4 bg-gray-200 rounded w-2/3"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-1/3"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-1/2"></div>
|
|
</div>
|
|
<div class="h-6 w-10 bg-gray-200 rounded-full ml-4 flex-shrink-0"></div>
|
|
</div>
|
|
<div class="space-y-2 mb-4">
|
|
<div class="h-3 bg-gray-200 rounded w-full"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-4/5"></div>
|
|
</div>
|
|
<div class="h-8 bg-gray-200 rounded w-full mt-auto"></div>
|
|
</div>
|
|
<div class="installed-skeleton plugin-card animate-pulse hidden lg:block">
|
|
<div class="flex items-start justify-between mb-4">
|
|
<div class="flex-1 min-w-0 space-y-2">
|
|
<div class="h-4 bg-gray-200 rounded w-4/5"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-2/5"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-3/5"></div>
|
|
</div>
|
|
<div class="h-6 w-10 bg-gray-200 rounded-full ml-4 flex-shrink-0"></div>
|
|
</div>
|
|
<div class="space-y-2 mb-4">
|
|
<div class="h-3 bg-gray-200 rounded w-full"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-3/4"></div>
|
|
</div>
|
|
<div class="h-8 bg-gray-200 rounded w-full mt-auto"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Plugin Store Section (Always visible at bottom) -->
|
|
<div id="plugin-store-section" class="border-t border-gray-200 pt-8 mt-8">
|
|
<div class="flex items-center justify-between mb-5 pb-3 border-b border-gray-200">
|
|
<div class="flex items-center gap-3">
|
|
<h3 class="text-lg font-bold text-gray-900">Plugin Store</h3>
|
|
<span id="store-count" class="text-sm text-gray-500 font-medium">
|
|
<i class="fas fa-spinner fa-spin mr-1"></i>Loading...
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="plugin-store-content" class="block">
|
|
|
|
<!-- GitHub Token Configuration (Combined Warning + Settings) -->
|
|
<div id="github-token-container" class="mb-5">
|
|
<!-- Warning Banner (shown when no token configured) -->
|
|
<div id="github-auth-warning" class="hidden bg-yellow-50 border border-yellow-300 p-4 rounded-lg">
|
|
<div class="flex">
|
|
<div class="flex-shrink-0">
|
|
<i class="fas fa-exclamation-triangle text-yellow-400"></i>
|
|
</div>
|
|
<div class="ml-3 flex-1">
|
|
<p class="text-sm text-yellow-700">
|
|
<strong>Limited API Access:</strong> GitHub API requests are limited to <span id="rate-limit-count">60</span> per hour without authentication.
|
|
Add a GitHub token to increase this to 5,000 requests/hour and get real-time plugin stats.
|
|
</p>
|
|
<p class="mt-2 text-sm text-yellow-700">
|
|
<a href="https://github.com/settings/tokens/new?description=LEDMatrix%20Plugin%20Manager&scopes=" target="_blank" class="font-medium underline hover:text-yellow-800">
|
|
Create a GitHub Token →
|
|
</a>
|
|
<span class="mx-2">|</span>
|
|
<a href="#" onclick="event.preventDefault(); openGithubTokenSettings()" class="font-medium underline hover:text-yellow-800">
|
|
Configure Token
|
|
</a>
|
|
</p>
|
|
</div>
|
|
<div class="flex-shrink-0">
|
|
<button type="button" onclick="dismissGithubWarning()" class="text-yellow-700 hover:text-yellow-900" aria-label="Dismiss GitHub token warning">
|
|
<i class="fas fa-times" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Panel (expandable configuration form) -->
|
|
<div id="github-token-settings" class="hidden bg-blue-50 border border-blue-200 rounded-lg p-5 shadow-sm mt-3">
|
|
<div class="flex items-start justify-between mb-4">
|
|
<div class="flex items-center space-x-2">
|
|
<i class="fab fa-github text-blue-600 text-xl"></i>
|
|
<h4 class="font-bold text-gray-900">GitHub API Configuration</h4>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<button id="toggle-github-token-collapse" class="text-gray-600 hover:text-gray-900 text-sm flex items-center font-medium transition-colors">
|
|
<i class="fas fa-chevron-up mr-1" id="github-token-icon-collapse"></i>
|
|
<span>Collapse</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="github-token-content" class="block">
|
|
|
|
<p class="text-sm text-gray-600 mb-3">
|
|
Configure your GitHub Personal Access Token to increase API rate limits and get real-time plugin statistics.
|
|
</p>
|
|
|
|
<div class="space-y-3">
|
|
<div>
|
|
<label for="github-token-input" class="block text-sm font-medium text-gray-700 mb-1">
|
|
GitHub Personal Access Token
|
|
</label>
|
|
<div class="relative">
|
|
<input type="password" id="github-token-input"
|
|
class="w-full px-3 py-2 pr-20 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
|
|
placeholder="ghp_xxxxxxxxxxxxxxxxxxxx">
|
|
<button type="button" onclick="toggleGithubTokenVisibility()"
|
|
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-500 hover:text-gray-700"
|
|
aria-label="Show token" aria-controls="github-token-input">
|
|
<i id="github-token-icon" class="fas fa-eye" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
<p class="text-xs text-gray-500 mt-1">
|
|
Token is stored in config_secrets.json. No scopes required for public repositories.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between">
|
|
<a href="https://github.com/settings/tokens/new?description=LEDMatrix%20Plugin%20Manager&scopes="
|
|
target="_blank"
|
|
class="text-sm text-blue-600 hover:text-blue-800">
|
|
<i class="fas fa-external-link-alt mr-1"></i>Create Token on GitHub
|
|
</a>
|
|
<div class="flex gap-2">
|
|
<button onclick="loadGithubToken()" class="px-3 py-1.5 text-sm bg-gray-600 hover:bg-gray-700 text-white rounded-md">
|
|
<i class="fas fa-sync mr-1"></i>Load Current
|
|
</button>
|
|
<button onclick="saveGithubToken()" class="px-4 py-1.5 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded-md">
|
|
<i class="fas fa-save mr-1"></i>Save Token
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-white border border-blue-200 rounded p-3">
|
|
<p class="text-xs text-gray-600">
|
|
<strong>Rate Limits:</strong><br>
|
|
Without token: 60 requests/hour<br>
|
|
With token: 5,000 requests/hour
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Search Row -->
|
|
<div class="flex gap-3 mb-4">
|
|
<input type="text" id="plugin-search" placeholder="Search plugins by name, description, or tags..." aria-label="Search the Plugin Store" class="form-control text-sm flex-[3] min-w-0 px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:shadow-md transition-shadow">
|
|
<select id="plugin-category" aria-label="Filter the Plugin Store by category" class="form-control text-sm flex-1 px-3 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:shadow-md transition-shadow">
|
|
<option value="">All Categories</option>
|
|
<!-- The rest come from the store's plugins (plugins_manager.js, syncStoreCategoryOptions). -->
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Sort & Filter Bar -->
|
|
<div id="store-filter-bar" class="flex flex-wrap items-center gap-3 mb-4 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
|
<!-- Sort -->
|
|
<select id="store-sort" aria-label="Sort the Plugin Store" class="text-sm px-3 py-1.5 border border-gray-300 rounded-md bg-white">
|
|
<option value="a-z">A → Z</option>
|
|
<option value="z-a">Z → A</option>
|
|
<option value="category">Category</option>
|
|
<option value="author">Author</option>
|
|
<option value="newest">Newest</option>
|
|
</select>
|
|
|
|
<div class="w-px h-6 bg-gray-300"></div>
|
|
|
|
<!-- Installed filter toggle -->
|
|
<button id="store-filter-installed" class="text-sm px-3 py-1.5 rounded-md border border-gray-300 bg-white hover:bg-gray-100 transition-colors" title="Cycle: All → Installed → Not Installed">
|
|
<i class="fas fa-filter mr-1 text-gray-400"></i>All
|
|
</button>
|
|
|
|
<div class="flex-1"></div>
|
|
|
|
<!-- Active filter count + clear -->
|
|
<span id="store-active-filters" class="hidden text-xs text-blue-600 font-medium"></span>
|
|
<button id="store-clear-filters" class="hidden text-sm px-3 py-1.5 rounded-md border border-red-300 bg-white text-red-600 hover:bg-red-50 transition-colors">
|
|
<i class="fas fa-times mr-1"></i>Clear Filters
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Results Bar (top pagination) -->
|
|
<div class="flex flex-wrap items-center justify-between gap-3 mb-4">
|
|
<span id="store-results-info" class="text-sm text-gray-600"></span>
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<select id="store-per-page" aria-label="Plugins per page" class="text-sm px-2 py-1 border border-gray-300 rounded-md bg-white">
|
|
<option value="12">12 per page</option>
|
|
<option value="24">24 per page</option>
|
|
<option value="48">48 per page</option>
|
|
</select>
|
|
<div id="store-pagination-top" class="flex flex-wrap items-center gap-1"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="plugin-store-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6">
|
|
<!-- Loading skeleton — hidden by showStoreLoading(false) when data arrives -->
|
|
<div class="store-loading col-span-full">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6">
|
|
{% for _ in range(10) %}
|
|
<div class="plugin-card animate-pulse">
|
|
<div class="flex items-start justify-between mb-4">
|
|
<div class="flex-1 min-w-0 space-y-2">
|
|
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-1/2"></div>
|
|
</div>
|
|
<div class="h-5 w-14 bg-gray-200 rounded-full ml-3 flex-shrink-0"></div>
|
|
</div>
|
|
<div class="space-y-2 mb-4">
|
|
<div class="h-3 bg-gray-200 rounded w-full"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-5/6"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-4/6"></div>
|
|
</div>
|
|
<div class="flex gap-2 mt-auto">
|
|
<div class="h-3 bg-gray-200 rounded w-12"></div>
|
|
<div class="h-3 bg-gray-200 rounded w-16"></div>
|
|
</div>
|
|
<div class="h-8 bg-gray-200 rounded w-full mt-3"></div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bottom Pagination -->
|
|
<div class="flex flex-wrap items-center justify-between gap-3 mt-4">
|
|
<span id="store-results-info-bottom" class="text-sm text-gray-600"></span>
|
|
<div id="store-pagination-bottom" class="flex items-center gap-1"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Starlark Apps Section (Tronbyte Community Apps) -->
|
|
<div id="starlark-apps-section" class="border-t border-gray-200 pt-8 mt-8">
|
|
<div class="flex items-center justify-between mb-5 pb-3 border-b border-gray-200">
|
|
<div class="flex items-center gap-3">
|
|
<h3 class="text-lg font-bold text-gray-900"><i class="fas fa-star text-yellow-500 mr-2"></i>Starlark Apps</h3>
|
|
<span id="starlark-apps-count" class="text-sm text-gray-500 font-medium"></span>
|
|
</div>
|
|
<button id="toggle-starlark-section" class="text-sm text-blue-600 hover:text-blue-800 flex items-center font-medium transition-colors">
|
|
<i class="fas fa-chevron-down mr-1" id="starlark-section-icon"></i>
|
|
<span>Show</span>
|
|
</button>
|
|
</div>
|
|
<div id="starlark-section-content" class="hidden">
|
|
<p class="text-sm text-gray-600 mb-4">Browse and install Starlark apps from the <a href="https://github.com/tronbyt/apps" target="_blank" class="text-blue-600 hover:text-blue-800 underline">Tronbyte community repository</a>. Requires <strong>Pixlet</strong> binary.</p>
|
|
|
|
<!-- Pixlet Status Banner -->
|
|
<div id="starlark-pixlet-status" class="mb-4"></div>
|
|
|
|
<!-- Search Row -->
|
|
<div class="flex gap-3 mb-4">
|
|
<input type="text" id="starlark-search" placeholder="Search by name, description, author..." aria-label="Search Starlark apps" class="form-control text-sm flex-[3] min-w-0 px-4 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:shadow-md transition-shadow">
|
|
<select id="starlark-category" aria-label="Filter Starlark apps by category" class="form-control text-sm flex-1 px-3 py-2.5 border border-gray-300 rounded-lg shadow-sm focus:shadow-md transition-shadow">
|
|
<option value="">All Categories</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Sort & Filter Bar -->
|
|
<div id="starlark-filter-bar" class="flex flex-wrap items-center gap-3 mb-4 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
|
<!-- Sort -->
|
|
<select id="starlark-sort" aria-label="Sort Starlark apps" class="text-sm px-3 py-1.5 border border-gray-300 rounded-md bg-white">
|
|
<option value="a-z">A → Z</option>
|
|
<option value="z-a">Z → A</option>
|
|
<option value="category">Category</option>
|
|
<option value="author">Author</option>
|
|
</select>
|
|
|
|
<div class="w-px h-6 bg-gray-300"></div>
|
|
|
|
<!-- Installed filter toggle -->
|
|
<button id="starlark-filter-installed" class="text-sm px-3 py-1.5 rounded-md border border-gray-300 bg-white hover:bg-gray-100 transition-colors" title="Cycle: All → Installed → Not Installed">
|
|
<i class="fas fa-filter mr-1 text-gray-400"></i>All
|
|
</button>
|
|
|
|
<!-- Author filter -->
|
|
<select id="starlark-filter-author" aria-label="Filter Starlark apps by author" class="text-sm px-3 py-1.5 border border-gray-300 rounded-md bg-white">
|
|
<option value="">All Authors</option>
|
|
</select>
|
|
|
|
<div class="flex-1"></div>
|
|
|
|
<!-- Active filter count + clear -->
|
|
<span id="starlark-active-filters" class="hidden text-xs text-blue-600 font-medium"></span>
|
|
<button id="starlark-clear-filters" class="hidden text-sm px-3 py-1.5 rounded-md border border-red-300 bg-white text-red-600 hover:bg-red-50 transition-colors">
|
|
<i class="fas fa-times mr-1"></i>Clear Filters
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Results Bar (top pagination) -->
|
|
<div class="flex flex-wrap items-center justify-between gap-3 mb-4">
|
|
<span id="starlark-results-info" class="text-sm text-gray-600"></span>
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<select id="starlark-per-page" aria-label="Starlark apps per page" class="text-sm px-2 py-1 border border-gray-300 rounded-md bg-white">
|
|
<option value="24">24 per page</option>
|
|
<option value="48">48 per page</option>
|
|
<option value="96">96 per page</option>
|
|
</select>
|
|
<div id="starlark-pagination-top" class="flex flex-wrap items-center gap-1"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Starlark Apps Grid -->
|
|
<div id="starlark-apps-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6">
|
|
</div>
|
|
|
|
<!-- Bottom Pagination -->
|
|
<div class="flex flex-wrap items-center justify-between gap-3 mt-4">
|
|
<span id="starlark-results-info-bottom" class="text-sm text-gray-600"></span>
|
|
<div id="starlark-pagination-bottom" class="flex items-center gap-1"></div>
|
|
</div>
|
|
|
|
<!-- Upload .star file -->
|
|
<div class="flex gap-3 mt-6 pt-4 border-t border-gray-200">
|
|
<button id="starlark-upload-btn" class="btn bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm">
|
|
<i class="fas fa-upload mr-2"></i>Upload .star File
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Install from GitHub URL Section (Separate section, always visible) -->
|
|
<div class="border-t border-gray-200 pt-8 mt-8">
|
|
<div class="flex items-center justify-between mb-5 pb-3 border-b border-gray-200">
|
|
<div>
|
|
<h3 class="text-lg font-bold text-gray-900">Install from GitHub</h3>
|
|
<p class="text-sm text-gray-600 mt-1">Install plugins directly from GitHub repositories</p>
|
|
</div>
|
|
<button id="toggle-github-install" class="text-sm text-blue-600 hover:text-blue-800 flex items-center font-medium transition-colors">
|
|
<i class="fas fa-chevron-down mr-1" id="github-install-icon"></i>
|
|
<span>Show</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div id="github-install-section" class="hidden space-y-4">
|
|
<!-- Saved Repositories Section -->
|
|
<div class="bg-blue-50 rounded-lg p-5 border border-blue-200 shadow-sm">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h4 class="text-base font-bold text-gray-900">
|
|
<i class="fas fa-bookmark mr-2 text-blue-600"></i>Saved Repositories
|
|
</h4>
|
|
<span id="saved-repos-count" class="text-xs text-gray-600 font-medium">0 saved</span>
|
|
</div>
|
|
<p class="text-xs text-gray-600 mb-3">Saved repositories are automatically loaded and their plugins appear in the Plugin Store above.</p>
|
|
<div id="saved-repositories-list" class="space-y-2 mb-3">
|
|
<!-- Saved repositories will be loaded here -->
|
|
</div>
|
|
<button id="refresh-saved-repos" class="text-xs text-blue-600 hover:text-blue-800">
|
|
<i class="fas fa-sync mr-1"></i>Refresh
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Direct Plugin Installation -->
|
|
<div class="bg-gray-50 rounded-lg p-5 border border-gray-200 shadow-sm">
|
|
<h4 class="text-base font-bold text-gray-900 mb-3">
|
|
<i class="fas fa-code-branch mr-2 text-blue-600"></i>Install Single Plugin
|
|
</h4>
|
|
<p class="text-xs text-gray-600 mb-3">Install a plugin directly from its GitHub repository URL</p>
|
|
<div class="space-y-2">
|
|
<div class="flex gap-2">
|
|
<input type="text" id="github-plugin-url" aria-label="Plugin GitHub repository URL"
|
|
placeholder="https://github.com/user/ledmatrix-plugin-name"
|
|
class="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
|
|
<!-- Wired by attachInstallButtonHandler (plugins_manager.js); an inline
|
|
onclick here ran a second install handler on every click. -->
|
|
<button type="button" id="install-plugin-from-url"
|
|
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded-md whitespace-nowrap">
|
|
<i class="fas fa-download mr-2"></i>Install
|
|
</button>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<label for="plugin-branch-input" class="text-xs text-gray-600 whitespace-nowrap">
|
|
<i class="fas fa-code-branch mr-1"></i>Branch (optional):
|
|
</label>
|
|
<input type="text" id="plugin-branch-input"
|
|
placeholder="main, test, etc. (default: main)"
|
|
class="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
|
|
</div>
|
|
</div>
|
|
<div id="github-plugin-status" class="mt-2 text-sm"></div>
|
|
</div>
|
|
|
|
<!-- Registry-Style Monorepo Installation -->
|
|
<div class="bg-gray-50 rounded-lg p-5 border border-gray-200 shadow-sm">
|
|
<h4 class="text-base font-bold text-gray-900 mb-3">
|
|
<i class="fas fa-folder-open mr-2 text-green-600"></i>Browse Plugin Registry
|
|
</h4>
|
|
<p class="text-xs text-gray-600 mb-3">Load a registry-style monorepo (like the official ledmatrix-plugins repo) to browse and install plugins</p>
|
|
<div class="flex gap-2 mb-2">
|
|
<input type="text" id="github-registry-url" aria-label="Plugin registry GitHub URL"
|
|
placeholder="https://github.com/user/ledmatrix-plugins"
|
|
class="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500">
|
|
<button id="load-registry-from-url" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white text-sm rounded-md whitespace-nowrap">
|
|
<i class="fas fa-search mr-2"></i>Load Registry
|
|
</button>
|
|
</div>
|
|
<div class="flex gap-2 mb-3">
|
|
<button id="save-registry-url" class="px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded-md whitespace-nowrap">
|
|
<i class="fas fa-bookmark mr-2"></i>Save Repository
|
|
</button>
|
|
<div id="registry-status" class="flex-1 text-sm"></div>
|
|
</div>
|
|
<div id="custom-registry-plugins" class="hidden">
|
|
<div class="border-t border-gray-300 pt-3 mt-3">
|
|
<p class="text-xs font-medium text-gray-700 mb-2">Available Plugins:</p>
|
|
<div id="custom-registry-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
<!-- Custom registry plugins will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- On-Demand Modal moved to base.html so it's always available -->
|
|
|
|
<style>
|
|
/* View toggle button styles */
|
|
.view-toggle-btn {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.view-toggle-btn.active {
|
|
background-color: #2563eb;
|
|
color: white;
|
|
}
|
|
|
|
.view-toggle-btn:not(.active) {
|
|
color: #374151;
|
|
}
|
|
|
|
.view-toggle-btn:not(.active):hover {
|
|
background-color: #e5e7eb;
|
|
}
|
|
|
|
|
|
/* Plugin config view styles */
|
|
.plugin-config-view {
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
/* Nested config section styles */
|
|
.nested-section {
|
|
position: relative;
|
|
margin-bottom: 1.5rem;
|
|
transition: all 0.2s ease;
|
|
z-index: 1;
|
|
clear: both;
|
|
/* Contain content but allow expansion */
|
|
overflow: visible;
|
|
/* Ensure proper stacking context */
|
|
isolation: isolate;
|
|
}
|
|
|
|
.nested-section button {
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
|
|
.nested-section button:hover {
|
|
background-color: #f3f4f6;
|
|
}
|
|
|
|
.nested-section .nested-content {
|
|
position: relative;
|
|
transition: max-height 0.3s ease, opacity 0.3s ease;
|
|
overflow: hidden;
|
|
z-index: 1;
|
|
/* Ensure content doesn't get clipped by parent */
|
|
min-height: 0;
|
|
/* Contain content properly */
|
|
contain: layout style;
|
|
}
|
|
|
|
/* When expanded, allow content to flow naturally */
|
|
.nested-content.expanded {
|
|
overflow: visible;
|
|
/* Ensure expanded content is fully visible */
|
|
min-height: auto;
|
|
}
|
|
|
|
.nested-section i {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
/* Smooth toggle animation */
|
|
.nested-content.collapsed {
|
|
opacity: 0;
|
|
max-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.nested-content.expanded {
|
|
opacity: 1;
|
|
max-height: none !important; /* Remove height constraint to allow natural expansion */
|
|
padding-bottom: 1rem !important; /* Ensure proper padding at bottom to prevent cutoff */
|
|
overflow: visible; /* Allow content to flow naturally when expanded */
|
|
margin-bottom: 0.5rem; /* Add spacing at bottom when expanded */
|
|
}
|
|
|
|
/* Nested sections within nested sections - add indentation and spacing */
|
|
.nested-content .nested-section {
|
|
margin-left: 1rem;
|
|
margin-bottom: 1.5rem; /* Increased spacing to prevent overlap */
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
/* Deeply nested sections need even more spacing */
|
|
.nested-content .nested-content .nested-section {
|
|
margin-bottom: 2rem;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
/* Form group spacing within nested sections */
|
|
.nested-content .form-group {
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
/* Ensure form-groups that come after nested sections have proper spacing */
|
|
.nested-section + .form-group {
|
|
margin-top: 1.5rem !important;
|
|
position: relative;
|
|
z-index: 0;
|
|
clear: both;
|
|
/* Ensure form-group doesn't overlap */
|
|
display: block;
|
|
width: 100%;
|
|
/* Ensure it's in normal document flow */
|
|
float: none;
|
|
}
|
|
|
|
/* Ensure any content after nested sections is properly spaced */
|
|
.nested-section ~ .form-group {
|
|
clear: both !important;
|
|
position: relative;
|
|
z-index: 0;
|
|
display: block;
|
|
/* Prevent overlap */
|
|
margin-top: 1.5rem !important;
|
|
width: 100%;
|
|
float: none;
|
|
}
|
|
|
|
/* Make nested section headers slightly smaller for hierarchy */
|
|
.nested-content .nested-section h4 {
|
|
font-size: 0.95em;
|
|
}
|
|
</style>
|
|
|