sidebar fxies
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Build distribution 📦 (push) Has been cancelled
ChangeDetection.io App Test / lint-code (push) Has been cancelled
ChangeDetection.io App Test / lint-translations (push) Has been cancelled
ChangeDetection.io App Test / lint-template-i18n (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Test the built package works basically. (push) Has been cancelled
Publish Python 🐍distribution 📦 to PyPI and TestPyPI / Publish Python 🐍 distribution 📦 to PyPI (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-10 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-11 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-12 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-13 (push) Has been cancelled
ChangeDetection.io App Test / test-application-3-14 (push) Has been cancelled

This commit is contained in:
dgtlmoon
2026-06-15 17:05:02 +02:00
parent a2d77752c0
commit 7f056a9728
9 changed files with 164 additions and 38 deletions
@@ -292,6 +292,10 @@ nav
{{ render_field(form.application.form.ui.form.timeago_format) }}
<span class="pure-form-message-inline">{{ _('How "Last Checked" and "Last Changed" times are shown in the watch overview list.') }}</span>
</div>
<div class="pure-control-group">
{{ render_field(form.application.form.ui.form.sidebar_mode) }}
<span class="pure-form-message-inline">{{ _('"Collapsed icon rail" keeps the left navigation slim and reveals labels on mouse-over; "Always expanded" keeps labels visible.') }}</span>
</div>
</div>
<div class="tab-pane-inner" id="proxies">
+13
View File
@@ -196,6 +196,19 @@ def get_darkmode_state():
def get_css_version():
return __version__
@app.template_global()
def get_sidebar_mode_class():
"""Body class that drives the left-rail behaviour (see parts/_action_sidebar.scss).
'collapsed' -> slim icon rail that expands on hover/focus (actionsidebar-minimal)
'pinned' -> rail always expanded with labels visible (actionside-bar-on)
"""
mode = datastore.data['settings']['application'].get('ui', {}).get('sidebar_mode', 'collapsed')
# Pinned mode is permanently expanded, so it carries 'action-side-bar-expanded'
# from the start. In collapsed mode that class is toggled on hover/focus by
# static/js/sidebar.js.
return 'actionside-bar-on action-side-bar-expanded' if mode == 'pinned' else 'actionsidebar-minimal'
@app.template_global()
def get_socketio_path():
"""Generate the correct Socket.IO path prefix for the client"""
+4
View File
@@ -1077,6 +1077,10 @@ class globalSettingsApplicationUIForm(Form):
timeago_format = SelectField(_l('Relative time format'),
choices=[('long', _l('Long (1 minute ago)')), ('short', _l('Short (1m ago)'))],
default='long', validators=[validators.Optional()])
sidebar_mode = SelectField(_l('Navigation sidebar'),
choices=[('collapsed', _l('Collapsed icon rail (expands on hover)')),
('pinned', _l('Always expanded'))],
default='collapsed', validators=[validators.Optional()])
# datastore.data['settings']['application']..
class globalSettingsApplicationForm(commonSettingsForm):
+1
View File
@@ -80,6 +80,7 @@ class model(dict):
'socket_io_enabled': True,
'favicons_enabled': True,
'timeago_format': 'long', # 'long' = "1 minute ago", 'short' = "1m ago"
'sidebar_mode': 'collapsed', # 'collapsed' = slim icon rail, expands on hover; 'pinned' = always expanded
},
}
}
@@ -5,7 +5,7 @@
//$action-sidebar-width-collapsed: 64px used by the resting inner-block width and the content-main left-padding in minimal mode
//$action-sidebar-content-slot: 1100px editable content column; .content-main max-width is now derived as content_slot + collapsed_width
$action-sidebar-width-expanded: 190px;
$action-sidebar-width-expanded: 200px;
$action-sidebar-width-collapsed: 64px;
$action-sidebar-content-slot: 1100px;
@@ -16,7 +16,16 @@ $action-sidebar-content-slot: 1100px;
display: flex;
flex-direction: column;
align-items: center;
width: $action-sidebar-width-expanded; // reserve enough to host the expanded inner block
// Pin the rail to the viewport as a full-height column (like the demo's
// `height: 100vh; position: sticky`). align-self:flex-start stops the flex
// row from stretching it to the page-content height; sticky+100vh then keeps
// it viewport-tall and fixed in place while the content scrolls, so the
// footer list always rests at the bottom of the window.
align-self: flex-start;
position: sticky;
top: 0;
height: 100vh;
background: rgba(255, 255, 255, 0.05);
z-index: 60;
pointer-events: none; // empty space doesn't intercept clicks/hovers
@media only screen and (max-width: $desktop-wide-breakpoint) {
@@ -25,6 +34,12 @@ $action-sidebar-content-slot: 1100px;
}
}
body.actionside-bar-on {
.action-sidebar {
/* width: $action-sidebar-width-expanded; // reserve enough to host the expanded inner block*/
}
}
// The actual interactive item block wraps only the items, not the whole height.
//
// Two body-level modes drive the rail's display state:
@@ -38,9 +53,17 @@ $action-sidebar-content-slot: 1100px;
pointer-events: auto; // restore interactivity inside the block
width: $action-sidebar-width-collapsed;
overflow: hidden;
// Fill the full height of the (stretched) rail and stack its lists in a
// column, so the footer list can sit at the bottom (see --bottom below).
flex: 1 1 auto;
display: flex;
flex-direction: column;
// Exit (mouse-away) is fast snaps back to icons.
transition: width 0.08s ease-out;
padding-left: $common-gap;
padding-right: $common-gap;
// Hover-to-expand only fires in minimal mode.
body.actionsidebar-minimal &:hover,
body.actionsidebar-minimal &:focus-within {
@@ -56,19 +79,24 @@ $action-sidebar-content-slot: 1100px;
}
}
.action-sidebar-list {
ul.action-sidebar-list {
list-style: none;
padding-right: 0;
padding-left: $common-gap/2;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 0;
}
// Footer list (status counters, live spark, version) sits at the bottom of the
// rail the flex equivalent of the demo's `.rail-spacer { flex: 1 }`.
.action-sidebar-list--bottom {
margin-top: auto;
}
.action-sidebar-li {
>:first-child {
padding-left: $common-gap/2;
/* padding-left: $common-gap/2;*/
}
list-style: none;
@@ -111,8 +139,9 @@ $action-sidebar-content-slot: 1100px;
position: relative;
display: flex;
align-items: center;
width: 100%;
height: 42px;
/* width: 100%;*/
/*height: 42px;*/
padding: $common-gap;
font-size: var(--body-main-text-size);
border-radius: 8px;
color: var(--color-white);
@@ -259,7 +288,7 @@ body.actionside-bar-on {
padding: 0.5rem 0.75rem;
border-bottom: 1px solid var(--color-border-table-cell);
.action-sidebar-list {
ul.action-sidebar-list {
gap: 1px;
}
@@ -365,4 +394,35 @@ body.actionside-bar-on {
.mobile-menu-section .action-sidebar-item {
padding-left: 0;
}
}
}
/** top logo stuff */
#action-sidebar-logo {
padding: $common-gap;
}
.actionsidebar-minimal {
#cdio-logo {
#logo-expanded {
display: none;
}
}
&.action-side-bar-expanded {
#cdio-logo {
#logo-expanded {
display: inline-block;
}
}
}
}
.action-side-bar-expanded {
#cdio-logo {
#logo-short {
display: none;
}
}
}
@@ -72,9 +72,31 @@ body {
// column side-by-side on desktop, stacked on mobile. Not sidebar-specific the
// rail's parts/_action_sidebar.scss styles target the rail itself, this is the
// outer wrapper any future side-column layout could share.
// App shell the demo's `.app`: a full-height flex row whose left column is the
// navigation rail and whose right column (`.app-main`) stacks the top menu over
// the page content. `min-height: 100vh` guarantees the rail's gutter runs the
// full window height even when the page content is short.
.app {
display: flex;
align-items: stretch;
min-height: 100vh;
// No gap here on purpose: the top menu (.header) should sit flush against the
// rail. The content's breathing room from the rail is applied to
// section.content alone (padding-left below), not the whole right column.
}
// Right-hand column: top menu (.header) above the scrolling content.
.app-main {
flex: 1 1 auto;
min-width: 0;
display: flex;
flex-direction: column;
// Space between the top menu and the page content below it.
gap: $common-gap;
}
.content-wrapper {
display: flex;
gap: 1rem;
width: 100%;
max-width: 100%;
position: relative;
@@ -98,7 +120,7 @@ body {
align-items: center;
@media only screen and (min-width: $desktop-wide-breakpoint) {
flex-direction: column;
padding-right: 1rem;
padding-top: 4rem;
}
}
@@ -190,9 +212,10 @@ body.spinner-active {
background-position: 0% 50%;
}
}
.pure-menu-heading {
color: var(--color-text-menu-heading);
padding-left: $common-gap;
#cdio-logo {
color: var(--color-white);
text-transform: uppercase;
}
.pure-menu-link {
@@ -211,13 +234,17 @@ body.spinner-active {
}
section.content {
padding-top: 80px;
// The top menu (.header) now sits in-flow above this section inside .app-main,
// so the old fixed-header clearance (padding-top: 80px) is no longer needed.
// Inset the content from the rail (and the window's right edge) so it doesn't
// butt against the left action bar the header above stays flush to the rail.
padding-bottom: 1em;
padding-left: $common-gap;
padding-right: $common-gap;
flex-direction: column;
display: flex;
align-items: center;
justify-content: center;
justify-content: flex-start;
}
code {
File diff suppressed because one or more lines are too long
+15 -18
View File
@@ -52,19 +52,20 @@
{%- endif %}
</head>
<body class="actionside-bar-on {{extra_classes}}">
<body class="{{ get_sidebar_mode_class() }} {{extra_classes}}">
<div class="app">
{% if current_user.is_authenticated or not has_password %}
<aside class="action-sidebar" aria-label="{{ _('Main navigation') }}">
<div class="action-sidebar-inner">
{% include "sidebar-nav.html" %}
</div>
</aside>
{% endif %}
<div class="app-main">
<div class="header">
<div {% if pure_menu_fixed != False %}class="pure-menu-fixed"{% endif %} style="width: 100%;">
<div>
<div class="home-menu pure-menu pure-menu-horizontal" id="nav-menu">
{% if has_password and not current_user.is_authenticated %}
<a id="cdio-logo" class="pure-menu-heading" href="https://changedetection.io" rel="noopener">
<strong>Change</strong>Detection.io</a>
{% else %}
<a id="cdio-logo" class="pure-menu-heading" href="{{url_for('watchlist.index')}}">
<strong>Change</strong>Detection.io</a>
{% endif %}
{% if current_diff_url and is_safe_valid_url(current_diff_url) %}
{% if current_diff_url and is_safe_valid_url(current_diff_url) %}
<a class="current-diff-url" href="{{ current_diff_url }}" title="{{ current_diff_url }}">
<span>{{ current_diff_url }}</span></a>
{% endif %}
@@ -170,13 +171,6 @@
</div>
<div class="content-wrapper">
{% if current_user.is_authenticated or not has_password %}
<aside class="action-sidebar" aria-label="{{ _('Main navigation') }}">
<div class="action-sidebar-inner">
{% include "sidebar-nav.html" %}
</div>
</aside>
{% endif %}
<div class="content-main">
{% if header %}
@@ -204,8 +198,11 @@
</div>
</div>
</section>
</div><!-- /.app-main -->
</div><!-- /.app -->
<script src="{{url_for('static_content', group='js', filename='toggle-theme.js')}}" defer></script>
<script src="{{url_for('static_content', group='js', filename='hamburger-menu.js')}}" defer></script>
<script src="{{url_for('static_content', group='js', filename='sidebar.js')}}" defer></script>
<div id="checking-now-fixed-tab" style="display: none;"><span class="spinner"></span><span class="status-text">&nbsp;{{ _('Checking now') }}</span></div>
<div id="realtime-conn-error" style="display:none">{{ _('Real-time updates offline') }}</div>
+21 -1
View File
@@ -2,7 +2,22 @@
{# Used by base.html (desktop left sidebar) and mobile hamburger drawer #}
{%- set ep = request.endpoint or '' -%}
<ul class="action-sidebar-list">
<ul class="action-sidebar-list action-sidebar-list--top">
<li class="action-sidebar-li" id="action-sidebar-logo">
{%- if has_password and not current_user.is_authenticated -%}
<a id="cdio-logo" href="https://changedetection.io" rel="noopener">
<span id="logo-expanded"><strong>Change</strong>Detection.io</span>
<span id="logo-short"><strong>CD</strong>IO</span>
</a>
{%- else -%}
<a id="cdio-logo" href="{{url_for('watchlist.index')}}">
<span id="logo-expanded"><strong>Change</strong>Detection.io</span>
<span id="logo-short"><strong>CD</strong>IO</span>
</a>
{%- endif -%}
</li>
<li class="action-sidebar-li">
<a href="{{ url_for('add_watch_ui.add_watch_ui_index') }}"
class="action-sidebar-item {% if ep.startswith('add_watch_ui.') %}active{% endif %}"
@@ -92,7 +107,12 @@
<span class="action-label">{{ _('Notifications') }}</span>
</a>
</li>
</ul>
{# Footer group — pushed to the bottom of the rail via `margin-top: auto`
(see .action-sidebar-list--bottom). Mirrors the demo's bottom-aligned rail
footer (status counters, live spark, version). #}
<ul class="action-sidebar-list action-sidebar-list--bottom">
<li class="action-sidebar-divider" aria-hidden="true"></li>
<li class="action-sidebar-li" id="checking-now-stats-sidebar">