mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2025-12-24 06:28:55 -05:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16e823b8d3 | ||
|
|
f2f11e3472 | ||
|
|
a3549c80a9 | ||
|
|
2b9c347ed6 | ||
|
|
98ccee866d |
30
.github/workflows/cloudflare-pages-status.yml
vendored
Normal file
30
.github/workflows/cloudflare-pages-status.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Deploy to Cloudflare Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy to Cloudflare Pages
|
||||
|
||||
steps:
|
||||
- name: Create status files
|
||||
run: |
|
||||
mkdir -p status
|
||||
echo "{ \"version\": \"${GITHUB_REF_NAME}\" }" > status/status.json
|
||||
|
||||
# Cache static files for 10 minutes
|
||||
cat > status/_headers << 'EOF'
|
||||
/*
|
||||
Cache-Control: public, max-age=600, s-maxage=600
|
||||
EOF
|
||||
|
||||
- name: Deploy to Cloudflare Pages
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_PAGES_TOKEN }}
|
||||
workingDirectory: "status"
|
||||
command: pages deploy . --project-name=cleanuparr-status
|
||||
1
.github/workflows/docs.yml
vendored
1
.github/workflows/docs.yml
vendored
@@ -22,6 +22,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
@@ -157,12 +157,12 @@ public static class ApiDI
|
||||
icons = new[]
|
||||
{
|
||||
new {
|
||||
src = "assets/icons/icon-192x192.png",
|
||||
src = "icons/icon-192x192.png",
|
||||
sizes = "192x192",
|
||||
type = "image/png"
|
||||
},
|
||||
new {
|
||||
src = "assets/icons/icon-512x512.png",
|
||||
src = "icons/icon-512x512.png",
|
||||
sizes = "512x512",
|
||||
type = "image/png"
|
||||
}
|
||||
|
||||
@@ -6,13 +6,11 @@
|
||||
<p-confirmDialog></p-confirmDialog>
|
||||
<!-- Main layout with sidebar and content -->
|
||||
<div class="layout-main">
|
||||
<!-- Desktop Sidebar -->
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-top-line"></div>
|
||||
|
||||
<!-- Shared Sidebar Content -->
|
||||
<app-sidebar-content
|
||||
[isMobile]="false">
|
||||
#sidebarContent
|
||||
[isMobile]="false"
|
||||
[enableMobileDrawer]="true">
|
||||
</app-sidebar-content>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +22,7 @@
|
||||
<div class="topbar-section left">
|
||||
<button pButton class="p-button-text sidebar-toggle-mobile"
|
||||
icon="pi pi-bars"
|
||||
(click)="mobileSidebarVisible.set(true)"></button>
|
||||
(click)="sidebarContent.toggleMobileDrawer()"></button>
|
||||
</div>
|
||||
<!-- <div class="topbar-section center">
|
||||
<div class="version-bar">
|
||||
@@ -63,25 +61,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Sidebar using PrimeNG sidebar component -->
|
||||
<p-drawer
|
||||
styleClass="mobile-sidebar"
|
||||
[visible]="mobileSidebarVisible()"
|
||||
(visibleChange)="mobileSidebarVisible.set($event)"
|
||||
[dismissible]="true"
|
||||
[showCloseIcon]="false"
|
||||
position="left">
|
||||
<ng-template #headless>
|
||||
<div class="mobile-sidebar-content">
|
||||
<div class="sidebar-top-line"></div>
|
||||
|
||||
<!-- Shared Sidebar Content for Mobile -->
|
||||
<app-sidebar-content
|
||||
[isMobile]="true"
|
||||
(navItemClicked)="mobileSidebarVisible.set(false)">
|
||||
</app-sidebar-content>
|
||||
</div>
|
||||
</ng-template>
|
||||
</p-drawer>
|
||||
</div>
|
||||
|
||||
@@ -15,208 +15,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Main Sidebar Styling
|
||||
.sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 260px;
|
||||
min-width: 260px;
|
||||
height: 100vh;
|
||||
background-color: var(--surface-overlay);
|
||||
border-right: 1px solid var(--surface-border);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
overflow-y: auto;
|
||||
z-index: 10;
|
||||
|
||||
// Top color line
|
||||
.sidebar-top-line {
|
||||
height: 3px;
|
||||
background: var(--primary-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
// Hide on mobile screens
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Top color line
|
||||
.sidebar-top-line {
|
||||
height: 3px;
|
||||
background: linear-gradient(to right, #673ab7, #9b59b6);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
// Logo container
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin-right: 15px;
|
||||
background-color: rgba(142, 68, 173, 0.1);
|
||||
border: 1px solid rgba(142, 68, 173, 0.3);
|
||||
animation: logo-glow 10s infinite ease-in-out;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
color: #a569bd;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
background: linear-gradient(to right, #fff, #bbb);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation menu
|
||||
.nav-menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 5px 0 20px 0;
|
||||
flex: 1;
|
||||
|
||||
// Sponsor link
|
||||
.sponsor-link {
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 1px solid rgba(142, 68, 173, 0.15);
|
||||
padding-bottom: 15px !important;
|
||||
|
||||
.heart-icon i {
|
||||
color: #ff4d6d !important;
|
||||
animation: heart-pulse 7.2s infinite ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
// Nav groups
|
||||
.nav-group {
|
||||
margin-bottom: 15px;
|
||||
|
||||
.nav-group-title {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: rgba(142, 68, 173, 0.8);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
padding: 0 20px 8px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 1px solid rgba(142, 68, 173, 0.2);
|
||||
text-shadow: 0 0 10px rgba(142, 68, 173, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation items
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
color: #e0e0e0;
|
||||
text-decoration: none;
|
||||
border-radius: 0 6px 6px 0;
|
||||
margin: 2px 0 2px 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.nav-icon-wrapper {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
border-radius: 8px;
|
||||
background: rgba(46, 39, 56, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 3px;
|
||||
background: transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(142, 68, 173, 0.15);
|
||||
transform: translateX(2px);
|
||||
|
||||
.nav-icon-wrapper {
|
||||
background: rgba(142, 68, 173, 0.2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: linear-gradient(to bottom, rgba(142, 68, 173, 0.4), rgba(103, 58, 183, 0.4));
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: linear-gradient(to right, rgba(142, 68, 173, 0.9), rgba(103, 58, 183, 0.7));
|
||||
box-shadow: 0 2px 10px rgba(142, 68, 173, 0.3);
|
||||
|
||||
.nav-icon-wrapper {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animation keyframes
|
||||
@keyframes logo-glow {
|
||||
0% { box-shadow: 0 0 15px #8e44ad, 0 0 5px rgba(142, 68, 173, 0.4); } // Purple
|
||||
20% { box-shadow: 0 0 15px #9b59b6, 0 0 5px rgba(155, 89, 182, 0.4); } // Lighter purple
|
||||
40% { box-shadow: 0 0 15px #673ab7, 0 0 5px rgba(103, 58, 183, 0.4); } // Deep purple
|
||||
60% { box-shadow: 0 0 15px #5e35b1, 0 0 5px rgba(94, 53, 177, 0.4); } // Dark deep purple
|
||||
80% { box-shadow: 0 0 15px #7e57c2, 0 0 5px rgba(126, 87, 194, 0.4); } // Medium purple
|
||||
100% { box-shadow: 0 0 15px #8e44ad, 0 0 5px rgba(142, 68, 173, 0.4); } // Back to purple
|
||||
}
|
||||
|
||||
@keyframes heart-pulse {
|
||||
0% { color: #ff4d6d; text-shadow: 0 0 10px #ff4d6d, 0 0 20px #ff4d6d; } // Pink
|
||||
50% { color: #ff0a33; text-shadow: 0 0 15px #ff0a33, 0 0 25px #ff0a33; } // Red
|
||||
100% { color: #ff4d6d; text-shadow: 0 0 10px #ff4d6d, 0 0 20px #ff4d6d; } // Pink
|
||||
}
|
||||
// Animation keyframes (logo-glow and heart-pulse moved to sidebar-content component)
|
||||
|
||||
@keyframes pulse-heart {
|
||||
0% {
|
||||
@@ -375,22 +174,6 @@
|
||||
|
||||
// Deep styling overrides for PrimeNG components
|
||||
:host ::ng-deep {
|
||||
// Mobile sidebar styling
|
||||
.p-sidebar.p-component.mobile-sidebar {
|
||||
background-color: var(--surface-overlay) !important;
|
||||
border: none !important;
|
||||
color: var(--text-color) !important;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2) !important;
|
||||
|
||||
.p-sidebar-header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.p-sidebar-content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Override Prime NG button styles
|
||||
.p-button {
|
||||
&.p-button-text {
|
||||
@@ -419,453 +202,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile sidebar content
|
||||
.mobile-sidebar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--surface-overlay);
|
||||
color: var(--text-color);
|
||||
height: 100%;
|
||||
|
||||
.sidebar-top-line {
|
||||
height: 3px;
|
||||
background: var(--primary-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 5px;
|
||||
|
||||
.sidebar-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin-right: 15px;
|
||||
background-color: rgba(142, 68, 173, 0.1);
|
||||
border: 1px solid rgba(142, 68, 173, 0.3);
|
||||
animation: logo-glow 10s infinite ease-in-out;
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
color: #a569bd;
|
||||
}
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin-left: 0.5rem;
|
||||
background: linear-gradient(to right, #fff, #bbb);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 5px 0 20px 0;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
|
||||
.sidebar-section-title {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: rgba(142, 68, 173, 0.8);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
padding: 0 20px 8px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
border-bottom: 1px solid rgba(142, 68, 173, 0.2);
|
||||
text-shadow: 0 0 10px rgba(142, 68, 173, 0.3);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.sidebar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
color: #e0e0e0;
|
||||
text-decoration: none;
|
||||
border-radius: 0 6px 6px 0;
|
||||
margin: 2px 0 2px 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.item-icon-wrapper {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
border-radius: 8px;
|
||||
background: rgba(46, 39, 56, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
font-size: 14px;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 3px;
|
||||
background: transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(142, 68, 173, 0.15);
|
||||
transform: translateX(2px);
|
||||
|
||||
.item-icon-wrapper {
|
||||
background: rgba(142, 68, 173, 0.2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
&::before {
|
||||
background: linear-gradient(to bottom, rgba(142, 68, 173, 0.4), rgba(103, 58, 183, 0.4));
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
border-left: 3px solid #4299e1;
|
||||
padding-left: calc(1rem - 3px);
|
||||
|
||||
i {
|
||||
color: #4299e1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sponsor-section {
|
||||
padding: 0.5rem 1rem 1rem;
|
||||
margin-top: auto;
|
||||
|
||||
.sponsor-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
i {
|
||||
color: #ff5a5f;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main layout
|
||||
.layout-main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
// Sidebar
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
background: linear-gradient(135deg, #2c1a47 0%, #1a0e29 50%, #1e1230 75%, rgba(30, 18, 48, 0.95) 100%);
|
||||
background-size: 100% 100%;
|
||||
border-right: 1px solid rgba(142, 68, 173, 0.15);
|
||||
box-shadow: 2px 0 15px rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 1000;
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(5px);
|
||||
|
||||
// Colored edge accents
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background: linear-gradient(to bottom, rgba(142, 68, 173, 0.6), rgba(103, 58, 183, 0.6)); // Purple gradient
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.sidebar-top-line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, rgba(142, 68, 173, 0.6), rgba(103, 58, 183, 0.6)); // Purple gradient
|
||||
z-index: 2;
|
||||
box-shadow: 0 0 8px rgba(142, 68, 173, 0.4); // Add subtle glow
|
||||
}
|
||||
// Sidebar container - all styling moved to sidebar-content component
|
||||
// The sidebar-content component now handles all visual styling via :host selector
|
||||
|
||||
// Transitions for text elements in expanded state
|
||||
.app-name,
|
||||
.sidebar-section-title,
|
||||
.sidebar-nav span,
|
||||
.sponsor-link span {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
width: auto;
|
||||
max-width: 180px;
|
||||
white-space: nowrap;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
width: 70px;
|
||||
|
||||
.app-name,
|
||||
.sidebar-section-title,
|
||||
.sidebar-nav span,
|
||||
.sponsor-link span {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar-nav li a {
|
||||
padding: 0.75rem 0;
|
||||
justify-content: center;
|
||||
|
||||
i {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sponsor-section .sponsor-link {
|
||||
padding: 0.75rem 0;
|
||||
justify-content: center;
|
||||
|
||||
i {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
// Logo container
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15px 20px 0px;
|
||||
border-bottom: none;
|
||||
margin-bottom: -15px;
|
||||
position: relative;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-right: 15px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(142, 68, 173, 0.1);
|
||||
border: 1px solid rgba(142, 68, 173, 0.3);
|
||||
animation: logo-glow 10s infinite ease-in-out;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
i {
|
||||
font-size: 30px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-small {
|
||||
display: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
letter-spacing: 0.5px;
|
||||
background: linear-gradient(to right, #fff, #bbb);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// Sponsor section
|
||||
.sponsor-section {
|
||||
padding: 0.5rem 1rem 1rem;
|
||||
|
||||
.sponsor-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
i {
|
||||
color: #ff5a5f;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sidebar footer with toggle button
|
||||
.sidebar-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 1rem;
|
||||
margin-top: auto;
|
||||
|
||||
.sidebar-toggle {
|
||||
color: #ffffff;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sidebar navigation
|
||||
.sidebar-nav {
|
||||
padding: 1rem 0;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
|
||||
.sidebar-section {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
.sidebar-section-title {
|
||||
padding: 0 1rem;
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.25rem;
|
||||
width: 1.5rem;
|
||||
margin-right: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 0.95rem;
|
||||
transition: opacity 0.2s ease, width 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
&.active a {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
border-left: 3px solid #4299e1;
|
||||
padding-left: calc(1rem - 3px);
|
||||
|
||||
i {
|
||||
color: #4299e1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sidebar navigation styles moved to sidebar-content component
|
||||
|
||||
// Content area
|
||||
.layout-content {
|
||||
@@ -1196,39 +541,12 @@
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes logo-glow {
|
||||
0% { box-shadow: 0 0 15px #8e44ad, 0 0 5px rgba(142, 68, 173, 0.4); } // Purple
|
||||
20% { box-shadow: 0 0 15px #9b59b6, 0 0 5px rgba(155, 89, 182, 0.4); } // Lighter purple
|
||||
40% { box-shadow: 0 0 15px #673ab7, 0 0 5px rgba(103, 58, 183, 0.4); } // Deep purple
|
||||
60% { box-shadow: 0 0 15px #5e35b1, 0 0 5px rgba(94, 53, 177, 0.4); } // Dark deep purple
|
||||
80% { box-shadow: 0 0 15px #7e57c2, 0 0 5px rgba(126, 87, 194, 0.4); } // Medium purple
|
||||
100% { box-shadow: 0 0 15px #8e44ad, 0 0 5px rgba(142, 68, 173, 0.4); } // Back to purple
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile sidebar styling
|
||||
.mobile-sidebar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #2c1a47 0%, #1a0e29 50%, #1e1230 75%, rgba(30, 18, 48, 0.95) 100%);
|
||||
overflow-y: auto;
|
||||
|
||||
.sidebar-top-line {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, rgba(142, 68, 173, 0.6), rgba(103, 58, 183, 0.6));
|
||||
z-index: 2;
|
||||
box-shadow: 0 0 8px rgba(142, 68, 173, 0.4);
|
||||
}
|
||||
}
|
||||
// Mobile sidebar styling moved to sidebar-content component
|
||||
|
||||
/* Media queries for responsiveness */
|
||||
@media screen and (max-width: 768px) {
|
||||
@@ -1252,16 +570,5 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Style PrimeNG mobile sidebar
|
||||
:host ::ng-deep {
|
||||
.p-sidebar.mobile-sidebar {
|
||||
.p-sidebar-header {
|
||||
display: none; // Hide the header since we're not using it
|
||||
}
|
||||
|
||||
.p-sidebar-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Drawer/Sidebar mobile styling is centralized in sidebar-content component
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
import { Component, HostListener, inject, signal } from '@angular/core';
|
||||
import { Router, RouterOutlet } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
@@ -9,7 +9,7 @@ import { ToolbarModule } from 'primeng/toolbar';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MenuModule } from 'primeng/menu';
|
||||
import { SidebarModule } from 'primeng/sidebar';
|
||||
import { DrawerModule } from 'primeng/drawer';
|
||||
|
||||
import { DividerModule } from 'primeng/divider';
|
||||
import { RippleModule } from 'primeng/ripple';
|
||||
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||
@@ -29,7 +29,6 @@ import { ToastContainerComponent } from '../../shared/components/toast-container
|
||||
FormsModule,
|
||||
MenuModule,
|
||||
SidebarModule,
|
||||
DrawerModule,
|
||||
DividerModule,
|
||||
RippleModule,
|
||||
ConfirmDialogModule,
|
||||
@@ -40,25 +39,15 @@ import { ToastContainerComponent } from '../../shared/components/toast-container
|
||||
styleUrl: './main-layout.component.scss'
|
||||
})
|
||||
export class MainLayoutComponent {
|
||||
// Mobile menu state
|
||||
mobileSidebarVisible = signal<boolean>(false);
|
||||
|
||||
// Inject router
|
||||
public router = inject(Router);
|
||||
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* Handles mobile navigation click events by closing the sidebar
|
||||
*/
|
||||
onMobileNavClick(): void {
|
||||
this.mobileSidebarVisible.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle mobile sidebar visibility
|
||||
* Toggle mobile sidebar visibility via sidebar component
|
||||
*/
|
||||
toggleMobileSidebar(): void {
|
||||
this.mobileSidebarVisible.update(value => !value);
|
||||
// This will be called via template reference
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
<div class="logo logo-large logo-glow">
|
||||
<img src="icons/128.png" width="40" height="40" alt="Logo">
|
||||
</div>
|
||||
<div class="logo logo-small logo-glow">
|
||||
<img src="icons/128.png" width="40" height="40" alt="Logo">
|
||||
</div>
|
||||
<h2>Cleanuparr</h2>
|
||||
</div>
|
||||
|
||||
@@ -26,7 +23,7 @@
|
||||
[@staggerItems]>
|
||||
<!-- Project Sponsors Link (always visible) -->
|
||||
<a href="https://cleanuparr.github.io/Cleanuparr/support"
|
||||
class="nav-item sponsor-link"
|
||||
class="nav-item support-link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
<div class="nav-icon-wrapper heart-icon">
|
||||
@@ -142,3 +139,21 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Mobile Drawer (only rendered when enableMobileDrawer is true) -->
|
||||
<p-drawer
|
||||
*ngIf="enableMobileDrawer"
|
||||
styleClass="mobile-sidebar"
|
||||
[visible]="mobileSidebarVisible()"
|
||||
(visibleChange)="onMobileDrawerVisibilityChange($event)"
|
||||
[dismissible]="true"
|
||||
[showCloseIcon]="false"
|
||||
position="left">
|
||||
<ng-template #headless>
|
||||
<app-sidebar-content
|
||||
[isMobile]="true"
|
||||
[enableMobileDrawer]="false"
|
||||
(navItemClicked)="onNavItemClick()">
|
||||
</app-sidebar-content>
|
||||
</ng-template>
|
||||
</p-drawer>
|
||||
|
||||
@@ -1,57 +1,158 @@
|
||||
// Main container stability
|
||||
:host {
|
||||
width: var(--sidebar-width);
|
||||
height: var(--sidebar-height);
|
||||
background: var(--sidebar-background);
|
||||
background-size: 100% 100%;
|
||||
border-right: var(--sidebar-border);
|
||||
box-shadow: var(--sidebar-shadow);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden; // Prevent scrolling on the host
|
||||
position: relative;
|
||||
z-index: var(--sidebar-z-index);
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
transition: all var(--sidebar-transition-duration) ease;
|
||||
backdrop-filter: var(--sidebar-backdrop-filter);
|
||||
|
||||
// Colored edge accents
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: var(--sidebar-top-line-height);
|
||||
background: var(--sidebar-top-line-background);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
// Transitions for text elements in expanded state
|
||||
.app-name,
|
||||
.sidebar-section-title,
|
||||
.sidebar-nav span,
|
||||
.support-link span {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
width: auto;
|
||||
max-width: 180px;
|
||||
white-space: nowrap;
|
||||
transition: all var(--sidebar-transition-duration) ease;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// When isMobile input is true, ensure component is always visible regardless of screen size
|
||||
&.mobile-variant {
|
||||
display: flex !important;
|
||||
position: static;
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
width: var(--sidebar-width-collapsed);
|
||||
|
||||
.app-name,
|
||||
.sidebar-section-title,
|
||||
.sidebar-nav span,
|
||||
.support-link span {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
transition: all var(--sidebar-transition-duration) ease;
|
||||
}
|
||||
|
||||
.sidebar-nav li a {
|
||||
padding: 0.75rem 0;
|
||||
justify-content: center;
|
||||
|
||||
i {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sponsor-section .support-link {
|
||||
padding: 0.75rem 0;
|
||||
justify-content: center;
|
||||
|
||||
i {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logo container
|
||||
/* ========================================================================
|
||||
LOGO SECTION
|
||||
======================================================================== */
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
flex: 0 0 auto; // Prevent logo container from growing/shrinking
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
width: var(--sidebar-logo-size);
|
||||
height: var(--sidebar-logo-size);
|
||||
margin-right: 15px;
|
||||
background-color: var(--primary-100);
|
||||
border: 1px solid var(--primary-300);
|
||||
box-shadow: 0 0 10px rgba(var(--primary-500-rgb), 0.2);
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 15px rgba(var(--primary-500-rgb), 0.3);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 20px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
border-radius: 50%;
|
||||
background-color: var(--sidebar-logo-background);
|
||||
border: var(--sidebar-logo-border);
|
||||
animation: logo-glow 10s infinite ease-in-out;
|
||||
transition: all var(--sidebar-transition-duration) ease;
|
||||
|
||||
.logo-glow {
|
||||
box-shadow: 0 0 10px 6px rgba(89, 16, 185, 0.5);
|
||||
animation: logo-glow 2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.logo-small {
|
||||
display: none;
|
||||
img {
|
||||
width: var(--sidebar-logo-size);
|
||||
height: var(--sidebar-logo-size);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
color: var(--sidebar-text-white);
|
||||
text-shadow: var(--sidebar-text-shadow);
|
||||
letter-spacing: 0.5px;
|
||||
background: var(--sidebar-text-gradient);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.logo-glow {
|
||||
box-shadow: var(--sidebar-logo-glow);
|
||||
animation: logo-glow 2s infinite ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +161,7 @@
|
||||
display: block;
|
||||
gap: 0; // Remove gap to prevent layout shifts
|
||||
transition: opacity 0.2s ease;
|
||||
margin-top: 15px; // Add spacing from logo container
|
||||
|
||||
// Keep horizontal overflow hidden to avoid horizontal scrollbar
|
||||
overflow-x: hidden;
|
||||
@@ -136,14 +238,14 @@
|
||||
}
|
||||
|
||||
// Sponsor link
|
||||
.sponsor-link {
|
||||
.support-link {
|
||||
border-bottom: none;
|
||||
margin-bottom: 15px;
|
||||
|
||||
.heart-icon i {
|
||||
color: rgb(147 0 255) !important;
|
||||
color: var(--sidebar-heart-color) !important;
|
||||
transition: all 0.3s ease;
|
||||
text-shadow: 0 0 6px rgba(239, 68, 68, 0.7), 0 0 12px rgba(239, 68, 68, 0.5);
|
||||
text-shadow: var(--sidebar-heart-shadow);
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
@@ -224,9 +326,14 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation items
|
||||
.nav-item {
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
NAVIGATION SECTION
|
||||
======================================================================== */
|
||||
|
||||
// Main navigation items
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
@@ -350,7 +457,7 @@
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: #ffffff;
|
||||
background-color: var(--sidebar-text-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,9 +483,65 @@
|
||||
transform: translateX(3px) scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
SPONSOR SECTION
|
||||
======================================================================== */
|
||||
|
||||
.sponsor-section {
|
||||
padding: 0.5rem 1rem 1rem;
|
||||
|
||||
.support-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
color: var(--sidebar-text-white);
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
i {
|
||||
color: #ff5a5f;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sidebar footer with toggle button
|
||||
.sidebar-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 1rem;
|
||||
margin-top: auto;
|
||||
|
||||
.sidebar-toggle {
|
||||
color: var(--sidebar-text-white);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ========================================================================
|
||||
ANIMATIONS
|
||||
======================================================================== */
|
||||
|
||||
@keyframes logo-glow {
|
||||
0% { box-shadow: 0 0 15px #8e44ad, 0 0 5px rgba(142, 68, 173, 0.4); }
|
||||
20% { box-shadow: 0 0 15px #9b59b6, 0 0 5px rgba(155, 89, 182, 0.4); }
|
||||
40% { box-shadow: 0 0 15px #673ab7, 0 0 5px rgba(103, 58, 183, 0.4); }
|
||||
60% { box-shadow: 0 0 15px #5e35b1, 0 0 5px rgba(94, 53, 177, 0.4); }
|
||||
80% { box-shadow: 0 0 15px #7e57c2, 0 0 5px rgba(126, 87, 194, 0.4); }
|
||||
100% { box-shadow: 0 0 15px #8e44ad, 0 0 5px rgba(142, 68, 173, 0.4); }
|
||||
}
|
||||
|
||||
// Loading skeleton animation
|
||||
@keyframes skeleton-loading {
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
@@ -388,7 +551,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Animation keyframes
|
||||
@keyframes heart-pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
@@ -403,3 +565,124 @@
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
.p-drawer.mobile-sidebar {
|
||||
background: var(--sidebar-background) !important;
|
||||
border: none !important;
|
||||
|
||||
.p-drawer-content {
|
||||
padding: 0 !important;
|
||||
background: var(--sidebar-background) !important;
|
||||
color: var(--sidebar-text-white) !important;
|
||||
|
||||
.sidebar-header {
|
||||
padding: 1.5rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.sidebar-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.sidebar-icon {
|
||||
font-size: 1.5rem;
|
||||
color: #ffcc00;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
margin-left: 0.5rem;
|
||||
color: var(--sidebar-text-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: var(--sidebar-nav-padding);
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
|
||||
.sidebar-section-title {
|
||||
padding: 0 1rem;
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.25rem;
|
||||
width: 1.5rem;
|
||||
margin-right: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.active a {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: var(--sidebar-text-white);
|
||||
font-weight: 600;
|
||||
border-left: 3px solid #4299e1;
|
||||
padding-left: calc(1rem - 3px);
|
||||
|
||||
i {
|
||||
color: #4299e1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sponsor-section {
|
||||
padding: 0.5rem 1rem 1rem;
|
||||
margin-top: auto;
|
||||
|
||||
.support-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
color: var(--sidebar-text-white);
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
i {
|
||||
color: #ff5a5f;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Component, Input, inject, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, Input, inject, Output, EventEmitter, OnInit, OnDestroy, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Router, RouterLink, NavigationEnd } from '@angular/router';
|
||||
import { ButtonModule } from 'primeng/button';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { DrawerModule } from 'primeng/drawer';
|
||||
import { filter, debounceTime } from 'rxjs/operators';
|
||||
import { Subscription, fromEvent } from 'rxjs';
|
||||
import { trigger, state, style, transition, animate, query, stagger } from '@angular/animations';
|
||||
|
||||
interface NavigationItem {
|
||||
@@ -32,10 +33,14 @@ interface RouteMapping {
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterLink,
|
||||
ButtonModule
|
||||
ButtonModule,
|
||||
DrawerModule
|
||||
],
|
||||
templateUrl: './sidebar-content.component.html',
|
||||
styleUrl: './sidebar-content.component.scss',
|
||||
host: {
|
||||
'[class.mobile-variant]': 'isMobile'
|
||||
},
|
||||
animations: [
|
||||
trigger('staggerItems', [
|
||||
transition(':enter', [
|
||||
@@ -67,7 +72,12 @@ interface RouteMapping {
|
||||
})
|
||||
export class SidebarContentComponent implements OnInit, OnDestroy {
|
||||
@Input() isMobile = false;
|
||||
@Input() enableMobileDrawer = false;
|
||||
@Output() navItemClicked = new EventEmitter<void>();
|
||||
@Output() mobileDrawerVisibilityChange = new EventEmitter<boolean>();
|
||||
|
||||
// Mobile drawer state
|
||||
mobileSidebarVisible = signal<boolean>(false);
|
||||
|
||||
// Inject router for active route styling
|
||||
public router = inject(Router);
|
||||
@@ -90,6 +100,7 @@ export class SidebarContentComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Route synchronization properties
|
||||
private routerSubscription?: Subscription;
|
||||
private resizeSubscription?: Subscription;
|
||||
private routeMappings: RouteMapping[] = [
|
||||
// Dashboard
|
||||
{ route: '/dashboard', navigationPath: ['dashboard'] },
|
||||
@@ -123,10 +134,31 @@ export class SidebarContentComponent implements OnInit, OnDestroy {
|
||||
setTimeout(() => {
|
||||
this.initializeNavigation();
|
||||
}, 100);
|
||||
|
||||
// Listen for window resize events to auto-hide mobile drawer
|
||||
this.setupWindowResizeListener();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.routerSubscription?.unsubscribe();
|
||||
this.resizeSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup window resize listener to auto-hide mobile drawer on larger screens
|
||||
*/
|
||||
private setupWindowResizeListener(): void {
|
||||
// Define the mobile breakpoint (should match CSS media query)
|
||||
const MOBILE_BREAKPOINT = 991;
|
||||
|
||||
this.resizeSubscription = fromEvent(window, 'resize')
|
||||
.pipe(
|
||||
debounceTime(150), // Debounce resize events for better performance
|
||||
filter(() => window.innerWidth > MOBILE_BREAKPOINT && this.mobileSidebarVisible())
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.mobileSidebarVisible.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,40 +255,40 @@ export class SidebarContentComponent implements OnInit, OnDestroy {
|
||||
label: 'Sonarr',
|
||||
icon: 'pi pi-play-circle',
|
||||
route: '/sonarr',
|
||||
iconUrl: '/icons/ext/sonarr-light.svg',
|
||||
iconUrlHover: '/icons/ext/sonarr.svg'
|
||||
iconUrl: 'icons/ext/sonarr-light.svg',
|
||||
iconUrlHover: 'icons/ext/sonarr.svg'
|
||||
},
|
||||
{
|
||||
id: 'radarr',
|
||||
label: 'Radarr',
|
||||
icon: 'pi pi-play-circle',
|
||||
route: '/radarr',
|
||||
iconUrl: '/icons/ext/radarr-light.svg',
|
||||
iconUrlHover: '/icons/ext/radarr.svg'
|
||||
iconUrl: 'icons/ext/radarr-light.svg',
|
||||
iconUrlHover: 'icons/ext/radarr.svg'
|
||||
},
|
||||
{
|
||||
id: 'lidarr',
|
||||
label: 'Lidarr',
|
||||
icon: 'pi pi-bolt',
|
||||
route: '/lidarr',
|
||||
iconUrl: '/icons/ext/lidarr-light.svg',
|
||||
iconUrlHover: '/icons/ext/lidarr.svg'
|
||||
iconUrl: 'icons/ext/lidarr-light.svg',
|
||||
iconUrlHover: 'icons/ext/lidarr.svg'
|
||||
},
|
||||
{
|
||||
id: 'readarr',
|
||||
label: 'Readarr',
|
||||
icon: 'pi pi-book',
|
||||
route: '/readarr',
|
||||
iconUrl: '/icons/ext/readarr-light.svg',
|
||||
iconUrlHover: '/icons/ext/readarr.svg'
|
||||
iconUrl: 'icons/ext/readarr-light.svg',
|
||||
iconUrlHover: 'icons/ext/readarr.svg'
|
||||
},
|
||||
{
|
||||
id: 'whisparr',
|
||||
label: 'Whisparr',
|
||||
icon: 'pi pi-lock',
|
||||
route: '/whisparr',
|
||||
iconUrl: '/icons/ext/whisparr-light.svg',
|
||||
iconUrlHover: '/icons/ext/whisparr.svg'
|
||||
iconUrl: 'icons/ext/whisparr-light.svg',
|
||||
iconUrlHover: 'icons/ext/whisparr.svg'
|
||||
},
|
||||
{ id: 'download-clients', label: 'Download Clients', icon: 'pi pi-download', route: '/download-clients' }
|
||||
]
|
||||
@@ -481,5 +513,38 @@ export class SidebarContentComponent implements OnInit, OnDestroy {
|
||||
if (this.isMobile) {
|
||||
this.navItemClicked.emit();
|
||||
}
|
||||
// Close mobile drawer when nav item is clicked
|
||||
if (this.mobileSidebarVisible()) {
|
||||
this.mobileSidebarVisible.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show mobile drawer
|
||||
*/
|
||||
showMobileDrawer(): void {
|
||||
this.mobileSidebarVisible.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide mobile drawer
|
||||
*/
|
||||
hideMobileDrawer(): void {
|
||||
this.mobileSidebarVisible.set(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle mobile drawer visibility
|
||||
*/
|
||||
toggleMobileDrawer(): void {
|
||||
this.mobileSidebarVisible.update(visible => !visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle mobile drawer visibility change
|
||||
*/
|
||||
onMobileDrawerVisibilityChange(visible: boolean): void {
|
||||
this.mobileSidebarVisible.set(visible);
|
||||
this.mobileDrawerVisibilityChange.emit(visible);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ const initialState: DownloadCleanerConfigState = {
|
||||
};
|
||||
|
||||
export const DownloadCleanerConfigStore = signalStore(
|
||||
{ providedIn: 'root' },
|
||||
withState(initialState),
|
||||
withMethods((store, http = inject(HttpClient), applicationPathService = inject(ApplicationPathService)) => ({
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ import { DocumentationService } from "../../core/services/documentation.service"
|
||||
NgIf,
|
||||
MobileAutocompleteComponent,
|
||||
],
|
||||
providers: [ConfirmationService],
|
||||
providers: [ConfirmationService, DownloadCleanerConfigStore],
|
||||
templateUrl: "./download-cleaner-settings.component.html",
|
||||
styleUrls: ["./download-cleaner-settings.component.scss"],
|
||||
})
|
||||
|
||||
@@ -26,22 +26,22 @@ export class ProviderTypeSelectionComponent {
|
||||
{
|
||||
type: NotificationProviderType.Apprise,
|
||||
name: 'Apprise',
|
||||
iconUrl: '/icons/ext/apprise-light.svg',
|
||||
iconUrlHover: '/icons/ext/apprise.svg',
|
||||
iconUrl: 'icons/ext/apprise-light.svg',
|
||||
iconUrlHover: 'icons/ext/apprise.svg',
|
||||
description: 'https://github.com/caronc/apprise'
|
||||
},
|
||||
{
|
||||
type: NotificationProviderType.Notifiarr,
|
||||
name: 'Notifiarr',
|
||||
iconUrl: '/icons/ext/notifiarr-light.svg',
|
||||
iconUrlHover: '/icons/ext/notifiarr.svg',
|
||||
iconUrl: 'icons/ext/notifiarr-light.svg',
|
||||
iconUrlHover: 'icons/ext/notifiarr.svg',
|
||||
description: 'https://notifiarr.com'
|
||||
},
|
||||
{
|
||||
type: NotificationProviderType.Ntfy,
|
||||
name: 'ntfy',
|
||||
iconUrl: '/icons/ext/ntfy-light.svg',
|
||||
iconUrlHover: '/icons/ext/ntfy.svg',
|
||||
iconUrl: 'icons/ext/ntfy-light.svg',
|
||||
iconUrlHover: 'icons/ext/ntfy.svg',
|
||||
description: 'https://ntfy.sh/'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="apple-mobile-web-app-title" content="Cleanuparr">
|
||||
<link rel="apple-touch-icon" href="assets/icons/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" href="icons/apple-touch-icon.png">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
|
||||
@@ -43,6 +43,50 @@
|
||||
--accent-purple-800: #4527A0;
|
||||
--accent-purple-900: #311B92;
|
||||
|
||||
/* Sidebar Layout Variables */
|
||||
--sidebar-width: 270px; /* Single source for both desktop/mobile */
|
||||
--sidebar-width-collapsed: 70px;
|
||||
--sidebar-height: 100vh;
|
||||
--sidebar-z-index: 1000;
|
||||
|
||||
/* Sidebar Visual Variables */
|
||||
--sidebar-background: linear-gradient(135deg, #2c1a47 0%, #1a0e29 50%, #1e1230 75%, rgba(30, 18, 48, 0.95) 100%);
|
||||
--sidebar-border: 1px solid rgba(142, 68, 173, 0.15);
|
||||
--sidebar-shadow: 2px 0 15px rgba(0, 0, 0, 0.3);
|
||||
--sidebar-backdrop-filter: blur(5px);
|
||||
|
||||
/* Sidebar Top Line */
|
||||
--sidebar-top-line-height: 1px;
|
||||
--sidebar-top-line-background: linear-gradient(90deg, rgba(142, 68, 173, 0.6), rgba(103, 58, 183, 0.6));
|
||||
--sidebar-top-line-shadow: 0 0 8px rgba(142, 68, 173, 0.4);
|
||||
|
||||
/* Logo Variables */
|
||||
--sidebar-logo-size: 50px;
|
||||
--sidebar-logo-background: rgba(142, 68, 173, 0.1);
|
||||
--sidebar-logo-border: 1px solid rgba(142, 68, 173, 0.3);
|
||||
--sidebar-logo-icon-size: 30px;
|
||||
|
||||
/* Navigation Variables */
|
||||
--sidebar-nav-padding: 1rem 0;
|
||||
--sidebar-nav-item-padding: 10px 20px;
|
||||
--sidebar-nav-icon-wrapper-size: 48px;
|
||||
--sidebar-nav-icon-size: 22px;
|
||||
--sidebar-nav-border-radius: 0 6px 6px 0;
|
||||
--sidebar-nav-hover-transform: translateX(4px);
|
||||
|
||||
/* Text & Color Variables */
|
||||
--sidebar-text-white: #ffffff;
|
||||
--sidebar-text-gradient: linear-gradient(to right, #fff, #bbb);
|
||||
--sidebar-text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
--sidebar-logo-glow: 0 0 10px 6px rgba(89, 16, 185, 0.5);
|
||||
--sidebar-heart-color: rgb(147, 0, 255);
|
||||
--sidebar-heart-shadow: 0 0 6px rgba(239, 68, 68, 0.7), 0 0 12px rgba(239, 68, 68, 0.5);
|
||||
|
||||
/* Animation Variables */
|
||||
--sidebar-transition-duration: 0.3s;
|
||||
--sidebar-transition-easing: cubic-bezier(0.4, 0.0, 0.2, 1);
|
||||
--sidebar-animation-easing: ease-in-out;
|
||||
|
||||
/* Standard color palette for logs and events */
|
||||
--yellow-50: #fffde7;
|
||||
--yellow-100: #fff9c4;
|
||||
@@ -258,129 +302,7 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
/* PrimeNG Sidebar Override */
|
||||
.p-sidebar.mobile-sidebar {
|
||||
background-color: #1a1e27 !important;
|
||||
border: none !important;
|
||||
|
||||
.p-sidebar-header {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.p-sidebar-content {
|
||||
padding: 0 !important;
|
||||
background-color: #1a1e27 !important;
|
||||
color: #ffffff !important;
|
||||
|
||||
.sidebar-header {
|
||||
padding: 1.5rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.sidebar-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.sidebar-icon {
|
||||
font-size: 1.5rem;
|
||||
color: #ffcc00;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
margin-left: 0.5rem;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
padding: 1rem 0;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
|
||||
.sidebar-section-title {
|
||||
padding: 0 1rem;
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.25rem;
|
||||
width: 1.5rem;
|
||||
margin-right: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.active a {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
border-left: 3px solid #4299e1;
|
||||
padding-left: calc(1rem - 3px);
|
||||
|
||||
i {
|
||||
color: #4299e1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sponsor-section {
|
||||
padding: 0.5rem 1rem 1rem;
|
||||
margin-top: auto;
|
||||
|
||||
.sponsor-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
i {
|
||||
color: #ff5a5f;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* PrimeNG Sidebar Override - moved to sidebar-content component */
|
||||
|
||||
/* Dark purple theme overrides */
|
||||
.dark-mode {
|
||||
|
||||
Reference in New Issue
Block a user