updated sidebar
This commit is contained in:
@@ -1,296 +1,161 @@
|
||||
/* Pawprint Wrapper - Sidebar Styles */
|
||||
/* Soleprint Sidebar - Injected Styles */
|
||||
|
||||
:root {
|
||||
--sidebar-width: 320px;
|
||||
--sidebar-bg: #1e1e1e;
|
||||
--sidebar-text: #e0e0e0;
|
||||
--sidebar-accent: #007acc;
|
||||
--sidebar-border: #333;
|
||||
--sidebar-shadow: 0 0 20px rgba(0,0,0,0.5);
|
||||
--card-bg: #2a2a2a;
|
||||
--card-hover: #3a3a3a;
|
||||
--success: #4caf50;
|
||||
--error: #f44336;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
--spr-sidebar-width: 60px;
|
||||
--spr-sidebar-width-expanded: 280px;
|
||||
--spr-sidebar-bg: #1a1a1a;
|
||||
--spr-sidebar-text: #e5e5e5;
|
||||
--spr-sidebar-text-muted: #a3a3a3;
|
||||
--spr-sidebar-border: #333;
|
||||
--spr-sidebar-accent: #d4a574;
|
||||
--spr-sidebar-hover: #2a2a2a;
|
||||
}
|
||||
|
||||
/* Sidebar Container */
|
||||
#pawprint-sidebar {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: var(--sidebar-width);
|
||||
height: 100vh;
|
||||
background: var(--sidebar-bg);
|
||||
color: var(--sidebar-text);
|
||||
box-shadow: var(--sidebar-shadow);
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 9999;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
#spr-sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: var(--spr-sidebar-width);
|
||||
height: 100vh;
|
||||
background: var(--spr-sidebar-bg);
|
||||
border-right: 1px solid var(--spr-sidebar-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 99999;
|
||||
transition: width 0.2s ease;
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
#pawprint-sidebar.expanded {
|
||||
transform: translateX(0);
|
||||
#spr-sidebar.expanded {
|
||||
width: var(--spr-sidebar-width-expanded);
|
||||
}
|
||||
|
||||
/* Toggle Button */
|
||||
#sidebar-toggle {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: var(--sidebar-bg);
|
||||
color: var(--sidebar-text);
|
||||
border: 1px solid var(--sidebar-border);
|
||||
border-right: none;
|
||||
border-radius: 8px 0 0 8px;
|
||||
padding: 12px 8px;
|
||||
cursor: pointer;
|
||||
z-index: 10000;
|
||||
font-size: 16px;
|
||||
transition: background 0.2s;
|
||||
box-shadow: -2px 0 8px rgba(0,0,0,0.3);
|
||||
/* Push page content */
|
||||
body.spr-sidebar-active {
|
||||
margin-left: var(--spr-sidebar-width) !important;
|
||||
transition: margin-left 0.2s ease;
|
||||
}
|
||||
|
||||
#sidebar-toggle:hover {
|
||||
background: var(--card-hover);
|
||||
body.spr-sidebar-active.spr-sidebar-expanded {
|
||||
margin-left: var(--spr-sidebar-width-expanded) !important;
|
||||
}
|
||||
|
||||
#sidebar-toggle .icon {
|
||||
display: block;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
#pawprint-sidebar.expanded ~ #sidebar-toggle .icon {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.sidebar-header {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--sidebar-border);
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);
|
||||
}
|
||||
|
||||
.sidebar-header h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
color: var(--sidebar-accent);
|
||||
}
|
||||
|
||||
.sidebar-header .nest-name {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.sidebar-content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Panel */
|
||||
.panel {
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
background: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--sidebar-border);
|
||||
}
|
||||
|
||||
.panel h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
color: var(--sidebar-accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Current User Display */
|
||||
.current-user {
|
||||
padding: 12px;
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
border: 1px solid rgba(76, 175, 80, 0.3);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.current-user strong {
|
||||
color: var(--success);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.current-user .logout-btn {
|
||||
margin-top: 8px;
|
||||
padding: 6px 12px;
|
||||
background: rgba(244, 67, 54, 0.1);
|
||||
border: 1px solid rgba(244, 67, 54, 0.3);
|
||||
color: var(--error);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.2s;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.current-user .logout-btn:hover {
|
||||
background: rgba(244, 67, 54, 0.2);
|
||||
}
|
||||
|
||||
/* User Cards */
|
||||
.user-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--sidebar-border);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.user-card:hover {
|
||||
background: var(--card-hover);
|
||||
border-color: var(--sidebar-accent);
|
||||
transform: translateX(-2px);
|
||||
}
|
||||
|
||||
.user-card.active {
|
||||
background: rgba(0, 122, 204, 0.2);
|
||||
border-color: var(--sidebar-accent);
|
||||
}
|
||||
|
||||
.user-card .icon {
|
||||
font-size: 24px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.user-card .info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-card .label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.user-card .role {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
opacity: 0.6;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Status Messages */
|
||||
.status-message {
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.status-message.success {
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
border-color: rgba(76, 175, 80, 0.3);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.status-message.error {
|
||||
background: rgba(244, 67, 54, 0.1);
|
||||
border-color: rgba(244, 67, 54, 0.3);
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.status-message.info {
|
||||
background: rgba(0, 122, 204, 0.1);
|
||||
border-color: rgba(0, 122, 204, 0.3);
|
||||
color: var(--sidebar-accent);
|
||||
}
|
||||
|
||||
/* Loading Spinner */
|
||||
.loading {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid rgba(255,255,255,0.1);
|
||||
border-top-color: var(--sidebar-accent);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Scrollbar */
|
||||
#pawprint-sidebar::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#pawprint-sidebar::-webkit-scrollbar-track {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
#pawprint-sidebar::-webkit-scrollbar-thumb {
|
||||
background: #444;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#pawprint-sidebar::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.sidebar-footer {
|
||||
padding: 16px 20px;
|
||||
border-top: 1px solid var(--sidebar-border);
|
||||
font-size: 11px;
|
||||
opacity: 0.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
#pawprint-sidebar {
|
||||
/* Sidebar Items */
|
||||
.spr-sidebar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 18px;
|
||||
color: var(--spr-sidebar-text-muted);
|
||||
text-decoration: none;
|
||||
transition: all 0.15s;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
}
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.spr-sidebar-item:hover {
|
||||
background: var(--spr-sidebar-hover);
|
||||
color: var(--spr-sidebar-text);
|
||||
}
|
||||
|
||||
.spr-sidebar-item.active {
|
||||
background: var(--spr-sidebar-accent);
|
||||
color: #0a0a0a;
|
||||
}
|
||||
|
||||
.spr-sidebar-item svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.spr-sidebar-item .label {
|
||||
display: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#spr-sidebar.expanded .spr-sidebar-item .label {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Divider */
|
||||
.spr-sidebar-divider {
|
||||
height: 1px;
|
||||
background: var(--spr-sidebar-border);
|
||||
margin: 8px 16px;
|
||||
}
|
||||
|
||||
/* Spacer */
|
||||
.spr-sidebar-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* User Section */
|
||||
.spr-sidebar-user {
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid var(--spr-sidebar-border);
|
||||
font-size: 12px;
|
||||
color: var(--spr-sidebar-text-muted);
|
||||
}
|
||||
|
||||
.spr-sidebar-user .email {
|
||||
display: none;
|
||||
margin-top: 4px;
|
||||
color: var(--spr-sidebar-text);
|
||||
}
|
||||
|
||||
#spr-sidebar.expanded .spr-sidebar-user .email {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Login Button */
|
||||
.spr-login-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
background: #4285f4;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.spr-login-btn:hover {
|
||||
background: #3367d6;
|
||||
}
|
||||
|
||||
.spr-login-btn svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* Header with room name */
|
||||
.spr-sidebar-header {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--spr-sidebar-border);
|
||||
}
|
||||
|
||||
.spr-sidebar-header .room-name {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: var(--spr-sidebar-accent);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
#spr-sidebar.expanded .spr-sidebar-header .room-name {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user