updated sidebar

This commit is contained in:
buenosairesam
2026-01-27 00:06:29 -03:00
parent cae5a913ca
commit fecb978a5f
5 changed files with 842 additions and 714 deletions

View File

@@ -1,295 +1,201 @@
// Soleprint Wrapper - Sidebar Logic
// Soleprint Sidebar - Self-Injecting Script
// This script creates and manages the soleprint sidebar when injected into a managed app
class SoleprintSidebar {
constructor() {
this.config = null;
this.currentUser = null;
this.sidebar = null;
this.toggleBtn = null;
}
(function () {
"use strict";
async init() {
// Load configuration
await this.loadConfig();
const SPR_BASE = "/spr";
// Create sidebar elements
this.createSidebar();
this.createToggleButton();
let config = null;
let user = null;
let expanded = localStorage.getItem("spr_sidebar_expanded") === "true";
// Setup event listeners
this.setupEventListeners();
// Icons as SVG strings
const icons = {
toggle: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M4 6h16M4 12h16M4 18h16"/>
</svg>`,
home: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 12l9-9 9 9M5 10v10a1 1 0 001 1h3m10-11v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1"/>
</svg>`,
soleprint: `<svg viewBox="0 0 24 24" fill="currentColor">
<ellipse cx="8" cy="10" rx="3" ry="5" transform="rotate(-10 8 10)"/>
<ellipse cx="8" cy="17" rx="2.5" ry="3" transform="rotate(-10 8 17)"/>
<ellipse cx="16" cy="8" rx="3" ry="5" transform="rotate(10 16 8)"/>
<ellipse cx="16" cy="15" rx="2.5" ry="3" transform="rotate(10 16 15)"/>
</svg>`,
artery: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2v8M12 10L6 18M12 10l6 8"/>
<circle cx="12" cy="10" r="2"/>
</svg>`,
atlas: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="8"/>
<path d="M12 4v16M4 12h16"/>
</svg>`,
station: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="4" y="6" width="16" height="12" rx="1"/>
<circle cx="9" cy="11" r="2"/>
<circle cx="15" cy="11" r="2"/>
</svg>`,
google: `<svg viewBox="0 0 24 24">
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>`,
user: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="8" r="4"/>
<path d="M4 20c0-4 4-6 8-6s8 2 8 6"/>
</svg>`,
logout: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9"/>
</svg>`,
};
// Check if user is already logged in
this.checkCurrentUser();
// Load saved sidebar state
this.loadSidebarState();
}
async loadConfig() {
async function init() {
try {
const response = await fetch("/wrapper/config.json");
this.config = await response.json();
console.log("[Soleprint] Config loaded:", this.config.nest_name);
} catch (error) {
console.error("[Soleprint] Failed to load config:", error);
// Use default config
this.config = {
nest_name: "default",
wrapper: {
environment: {
backend_url: "http://localhost:8000",
frontend_url: "http://localhost:3000",
},
users: [],
},
};
}
}
// Load config
const response = await fetch(`${SPR_BASE}/api/sidebar/config`);
config = await response.json();
createSidebar() {
const sidebar = document.createElement("div");
sidebar.id = "pawprint-sidebar";
sidebar.innerHTML = this.getSidebarHTML();
document.body.appendChild(sidebar);
this.sidebar = sidebar;
}
// Check auth status
await checkAuth();
createToggleButton() {
const button = document.createElement("button");
button.id = "sidebar-toggle";
button.innerHTML = '<span class="icon">◀</span>';
button.title = "Toggle Soleprint Sidebar (Ctrl+Shift+P)";
document.body.appendChild(button);
this.toggleBtn = button;
}
// Create sidebar
createSidebar();
getSidebarHTML() {
const users = this.config.wrapper.users || [];
return `
<div class="sidebar-header">
<h2>🐾 Soleprint</h2>
<div class="nest-name">${this.config.nest_name}</div>
</div>
<div class="sidebar-content">
<div id="status-container"></div>
<!-- Quick Login Panel -->
<div class="panel">
<h3>👤 Quick Login</h3>
<div id="current-user-display" style="display: none;">
<div class="current-user">
Logged in as: <strong id="current-username"></strong>
<button class="logout-btn" onclick="soleprintSidebar.logout()">
Logout
</button>
</div>
</div>
<div class="user-cards">
${users
.map(
(user) => `
<div class="user-card" data-user-id="${user.id}" onclick="soleprintSidebar.loginAs('${user.id}')">
<div class="icon">${user.icon}</div>
<div class="info">
<span class="label">${user.label}</span>
<span class="role">${user.role}</span>
</div>
</div>
`,
)
.join("")}
</div>
</div>
<!-- Environment Info Panel -->
<div class="panel">
<h3>🌍 Environment</h3>
<div style="font-size: 12px; opacity: 0.8;">
<div style="margin-bottom: 8px;">
<strong>Backend:</strong><br>
<code style="font-size: 11px;">${this.config.wrapper.environment.backend_url}</code>
</div>
<div>
<strong>Frontend:</strong><br>
<code style="font-size: 11px;">${this.config.wrapper.environment.frontend_url}</code>
</div>
</div>
</div>
</div>
<div class="sidebar-footer">
Soleprint Dev Tools
</div>
`;
}
setupEventListeners() {
// Toggle button
this.toggleBtn.addEventListener("click", () => this.toggle());
// Keyboard shortcut: Ctrl+Shift+P
document.addEventListener("keydown", (e) => {
if (e.ctrlKey && e.shiftKey && e.key === "P") {
e.preventDefault();
this.toggle();
}
});
}
toggle() {
this.sidebar.classList.toggle("expanded");
this.saveSidebarState();
}
saveSidebarState() {
const isExpanded = this.sidebar.classList.contains("expanded");
localStorage.setItem("pawprint_sidebar_expanded", isExpanded);
}
loadSidebarState() {
const isExpanded =
localStorage.getItem("pawprint_sidebar_expanded") === "true";
if (isExpanded) {
this.sidebar.classList.add("expanded");
}
}
showStatus(message, type = "info") {
const container = document.getElementById("status-container");
const statusDiv = document.createElement("div");
statusDiv.className = `status-message ${type}`;
statusDiv.textContent = message;
container.innerHTML = "";
container.appendChild(statusDiv);
// Auto-remove after 5 seconds
setTimeout(() => {
statusDiv.remove();
}, 5000);
}
async loginAs(userId) {
const user = this.config.wrapper.users.find((u) => u.id === userId);
if (!user) return;
this.showStatus(`Logging in as ${user.label}... ⏳`, "info");
try {
const backendUrl = this.config.wrapper.environment.backend_url;
const response = await fetch(`${backendUrl}/api/token/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: user.username,
password: user.password,
}),
// Setup keyboard shortcut
document.addEventListener("keydown", (e) => {
if (e.ctrlKey && e.shiftKey && e.key === "S") {
e.preventDefault();
toggleSidebar();
}
});
if (!response.ok) {
throw new Error(`Login failed: ${response.status}`);
}
console.log("[Soleprint] Sidebar initialized for room:", config.room);
} catch (error) {
console.error("[Soleprint] Failed to initialize sidebar:", error);
}
}
async function checkAuth() {
if (!config.auth_enabled) return;
try {
const response = await fetch(`${SPR_BASE}/artery/google/oauth/status`, {
credentials: "include",
});
const data = await response.json();
// Store tokens
localStorage.setItem("access_token", data.access);
localStorage.setItem("refresh_token", data.refresh);
// Store user info
localStorage.setItem(
"user_info",
JSON.stringify({
username: user.username,
label: user.label,
role: data.details?.role || user.role,
}),
);
this.showStatus(`✓ Logged in as ${user.label}`, "success");
this.currentUser = user;
this.updateCurrentUserDisplay();
// Reload page after short delay
setTimeout(() => {
window.location.reload();
}, 1000);
if (data.authenticated) {
user = data.user;
}
} catch (error) {
console.error("[Soleprint] Login error:", error);
this.showStatus(`✗ Login failed: ${error.message}`, "error");
console.log("[Soleprint] Auth check failed:", error);
}
}
logout() {
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
localStorage.removeItem("user_info");
this.showStatus("✓ Logged out", "success");
this.currentUser = null;
this.updateCurrentUserDisplay();
// Reload page after short delay
setTimeout(() => {
window.location.reload();
}, 1000);
}
checkCurrentUser() {
const userInfo = localStorage.getItem("user_info");
if (userInfo) {
try {
this.currentUser = JSON.parse(userInfo);
this.updateCurrentUserDisplay();
} catch (error) {
console.error("[Soleprint] Failed to parse user info:", error);
}
function createSidebar() {
// Add body class
document.body.classList.add("spr-sidebar-active");
if (expanded) {
document.body.classList.add("spr-sidebar-expanded");
}
const sidebar = document.createElement("div");
sidebar.id = "spr-sidebar";
if (expanded) sidebar.classList.add("expanded");
sidebar.innerHTML = `
<div class="spr-sidebar-header">
<button class="spr-sidebar-item" onclick="sprSidebar.toggle()" title="Toggle sidebar (Ctrl+Shift+S)">
${icons.toggle}
<span class="label">Menu</span>
</button>
<div class="room-name">${config.room}</div>
</div>
<a href="/" class="spr-sidebar-item" title="Home">
${icons.home}
<span class="label">Home</span>
</a>
<div class="spr-sidebar-divider"></div>
<a href="${SPR_BASE}/" class="spr-sidebar-item" title="Soleprint">
${icons.soleprint}
<span class="label">Soleprint</span>
</a>
<a href="${config.tools.artery}" class="spr-sidebar-item" title="Artery">
${icons.artery}
<span class="label">Artery</span>
</a>
<a href="${config.tools.atlas}" class="spr-sidebar-item" title="Atlas">
${icons.atlas}
<span class="label">Atlas</span>
</a>
<a href="${config.tools.station}" class="spr-sidebar-item" title="Station">
${icons.station}
<span class="label">Station</span>
</a>
<div class="spr-sidebar-spacer"></div>
${config.auth_enabled ? renderAuthSection() : ""}
`;
document.body.appendChild(sidebar);
}
updateCurrentUserDisplay() {
const display = document.getElementById("current-user-display");
const username = document.getElementById("current-username");
if (this.currentUser) {
display.style.display = "block";
username.textContent = this.currentUser.username;
// Highlight active user card
document.querySelectorAll(".user-card").forEach((card) => {
card.classList.remove("active");
});
const activeCard = document.querySelector(
`.user-card[data-user-id="${this.getUserIdByUsername(this.currentUser.username)}"]`,
);
if (activeCard) {
activeCard.classList.add("active");
}
function renderAuthSection() {
if (user) {
return `
<div class="spr-sidebar-user">
<div class="spr-sidebar-item" title="${user.email}">
${icons.user}
<span class="label">${user.email}</span>
</div>
<a href="${config.auth.logout_url}" class="spr-sidebar-item" title="Logout">
${icons.logout}
<span class="label">Logout</span>
</a>
</div>
`;
} else {
display.style.display = "none";
return `
<div class="spr-sidebar-user">
<a href="${config.auth.login_url}" class="spr-sidebar-item spr-login-btn" title="Login with Google">
${icons.google}
<span class="label">Login with Google</span>
</a>
</div>
`;
}
}
getUserIdByUsername(username) {
const user = this.config.wrapper.users.find((u) => u.username === username);
return user ? user.id : null;
function toggleSidebar() {
const sidebar = document.getElementById("spr-sidebar");
expanded = !expanded;
sidebar.classList.toggle("expanded", expanded);
document.body.classList.toggle("spr-sidebar-expanded", expanded);
localStorage.setItem("spr_sidebar_expanded", expanded);
}
}
// Initialize sidebar when DOM is ready
const soleprintSidebar = new SoleprintSidebar();
// Expose to global scope for onclick handlers
window.sprSidebar = {
toggle: toggleSidebar,
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => soleprintSidebar.init());
} else {
soleprintSidebar.init();
}
// Initialize when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
console.log("[Soleprint] Sidebar script loaded");
console.log("[Soleprint] Sidebar script loaded");
})();