From 4b11b591695b01673eddf97a87fc7a6455e3e47e Mon Sep 17 00:00:00 2001 From: Andrew Collington Date: Sat, 4 Oct 2025 16:04:40 +0100 Subject: [PATCH 1/6] Start of CSS added --- build/_frontend/interface.scss | 102 +++++++++++++++++++++++++++++++++ index.php | 2 +- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/build/_frontend/interface.scss b/build/_frontend/interface.scss index e8922e6..4ba27b0 100644 --- a/build/_frontend/interface.scss +++ b/build/_frontend/interface.scss @@ -437,3 +437,105 @@ $footer-border-color: #CCC; opacity: 0; } } + +@mixin dark-theme { + background-color: #121212; + color: #ddd; + + a { + color: dodgerblue; + } + + .nav-tab { + background-color: #1e1e1e; + border-color: #555; + + &:hover { + background-color: #2a2a2a; + } + + &.active { + border-color: #555; + border-top-color: #84b8ff; + border-bottom-color: #1e1e1e; + } + } + + .nav-tab-link-reset, + .nav-tab-link-realtime { + background-color: transparent; + + &.is-resetting, + &.live-update { + background-color: transparent; + } + + &.pulse::before { + border-color: #00e600; + } + } + + .graph-widget .widget-value, + .widget-value span.large, + .widget-value span.large + span { + color: #84b8ff; + } + + .widget-panel { + background-color: #2a2a2a; + } + + .widget-header { + background-color: #333; + color: #ccc; + } + + .tables { + tr:nth-child(odd) { + background-color: #2a2a2a; + } + + tr:nth-child(even) { + background-color: #1f1f1f; + } + + th { + background-color: #3a4a5e; + color: #ddd; + border-color: #444; + } + + td { + border-color: #333; + } + } + + .main-footer { + border-top-color: #444; + color: #ccc; + } + + .pagination li a { + &.active { + background-color: #4d75af; + color: #fff; + } + + &:hover:not(.active) { + background-color: #ff7400; + color: #fff; + } + } +} + +@media (prefers-color-scheme: dark) { + .opcache-gui { + @include dark-theme; + } +} + +.dark .opcache-gui { + @include dark-theme; +} + + diff --git a/index.php b/index.php index a56c646..e32d3a1 100644 --- a/index.php +++ b/index.php @@ -528,7 +528,7 @@ protected function jitState(array $status, array $directives): array From 9c51904a477f4d481db372b403308b07124d7d81 Mon Sep 17 00:00:00 2001 From: Andrew Collington Date: Sat, 4 Oct 2025 16:37:01 +0100 Subject: [PATCH 2/6] Flexible tab content --- build/_frontend/interface.scss | 12 ++++++++++++ index.php | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/build/_frontend/interface.scss b/build/_frontend/interface.scss index 4ba27b0..e9a304d 100644 --- a/build/_frontend/interface.scss +++ b/build/_frontend/interface.scss @@ -48,6 +48,9 @@ $footer-border-color: #CCC; font-size: 90%; padding: 0; margin: 0; + min-height: 100vh; + display: flex; + flex-direction: column; .hide { display: none; @@ -67,6 +70,12 @@ $footer-border-color: #CCC; white-space: nowrap !important; } + header, .main-nav { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + } .main-nav { padding-top: 20px; } @@ -148,6 +157,9 @@ $footer-border-color: #CCC; .tab-content { padding: 2em; + flex: 1 1 auto; + min-height: 0; + overflow: auto; } .tab-content-overview-counts { diff --git a/index.php b/index.php index e32d3a1..1b4b6ca 100644 --- a/index.php +++ b/index.php @@ -528,7 +528,7 @@ protected function jitState(array $status, array $directives): array From 3289551203ba407d201c059e61fdd424dc7dbf9b Mon Sep 17 00:00:00 2001 From: Andrew Collington Date: Sat, 4 Oct 2025 19:21:44 +0100 Subject: [PATCH 3/6] Fixed github logo colour when dark --- build/_frontend/interface.scss | 15 ++++++++++++++- index.php | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/build/_frontend/interface.scss b/build/_frontend/interface.scss index e9a304d..43c5be6 100644 --- a/build/_frontend/interface.scss +++ b/build/_frontend/interface.scss @@ -4,6 +4,11 @@ $nav-border-color: #CCC; $nav-background-color: #FFF; $nav-icon-color: #626262; $nav-icon-active-color: #00ba00; +$nav-icon-color-dark: #CCCCCC; + +// SVG icon colors for light and dark modes +$github-icon-color: $nav-icon-color; +$github-icon-color-dark: $nav-icon-color-dark; $table-header-color: #6CA6EF; $table-row-color: #EFFEFF; @@ -38,6 +43,10 @@ $footer-border-color: #CCC; @return "rgb(#{$r}, #{$g}, #{$b})"; } +@mixin github-icon($color) { + background-image: url('data:image/svg+xml;utf8,'); +} + :root { --opcache-gui-graph-track-fill-color: #{$widget-graph-fill-color}; --opcache-gui-graph-track-background-color: #{$widget-graph-background-color}; @@ -342,7 +351,7 @@ $footer-border-color: #CCC; } .github-link { - background-image: url('data:image/svg+xml;utf8,'); + @include github-icon($github-icon-color); } .sponsor-link { @@ -487,6 +496,10 @@ $footer-border-color: #CCC; } } + .github-link { + @include github-icon($github-icon-color-dark); + } + .graph-widget .widget-value, .widget-value span.large, .widget-value span.large + span { diff --git a/index.php b/index.php index 1b4b6ca..88d4e13 100644 --- a/index.php +++ b/index.php @@ -528,7 +528,7 @@ protected function jitState(array $status, array $directives): array From 3f1a80477b2406942b5f6194729f1afc16813668 Mon Sep 17 00:00:00 2001 From: Andrew Collington Date: Sat, 4 Oct 2025 20:08:11 +0100 Subject: [PATCH 4/6] Refined tabs --- build/_frontend/interface.scss | 7 ++++--- index.php | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build/_frontend/interface.scss b/build/_frontend/interface.scss index 43c5be6..b13d2c2 100644 --- a/build/_frontend/interface.scss +++ b/build/_frontend/interface.scss @@ -468,15 +468,16 @@ $footer-border-color: #CCC; } .nav-tab { - background-color: #1e1e1e; - border-color: #555; + background-color: transparent; + border-color: transparent; &:hover { background-color: #2a2a2a; } &.active { - border-color: #555; + background-color: #1e1e1e; + border-color: transparent; border-top-color: #84b8ff; border-bottom-color: #1e1e1e; } diff --git a/index.php b/index.php index 88d4e13..0988d38 100644 --- a/index.php +++ b/index.php @@ -528,7 +528,7 @@ protected function jitState(array $status, array $directives): array From c3fca5568fe1122eee80fe382262bda4a7d1ec81 Mon Sep 17 00:00:00 2001 From: Andrew Collington Date: Sat, 4 Oct 2025 23:55:34 +0100 Subject: [PATCH 5/6] Theme toggle and some html refinements --- build/_frontend/interface.jsx | 303 +++++++++++++++++++++------------ build/_frontend/interface.scss | 95 ++++++++++- build/_languages/de.json | 8 +- build/_languages/es.json | 22 ++- build/_languages/example.json | 8 +- build/_languages/fr.json | 18 +- index.php | 114 +++++++++++-- 7 files changed, 423 insertions(+), 145 deletions(-) diff --git a/build/_frontend/interface.jsx b/build/_frontend/interface.jsx index e59ecfe..120f38c 100644 --- a/build/_frontend/interface.jsx +++ b/build/_frontend/interface.jsx @@ -1,10 +1,12 @@ class Interface extends React.Component { + static THEME_STORAGE_KEY = 'opcache_gui_theme'; constructor(props) { super(props); this.state = { realtime: this.getCookie(), resetting: false, - opstate: props.opstate + opstate: props.opstate, + theme: this.getStoredTheme() } this.polling = false; this.isSecure = (window.location.protocol === 'https:'); @@ -67,6 +69,38 @@ class Interface extends React.Component { return v ? !!v[2] : false; }; + getStoredTheme = () => { + try { + const t = localStorage.getItem(Interface.THEME_STORAGE_KEY); + return t === 'light' || t === 'dark' || t === 'system' ? t : 'system'; + } catch (e) { + return 'system'; + } + }; + + applyTheme = (theme) => { + const root = document.documentElement; + root.classList.remove('dark'); + root.classList.remove('light'); + if (theme === 'dark') { + root.classList.add('dark'); + } else if (theme === 'light') { + root.classList.add('light'); + } + }; + + setTheme = (theme) => { + this.setState({ theme }); + try { + localStorage.setItem(Interface.THEME_STORAGE_KEY, theme); + } catch (e) {} + this.applyTheme(theme); + }; + + componentDidMount() { + this.applyTheme(this.state.theme); + } + txt = (text, ...args) => { if (this.props.language !== null && this.props.language.hasOwnProperty(text) && this.props.language[text]) { text = this.props.language[text]; @@ -81,16 +115,16 @@ class Interface extends React.Component { const { opstate, realtimeRefresh, ...otherProps } = this.props; return ( <> -
- -
+
- -
- +
+ +
+ + + -
- +
+ { + props.allow.filelist && +
+ - + } + { + (props.allow.filelist && props.opstate.blacklist.length && +
+ - ) + } + { + (props.allow.filelist && props.opstate.preload.length && +
+ -
-
- { - props.allow.filelist && -
- -
- } - { - (props.allow.filelist && props.opstate.blacklist.length && -
- -
) - } - { - (props.allow.filelist && props.opstate.preload.length && -
- -
) - } - { - props.allow.reset && -
- } - { - props.allow.realtime && -
- } - - +
) + } + { + props.allow.reset && +
+ } + { + props.allow.realtime && +
+ } + ); } +function ThemeSwitcher(props) { + const themeOrder = ['light', 'dark', 'system']; + const index = Math.max(0, themeOrder.indexOf(props.theme)); + const set = (t) => props.onThemeChange && props.onThemeChange(t); + const btn = (t, icon, label) => ( + + ); + const SunIcon = ( + + ); + const MoonIcon = ( + + ); + const LaptopIcon = ( + + ); + + return ( +
+
+
+ {btn('light', SunIcon, 'Light')} + {btn('dark', MoonIcon, 'Dark')} + {btn('system', LaptopIcon, 'System')} +
+
+ ); +} class Tabs extends React.Component { constructor(props) { @@ -208,34 +282,43 @@ class Tabs extends React.Component { const children = this.props.children.filter(Boolean); + console.log(this.props) + return ( <> -
    - {children.map((child) => { - const { tabId, label, className, handler, tabIndex } = child.props; - return ( - - ); - })} -
-
- {children.map((child) => ( -
- {child.props.children} -
- ))} -
+
+ + +
+
+
+ {children.map((child) => ( +
+ {child.props.children} +
+ ))} +
+
); } diff --git a/build/_frontend/interface.scss b/build/_frontend/interface.scss index b13d2c2..de4c5b7 100644 --- a/build/_frontend/interface.scss +++ b/build/_frontend/interface.scss @@ -6,7 +6,6 @@ $nav-icon-color: #626262; $nav-icon-active-color: #00ba00; $nav-icon-color-dark: #CCCCCC; -// SVG icon colors for light and dark modes $github-icon-color: $nav-icon-color; $github-icon-color-dark: $nav-icon-color-dark; @@ -82,16 +81,17 @@ $footer-border-color: #CCC; header, .main-nav { display: flex; flex-direction: column; - flex: 1 1 auto; min-height: 0; + padding-top: 20px; } .main-nav { - padding-top: 20px; + position: relative; } .nav-tab-list { list-style-type: none; padding-left: 8px; + padding-right: 160px; // reserve space for theme switcher on the right margin: 0; border-bottom: 1px solid $nav-border-color; } @@ -164,6 +164,10 @@ $footer-border-color: #CCC; } } + main { + flex: 1 1 auto; + } + .tab-content { padding: 2em; flex: 1 1 auto; @@ -350,6 +354,64 @@ $footer-border-color: #CCC; } } + .theme-switcher { + position: absolute; + top: 0; + right: 8px; + height: 54px; + display: flex; + align-items: center; + z-index: 2; + margin: 15px 20px 0 0; + } + + .theme-toggle { + position: relative; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + align-items: stretch; + min-width: 156px; + height: 30px; + padding: 0; + border: 1px solid $nav-border-color; + border-radius: 999px; + background-color: $nav-background-color; + box-shadow: inset 0 0 0 1px rgba(0,0,0,0.02); + } + + .theme-toggle-slider { + position: absolute; + left: 3px; + top: 3px; + bottom: 3px; + width: calc((100% - 12px) / 3); + border-radius: 999px; + background-color: $nav-hover-color; + transition: transform .2s ease; + z-index: 0; + } + + .theme-toggle-btn { + appearance: none; + -webkit-appearance: none; + border: 0; + background: transparent; + padding: 0; + margin: 0; + display: inline-flex; + align-items: center; + justify-content: center; + color: $nav-icon-color; + cursor: pointer; + border-radius: 999px; + position: relative; + z-index: 1; + } + + .theme-toggle-btn.active { + color: $nav-icon-active-color; + } + .github-link { @include github-icon($github-icon-color); } @@ -416,6 +478,7 @@ $footer-border-color: #CCC; @media screen and (max-width: 750px) { .nav-tab-list { border-bottom: 0; + padding-right: 8px; } .nav-tab { display: block; @@ -430,6 +493,13 @@ $footer-border-color: #CCC; .nav-tab-link[data-for].active { border-bottom-color: $nav-border-color; } + .theme-switcher { + position: static; + height: auto; + margin: 5px; + align-self: center; + transform: none; + } .tab-content-overview-info { margin-right: auto; clear: both; @@ -439,6 +509,9 @@ $footer-border-color: #CCC; display: block; width: 100%; } + header { + flex-direction: column-reverse; + } } @media screen and (max-width: 550px) { @@ -552,10 +625,24 @@ $footer-border-color: #CCC; color: #fff; } } + + .theme-toggle { + background-color: #1e1e1e; + border-color: #444; + } + .theme-toggle-slider { + background-color: #2a2a2a; + } + .theme-toggle-btn { + color: $nav-icon-color-dark; + } + .theme-toggle-btn.active { + color: #84b8ff; + } } @media (prefers-color-scheme: dark) { - .opcache-gui { + :root:not(.light) .opcache-gui { @include dark-theme; } } diff --git a/build/_languages/de.json b/build/_languages/de.json index 6ffe416..d568dd5 100644 --- a/build/_languages/de.json +++ b/build/_languages/de.json @@ -15,6 +15,7 @@ "CPU-specific optimization": "", "CSE, STRING construction": "", "Currently unused": "Aktuell unbenutzt", + "Dark": "Dunkel", "DCE (dead code elimination)": "", "Descending": "Absteigend", "DFA based optimization": "", @@ -52,6 +53,7 @@ "Last reset": "Letzter Reset", "Last used": "Letzte Verwendung", "last used": "Letzte Verwendung", + "Light": "Hell", "max cached keys": "max gecachte Schlüssel", "Memory consumption": "Speicher Verwendung", "memory usage": "Speicherauslastung", @@ -59,8 +61,8 @@ "Merge equal constants": "", "Minimal JIT (call standard VM handlers)": "", "never": "nie", - "Next": "Nächste", "Next page": "Nächste Seite", + "Next": "Nächste", "No files have been cached or you have opcache.file_cache_only<\/i> turned on": "Keine Dateien wurden gecacht oder opcache.file_cache_only<\/i> ist aktiviert", "No files have been ignored via opcache.blacklist_filename<\/i>": "Keine Dateien wurden durch opcache.blacklist_filename<\/i> ignoriert", "No files have been preloaded opcache.preload<\/i>": "Keine Dateien wurden durch opcache.preload<\/i> vorgeladen", @@ -83,8 +85,8 @@ "Perform global register allocation": "", "preload memory": "Speicher vorladen", "Preloaded": "Vorgeladen", - "Previous": "Vorheriges", "Previous page": "Vorherige Seite", + "Previous": "Vorheriges", "Profile functions on first request and compile the hottest functions afterwards": "", "Profile on the fly and compile hot functions": "", "Register allocation": "", @@ -98,7 +100,9 @@ "Sponsor this project": "Unterstütze dieses Projekt", "Start time": "Startzeit", "Start typing to filter on script path": "Filter für Skript-Pfade", + "System": "System", "the opcache.jit_buffer_size must be set to fully enable JIT": "opcache.jit_buffer_size muss gesetzt sein um JIT vollständig zu aktivieren", + "Theme": "Theme", "TMP VAR usage": "", "total memory": "verfügbarer Speicher", "Trigger": "Trigger", diff --git a/build/_languages/es.json b/build/_languages/es.json index 43eda82..36e5e18 100644 --- a/build/_languages/es.json +++ b/build/_languages/es.json @@ -15,12 +15,14 @@ "CPU-specific optimization": "Optimización específica de la CPU", "CSE, STRING construction": "Construcción CSE, STRING", "Currently unused": "Actualmente sin utilizar", + "Dark": "Oscuro", "DCE (dead code elimination)": "DCE (eliminación del código muerto)", "Descending": "Descendente", "DFA based optimization": "Optimización basada en DFA", "Directives": "Directivas", "Disable CPU-specific optimization": "Desactivar la optimización específica de la CPU", "Disable real-time update": "Desactivar la actualización en tiempo real", + "disabled due to opcache.jit setting": "ddesactivado debido a la configuración de opcache.jit", "Do not perform register allocation": "No realizar la asignación de registros", "Enable real-time update": "Activar la actualización en tiempo real", "Enable use of AVX, if the CPU supports it": "Habilitar el uso de AVX, si la CPU lo soporta", @@ -34,6 +36,7 @@ "hits": "hits", "Host": "Host", "Ignored": "Ignorado", + "incompatible with extensions that override zend_execute_ex(), such as xdebug": "incompatible con las extensiones que anulan zend_execute_ex(), como xdebug", "INIT_FCALL_BY_NAME -> DO_FCALL": "INIT_FCALL_BY_NAME -> DO_FCALL", "Inline functions": "Funciones inline", "Inline VM handlers": "Controladores de VM en línea", @@ -41,12 +44,14 @@ "Invalidate all matching files": "Invalidar todos los ficheros coincidentes", "jit buffer free": "búfer jit libre", "jit buffer": "búfer jit", + "JIT enabled": "JIT activado", "keys": "llaves", "Last modified": "Última modificación", "last modified": "última modificaciónn", "Last reset": "Último reinicio", "Last used": "Último uso", "last used": "último uso", + "Light": "Claro", "max cached keys": "llaves máximas en caché", "Memory consumption": "Consumo de memoria", "memory usage": "uso de memoria", @@ -54,13 +59,14 @@ "Merge equal constants": "Fusionar las constantes iguales", "Minimal JIT (call standard VM handlers)": "JIT mínimo (llame a controladores de VM estándar)", "never": "nunca", - "Next": "Siguiente", "Next page": "Página siguiente", + "Next": "Siguiente", "No files have been cached or you have opcache.file_cache_only<\/i> turned on": "No se han almacenado archivos en caché o tiene opcache.file_cache_only<\/i> activado", "No files have been ignored via opcache.blacklist_filename<\/i>": "No se han ignorado archivos a través de opcache.blacklist_filename<\/i>", "No files have been preloaded opcache.preload<\/i>": "No se han precargado archivos opcache.preload<\/i>", "No JIT": "No JIT", "no value": "sin valor", + "No": "No", "NOP removal": "Eliminación de NOP", "number of cached files": "número de archivos en caché", "number of cached keys": "número de llaves en caché", @@ -77,8 +83,8 @@ "Perform global register allocation": "Realizar la asignación global de registros", "preload memory": "precargar la memoria", "Preloaded": "Precargado", - "Previous": "Anterior", "Previous page": "Página anterior", + "Previous": "Anterior", "Profile functions on first request and compile the hottest functions afterwards": "Perfile las funciones en la primera solicitud y luego recopile las funciones más populares", "Profile on the fly and compile hot functions": "Perfilar sobre la marcha y compilar funciones en caliente", "Register allocation": "Asignación de registros", @@ -90,6 +96,9 @@ "Sort order": "Orden de clasificación", "Start time": "Hora de inicio", "Start typing to filter on script path": "Empiece a escribir para filtrar por ruta del script", + "System": "Sistema", + "the opcache.jit_buffer_size must be set to fully enable JIT": "el opcache.jit_buffer_size debe estar configurado para habilitar completamente JIT", + "Theme": "Tema", "TMP VAR usage": "Utilización de TMP VAR", "total memory": "memoria total", "Trigger": "Disparador", @@ -101,15 +110,10 @@ "View manual page": "Ver la página del manual", "View {0} manual entry": "Ver {0} entrada manual", "wasted memory": "memoria perdida", + "Yes": "Sí", "You have opcache.file_cache_only<\/i> turned on. As a result, the memory information is not available. Statistics and file list may also not be returned by opcache_get_statistics()<\/i>.": "Tienes opcache.file_cache_only activado. Como resultado, la información de la memoria no está disponible. Es posible que las estadísticas y la lista de archivos tampoco sean devueltas por opcache_get_statistics()<\/i>.", "{0} files cached": "{0} archivos almacenados en caché", "{0} files cached, {1} showing due to filter '{2}'": "{0} archivos almacenados en caché, {1} se visualizan debido al filtro '{2}'", "{0} ignore file locations": "{0} ignorar ubicaciones de archivos", - "{0} preloaded files": "{0} archivos precargados", - "JIT enabled": "JIT activado", - "disabled due to opcache.jit setting": "ddesactivado debido a la configuración de opcache.jit", - "the opcache.jit_buffer_size must be set to fully enable JIT": "el opcache.jit_buffer_size debe estar configurado para habilitar completamente JIT", - "incompatible with extensions that override zend_execute_ex(), such as xdebug": "incompatible con las extensiones que anulan zend_execute_ex(), como xdebug", - "Yes": "Sí", - "No": "No" + "{0} preloaded files": "{0} archivos precargados" } diff --git a/build/_languages/example.json b/build/_languages/example.json index a8abca5..aff73d3 100644 --- a/build/_languages/example.json +++ b/build/_languages/example.json @@ -15,6 +15,7 @@ "CPU-specific optimization": "", "CSE, STRING construction": "", "Currently unused": "", + "Dark": "", "DCE (dead code elimination)": "", "Descending": "", "DFA based optimization": "", @@ -52,6 +53,7 @@ "Last reset": "", "Last used": "", "last used": "", + "Light": "", "max cached keys": "", "Memory consumption": "", "memory usage": "", @@ -59,8 +61,8 @@ "Merge equal constants": "", "Minimal JIT (call standard VM handlers)": "", "never": "", - "Next": "", "Next page": "", + "Next": "", "No files have been cached or you have opcache.file_cache_only<\/i> turned on": "", "No files have been ignored via opcache.blacklist_filename<\/i>": "", "No files have been preloaded opcache.preload<\/i>": "", @@ -83,8 +85,8 @@ "Perform global register allocation": "", "preload memory": "", "Preloaded": "", - "Previous": "", "Previous page": "", + "Previous": "", "Profile functions on first request and compile the hottest functions afterwards": "", "Profile on the fly and compile hot functions": "", "Register allocation": "", @@ -98,7 +100,9 @@ "Sponsor this project": "", "Start time": "", "Start typing to filter on script path": "", + "System": "", "the opcache.jit_buffer_size must be set to fully enable JIT": "", + "Theme": "", "TMP VAR usage": "", "total memory": "", "Trigger": "", diff --git a/build/_languages/fr.json b/build/_languages/fr.json index a808a1c..70e4fb5 100644 --- a/build/_languages/fr.json +++ b/build/_languages/fr.json @@ -15,12 +15,14 @@ "CPU-specific optimization": "Optimisation spécifique au CPU", "CSE, STRING construction": "Construction CSE, STRING", "Currently unused": "Actuellement inutilisé", + "Dark": "Sombre", "DCE (dead code elimination)": "DCE (élimination du code mort)", "Descending": "Décroissant", "DFA based optimization": "Optimisation basée sur DFA", "Directives": "Directives", "Disable CPU-specific optimization": "Désactiver l'optimisation spécifique au processeur", "Disable real-time update": "Désactiver la mise à jour en temps réel", + "disabled due to opcache.jit setting": "désactivé en raison du paramètre opcache.jit", "Do not perform register allocation": "Ne pas effectuer d'allocation de registre", "Enable real-time update": "Activer la mise à jour en temps réel", "Enable use of AVX, if the CPU supports it": "Activer l'utilisation d'AVX, si le CPU le prend en charge", @@ -34,6 +36,7 @@ "hits": "accès", "Host": "Hôte", "Ignored": "Ignoré", + "incompatible with extensions that override zend_execute_ex(), such as xdebug": "incompatible avec les extensions qui remplacent zend_execute_ex(), telles que xdebug", "INIT_FCALL_BY_NAME -> DO_FCALL": "INIT_FCALL_BY_NAME -> DO_FCALL", "Inline functions": "Fonctions inline", "Inline VM handlers": "Gestionnaires de VM inline", @@ -41,12 +44,14 @@ "Invalidate all matching files": "Invalider tous les fichiers correspondants", "jit buffer free": "tampon jit libre", "jit buffer": "tampon jit", + "JIT enabled": "JIT activé", "keys": "clés", "Last modified": "Dernière modification", "last modified": "dernière modification", "Last reset": "Dernière réinitialisation", "Last used": "Dernière utilisation", "last used": "dernière utilisation", + "Light": "Clair", "max cached keys": "max de clés en cache", "Memory consumption": "Consommation mémoire", "memory usage": "utilisation de la mémoire", @@ -60,6 +65,7 @@ "No files have been preloaded opcache.preload<\/i>": "Aucun fichier n'a été préchargé opcache.preload<\/i>", "No JIT": "Pas de JIT", "no value": "pas de valeur", + "No": "Non", "NOP removal": "Suppression de NOP", "number of cached files": "nombre de fichiers en cache", "number of cached keys": "nombre de clés en cache", @@ -88,6 +94,9 @@ "Sort order": "Ordre de tri", "Start time": "Heure de début", "Start typing to filter on script path": "Commencez à taper pour filtrer sur le chemin du script", + "System": "Système", + "the opcache.jit_buffer_size must be set to fully enable JIT": "le opcache.jit_buffer_size doit être défini pour activer complètement JIT", + "Theme": "Thème", "TMP VAR usage": "Utilisation TMP VAR", "total memory": "mémoire totale", "Trigger": "Déclencheur", @@ -99,15 +108,10 @@ "View manual page": "Voir la page du manuel", "View {0} manual entry": "Voir la page {0} du manuel", "wasted memory": "mémoire perdue", + "Yes": "Oui", "You have opcache.file_cache_only<\/i> turned on. As a result, the memory information is not available. Statistics and file list may also not be returned by opcache_get_statistics()<\/i>.": "Vous avez opcache.file_cache_only<\/i> activé. Par conséquent, les informations sur la mémoire ne sont pas disponibles. Les statistiques et la liste de fichiers peuvent également ne pas être renvoyées par opcache_get_statistics()<\/i>.", "{0} files cached": "{0} fichiers mis en cache", "{0} files cached, {1} showing due to filter '{2}'": "{0} fichiers mis en cache, {1} s'affichent en raison du filtre '{2}'", "{0} ignore file locations": "{0} ignore les fichiers en fonction de l'emplacement", - "{0} preloaded files": "{0} fichiers préchargés", - "JIT enabled": "JIT activé", - "disabled due to opcache.jit setting": "désactivé en raison du paramètre opcache.jit", - "the opcache.jit_buffer_size must be set to fully enable JIT": "le opcache.jit_buffer_size doit être défini pour activer complètement JIT", - "incompatible with extensions that override zend_execute_ex(), such as xdebug": "incompatible avec les extensions qui remplacent zend_execute_ex(), telles que xdebug", - "Yes": "Oui", - "No": "Non" + "{0} preloaded files": "{0} fichiers préchargés" } diff --git a/index.php b/index.php index 0988d38..195fdaf 100644 --- a/index.php +++ b/index.php @@ -528,7 +528,7 @@ protected function jitState(array $status, array $directives): array @@ -608,6 +608,33 @@ class Interface extends React.Component { const v = document.cookie.match(`(^|;) ?${this.props.cookie.name}=([^;]*)(;|$)`); return v ? !!v[2] : false; }); + _defineProperty(this, "getStoredTheme", () => { + try { + const t = localStorage.getItem(Interface.THEME_STORAGE_KEY); + return t === 'light' || t === 'dark' || t === 'system' ? t : 'system'; + } catch (e) { + return 'system'; + } + }); + _defineProperty(this, "applyTheme", theme => { + const root = document.documentElement; + root.classList.remove('dark'); + root.classList.remove('light'); + if (theme === 'dark') { + root.classList.add('dark'); + } else if (theme === 'light') { + root.classList.add('light'); + } + }); + _defineProperty(this, "setTheme", theme => { + this.setState({ + theme + }); + try { + localStorage.setItem(Interface.THEME_STORAGE_KEY, theme); + } catch (e) {} + this.applyTheme(theme); + }); _defineProperty(this, "txt", (text, ...args) => { if (this.props.language !== null && this.props.language.hasOwnProperty(text) && this.props.language[text]) { text = this.props.language[text]; @@ -620,7 +647,8 @@ class Interface extends React.Component { this.state = { realtime: this.getCookie(), resetting: false, - opstate: props.opstate + opstate: props.opstate, + theme: this.getStoredTheme() }; this.polling = false; this.isSecure = window.location.protocol === 'https:'; @@ -628,29 +656,33 @@ class Interface extends React.Component { this.startTimer(); } } + componentDidMount() { + this.applyTheme(this.state.theme); + } render() { const { opstate, realtimeRefresh, ...otherProps } = this.props; - return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("header", null, /*#__PURE__*/React.createElement(MainNavigation, _extends({}, otherProps, { + return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(MainNavigation, _extends({}, otherProps, { opstate: this.state.opstate, realtime: this.state.realtime, resetting: this.state.resetting, realtimeHandler: this.realtimeHandler, resetHandler: this.resetHandler, + theme: this.state.theme, + onThemeChange: this.setTheme, txt: this.txt - }))), /*#__PURE__*/React.createElement(Footer, { + })), /*#__PURE__*/React.createElement(Footer, { version: this.props.opstate.version.gui, txt: this.txt })); } } +_defineProperty(Interface, "THEME_STORAGE_KEY", 'opcache_gui_theme'); function MainNavigation(props) { - return /*#__PURE__*/React.createElement("nav", { - className: "main-nav" - }, /*#__PURE__*/React.createElement(Tabs, null, /*#__PURE__*/React.createElement("div", { + return /*#__PURE__*/React.createElement(Tabs, props, /*#__PURE__*/React.createElement("div", { label: props.txt("Overview"), tabId: "overview", tabIndex: 1 @@ -723,7 +755,62 @@ className: `nav-tab-link-reset${props.resetting ? ' is-resetting pulse' : ''}`, className: `nav-tab-link-realtime${props.realtime ? ' live-update pulse' : ''}`, handler: props.realtimeHandler, tabIndex: 6 - }))); + })); +} +function ThemeSwitcher(props) { + const themeOrder = ['light', 'dark', 'system']; + const index = Math.max(0, themeOrder.indexOf(props.theme)); + const set = t => props.onThemeChange && props.onThemeChange(t); + const btn = (t, icon, label) => /*#__PURE__*/React.createElement("button", { + type: "button", + className: `theme-toggle-btn${props.theme === t ? ' active' : ''}`, + "aria-pressed": props.theme === t, + "aria-label": props.txt(label), + title: props.txt(label), + onClick: () => set(t) + }, icon, /*#__PURE__*/React.createElement("span", { + className: "sr-only" + }, props.txt(label))); + const SunIcon = /*#__PURE__*/React.createElement("svg", { + width: "18", + height: "18", + viewBox: "0 0 24 24", + fill: "currentColor", + "aria-hidden": "true" + }, /*#__PURE__*/React.createElement("path", { + d: "M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.8 1.42-1.42zm10.45 12.02l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM12 4V1h-0v3h0zm0 19v-3h0v3h0zM4 12H1v0h3v0zm19 0h-3v0h3v0zM6.76 19.16l-1.42 1.42-1.79-1.8 1.41-1.41 1.8 1.79zM17.24 4.84l1.4-1.4 1.8 1.79-1.41 1.41-1.79-1.8zM12 6a6 6 0 100 12 6 6 0 000-12z" + })); + const MoonIcon = /*#__PURE__*/React.createElement("svg", { + width: "18", + height: "18", + viewBox: "0 0 24 24", + fill: "currentColor", + "aria-hidden": "true" + }, /*#__PURE__*/React.createElement("path", { + d: "M21 12.79A9 9 0 1111.21 3a7 7 0 109.79 9.79z" + })); + const LaptopIcon = /*#__PURE__*/React.createElement("svg", { + width: "18", + height: "18", + viewBox: "0 0 24 24", + fill: "currentColor", + "aria-hidden": "true" + }, /*#__PURE__*/React.createElement("path", { + d: "M4 5h16a1 1 0 011 1v9H3V6a1 1 0 011-1zm-2 12h20a1 1 0 01-1 1H3a1 1 0 01-1-1z" + })); + return /*#__PURE__*/React.createElement("div", { + className: "theme-switcher", + "aria-label": props.txt('Theme') + }, /*#__PURE__*/React.createElement("div", { + className: "theme-toggle", + role: "radiogroup", + "aria-label": props.txt('Theme') + }, /*#__PURE__*/React.createElement("div", { + className: "theme-toggle-slider", + style: { + transform: `translateX(${index * 100}%)` + } + }), btn('light', SunIcon, 'Light'), btn('dark', MoonIcon, 'Dark'), btn('system', LaptopIcon, 'System'))); } class Tabs extends React.Component { constructor(props) { @@ -745,7 +832,12 @@ class Tabs extends React.Component { } } = this; const children = this.props.children.filter(Boolean); - return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("ul", { + console.log(this.props); + return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("header", null, /*#__PURE__*/React.createElement(ThemeSwitcher, { + theme: this.props.theme, + onThemeChange: this.props.onThemeChange, + txt: this.props.txt + }), /*#__PURE__*/React.createElement("nav", null, /*#__PURE__*/React.createElement("ul", { className: "nav-tab-list" }, children.map(child => { const { @@ -764,7 +856,7 @@ className: className, tabIndex: tabIndex, tabId: tabId }); - })), /*#__PURE__*/React.createElement("div", { + })))), /*#__PURE__*/React.createElement("main", null, /*#__PURE__*/React.createElement("div", { className: "tab-content" }, children.map(child => /*#__PURE__*/React.createElement("div", { key: child.props.label, @@ -772,7 +864,7 @@ className: "tab-content" display: child.props.label === activeTab ? 'block' : 'none' }, id: `${child.props.tabId}-content` - }, child.props.children)))); + }, child.props.children))))); } } class Tab extends React.Component { From 2ce7e16a78c8d7d8c9a2a59f8fae8682bb2968b7 Mon Sep 17 00:00:00 2001 From: Andrew Collington Date: Sun, 5 Oct 2025 01:29:42 +0100 Subject: [PATCH 6/6] Style and interface improvements - Inline SVG, making colourization easier - Reworked some styles - Different icons and animation in top navigation --- build/_frontend/interface.jsx | 29 +- build/_frontend/interface.scss | 870 +++++++++++++++++---------------- build/template.phps | 4 +- index.php | 62 ++- package.json | 2 +- 5 files changed, 536 insertions(+), 431 deletions(-) diff --git a/build/_frontend/interface.jsx b/build/_frontend/interface.jsx index 120f38c..e11e622 100644 --- a/build/_frontend/interface.jsx +++ b/build/_frontend/interface.jsx @@ -205,14 +205,25 @@ function MainNavigation(props) { className={`nav-tab-link-reset${props.resetting ? ' is-resetting pulse' : ''}`} handler={props.resetHandler} tabIndex={5} + icon={( + + )} >
} { props.allow.realtime && } @@ -291,7 +302,7 @@ class Tabs extends React.Component {
); } diff --git a/build/_frontend/interface.scss b/build/_frontend/interface.scss index de4c5b7..70b6eff 100644 --- a/build/_frontend/interface.scss +++ b/build/_frontend/interface.scss @@ -42,10 +42,6 @@ $footer-border-color: #CCC; @return "rgb(#{$r}, #{$g}, #{$b})"; } -@mixin github-icon($color) { - background-image: url('data:image/svg+xml;utf8,'); -} - :root { --opcache-gui-graph-track-fill-color: #{$widget-graph-fill-color}; --opcache-gui-graph-track-background-color: #{$widget-graph-background-color}; @@ -60,6 +56,12 @@ $footer-border-color: #CCC; display: flex; flex-direction: column; + #interface { + flex: 1 1 auto; + display: flex; + flex-direction: column; + } + .hide { display: none; } @@ -84,6 +86,7 @@ $footer-border-color: #CCC; min-height: 0; padding-top: 20px; } + .main-nav { position: relative; } @@ -94,10 +97,12 @@ $footer-border-color: #CCC; padding-right: 160px; // reserve space for theme switcher on the right margin: 0; border-bottom: 1px solid $nav-border-color; + display: flex; + align-items: end; } .nav-tab { - display: inline-block; + display: inline-flex; margin: 0 0 -1px 0; padding: 15px 30px; border: 1px solid transparent; @@ -106,6 +111,7 @@ $footer-border-color: #CCC; background-color: $nav-background-color; cursor: pointer; user-select: none; + align-items: center; &:hover { background-color: $nav-hover-color; @@ -128,527 +134,571 @@ $footer-border-color: #CCC; } } - .nav-tab-link-reset { - background-image: url('data:image/svg+xml;utf8,'); - - &.is-resetting { - background-image: url('data:image/svg+xml;utf8,'); + .nav-tab-link-reset, .nav-tab-link-realtime { + > svg { + overflow: visible; + width: 1.1rem; + height: 1.1rem; + margin-right: 0.5em; + + > path { + fill: $nav-icon-color; + } } - } - .nav-tab-link-realtime { - background-image: url('data:image/svg+xml;utf8,'); - - &.live-update { - background-image: url('data:image/svg+xml;utf8,'); + &.activated { + > svg > path { + fill: $nav-icon-active-color; + transform-origin: 50% 50%; + display: inline-block; + } } } - .nav-tab-link-reset, .nav-tab-link-realtime { - position: relative; - padding-left: 50px; + .nav-tab-link-reset { + &.activated { + > svg > path { + animation: spin-all 2s linear infinite; + } + } - &.pulse::before { - content: ""; - position: absolute; - top: 12px; - left: 25px; - width: 18px; - height: 18px; - z-index: 10; - opacity: 0; - background-color: transparent; - border: 2px solid $nav-icon-active-color; - border-radius: 100%; - animation: pulse 2s linear infinite; + &.is-resetting { + > svg > path { + fill: $nav-icon-active-color; + } + } } - } - main { - flex: 1 1 auto; + .nav-tab-link-realtime { + &.activated { + > svg > path { + animation: spin-pause 2s ease-in infinite; + } + } + } } - .tab-content { - padding: 2em; - flex: 1 1 auto; - min-height: 0; - overflow: auto; - } + main { + flex: 1 1 auto; + } - .tab-content-overview-counts { - width: 270px; - float: right; - } + .tab-content { + padding: 2em; + flex: 1 1 auto; + min-height: 0; + overflow: auto; + } - .tab-content-overview-info { - margin-right: 280px; - } + .tab-content-overview-counts { + width: 270px; + float: right; + } - .graph-widget { - max-width: 100%; - height: auto; - margin: 0 auto; - display: flex; - position: relative; + .tab-content-overview-info { + margin-right: 280px; + } - .widget-value { - display: flex; - align-items: center; - justify-content: center; - text-align: center; - position: absolute; - top: 0; - width: 100%; - height: 100%; + .graph-widget { + max-width: 100%; + height: auto; margin: 0 auto; - font-size: 3.2em; - font-weight: 100; - color: $widget-graph-fill-color; - user-select: none; - } - } + display: flex; + position: relative; - .widget-panel { - background-color: $widget-background-color; - margin-bottom: 10px; - } + .widget-value { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + position: absolute; + top: 0; + width: 100%; + height: 100%; + margin: 0 auto; + font-size: 3.2em; + font-weight: 100; + color: $widget-graph-fill-color; + user-select: none; + } + } - .widget-header { - background-color: $widget-header-color; - padding: 4px 6px; - margin: 0; - text-align: center; - font-size: 1rem; - font-weight: bold; - } + .widget-panel { + background-color: $widget-background-color; + margin-bottom: 10px; + } - .widget-value { - margin: 0; - text-align: center; + .widget-header { + background-color: $widget-header-color; + padding: 4px 6px; + margin: 0; + text-align: center; + font-size: 1rem; + font-weight: bold; + } - span.large { - color: $widget-graph-fill-color; - font-size: 80pt; + .widget-value { margin: 0; - padding: 0; text-align: center; - + span { - font-size: 20pt; - margin: 0; + span.large { color: $widget-graph-fill-color; + font-size: 80pt; + margin: 0; + padding: 0; + text-align: center; + + + span { + font-size: 20pt; + margin: 0; + color: $widget-graph-fill-color; + } } } - } - - .widget-info { - margin: 0; - padding: 10px; - * { + .widget-info { margin: 0; - line-height: 1.75em; - text-align: left; + padding: 10px; + + * { + margin: 0; + line-height: 1.75em; + text-align: left; + } } - } - .tables { - margin: 0 0 1em 0; - border-collapse: collapse; - width: 100%; - table-layout: fixed; + .tables { + margin: 0 0 1em 0; + border-collapse: collapse; + width: 100%; + table-layout: fixed; + + tr { + &:nth-child(odd) { + background-color: $table-row-color; + } - tr { - &:nth-child(odd) { - background-color: $table-row-color; + &:nth-child(even) { + background-color: $table-row-color-alternative; + } } - &:nth-child(even) { - background-color: $table-row-color-alternative; + th { + text-align: left; + padding: 6px; + background-color: $table-header-color; + color: $table-header-font-color; + border-color: $table-header-border-color; + font-weight: normal; } - } - th { - text-align: left; - padding: 6px; - background-color: $table-header-color; - color: $table-header-font-color; - border-color: $table-header-border-color; - font-weight: normal; + td { + padding: 4px 6px; + line-height: 1.4em; + vertical-align: top; + border-color: $table-row-border-color; + overflow: hidden; + overflow-wrap: break-word; + text-overflow: ellipsis; + } } - td { - padding: 4px 6px; - line-height: 1.4em; - vertical-align: top; - border-color: $table-row-border-color; - overflow: hidden; - overflow-wrap: break-word; - text-overflow: ellipsis; - } - } + .directive-list { + list-style-type: none; + padding: 0; + margin: 0; - .directive-list { - list-style-type: none; - padding: 0; - margin: 0; + li { + margin-bottom: 0.5em; - li { - margin-bottom: 0.5em; + &:last-child { + margin-bottom: 0; + } - &:last-child { - margin-bottom: 0; + ul { + margin-top: 1.5em; + } } + } - ul { - margin-top: 1.5em; + .file-filter { + width: 520px; + } + + .file-metainfo { + font-size: 80%; + + &.invalid { + font-style: italic; } } - } - .file-filter { - width: 520px; - } + .file-pathname { + width: 70%; + display: block; + } - .file-metainfo { - font-size: 80%; + .nav-tab-link-reset, + .nav-tab-link-realtime, + .github-link, + .sponsor-link { + background-repeat: no-repeat; + background-color: transparent; + } - &.invalid { - font-style: italic; + .nav-tab-link-reset, + .nav-tab-link-realtime { + background-position: 24px 50%; } - } - .file-pathname { - width: 70%; - display: block; - } + .main-footer { + border-top: 1px solid $footer-border-color; + padding: 1em 2em; + display: flex; + align-items: center; + } - .nav-tab-link-reset, - .nav-tab-link-realtime, - .github-link, - .sponsor-link { - background-repeat: no-repeat; - background-color: transparent; - } + .github-link, + .sponsor-link { + background-position: 0 50%; + padding: 2em 0 2em 2.3em; + text-decoration: none; + opacity: 0.7; + font-size: 80%; + display: flex; + align-items: center; - .nav-tab-link-reset, - .nav-tab-link-realtime { - background-position: 24px 50%; - } + &:hover { + opacity: 1; + } - .main-footer { - border-top: 1px solid $footer-border-color; - padding: 1em 2em; - } + > svg { + height: 1rem; + width: 1rem; + margin-right: 0.25rem; + } + } - .github-link, - .sponsor-link { - background-position: 0 50%; - padding: 2em 0 2em 2.3em; - text-decoration: none; - opacity: 0.7; - font-size: 80%; + .theme-switcher { + position: absolute; + top: 0; + right: 8px; + height: 54px; + display: flex; + align-items: center; + z-index: 2; + margin: 15px 20px 0 0; + } - &:hover { - opacity: 1; + .theme-toggle { + position: relative; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + align-items: stretch; + min-width: 156px; + height: 30px; + padding: 0; + border: 1px solid $nav-border-color; + border-radius: 999px; + background-color: $nav-background-color; + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.02); } - } - .theme-switcher { - position: absolute; - top: 0; - right: 8px; - height: 54px; - display: flex; - align-items: center; - z-index: 2; - margin: 15px 20px 0 0; - } + .theme-toggle-slider { + position: absolute; + left: 3px; + top: 3px; + bottom: 3px; + width: calc((100% - 12px) / 3); + border-radius: 999px; + background-color: $nav-hover-color; + transition: transform .2s ease; + z-index: 0; + } - .theme-toggle { - position: relative; - display: grid; - grid-template-columns: 1fr 1fr 1fr; - align-items: stretch; - min-width: 156px; - height: 30px; - padding: 0; - border: 1px solid $nav-border-color; - border-radius: 999px; - background-color: $nav-background-color; - box-shadow: inset 0 0 0 1px rgba(0,0,0,0.02); - } + .theme-toggle-btn { + appearance: none; + -webkit-appearance: none; + border: 0; + background: transparent; + padding: 0; + margin: 0; + display: inline-flex; + align-items: center; + justify-content: center; + color: $nav-icon-color; + cursor: pointer; + border-radius: 999px; + position: relative; + z-index: 1; + } - .theme-toggle-slider { - position: absolute; - left: 3px; - top: 3px; - bottom: 3px; - width: calc((100% - 12px) / 3); - border-radius: 999px; - background-color: $nav-hover-color; - transition: transform .2s ease; - z-index: 0; - } + .theme-toggle-btn.active { + color: $nav-icon-active-color; + } - .theme-toggle-btn { - appearance: none; - -webkit-appearance: none; - border: 0; - background: transparent; - padding: 0; - margin: 0; - display: inline-flex; - align-items: center; - justify-content: center; - color: $nav-icon-color; - cursor: pointer; - border-radius: 999px; - position: relative; - z-index: 1; - } + .github-link > svg > path { + fill: $nav-icon-color; + } - .theme-toggle-btn.active { - color: $nav-icon-active-color; - } + .sponsor-link { + margin-left: 2em; + } - .github-link { - @include github-icon($github-icon-color); - } + .file-cache-only { + margin-top: 0; + } - .sponsor-link { - background-image: url('data:image/svg+xml;utf8,'); - margin-left: 2em; - } + .paginate-filter { + display: flex; + align-items: baseline; + justify-content: space-between; + flex-wrap: wrap; - .file-cache-only { - margin-top: 0; - } + .filter > * { + padding: 3px; + margin: 3px 3px 10px 0; + } + } - .paginate-filter { - display: flex; - align-items: baseline; - justify-content: space-between; - flex-wrap: wrap; + .pagination { + margin: 10px 0; + padding: 0; - .filter > * { - padding: 3px; - margin: 3px 3px 10px 0; + li { + display: inline-block; + + a { + display: inline-flex; + align-items: center; + white-space: nowrap; + line-height: 1; + padding: 0.5rem 0.75rem; + border-radius: 3px; + text-decoration: none; + height: 100%; + + &.arrow { + font-size: 1.1rem; + } + + &:active { + transform: translateY(2px); + } + + &.active { + background-color: $pagination-active-color; + color: $pagination-active-font-color; + } + + &:hover:not(.active) { + background-color: $pagination-hover-color; + color: $pagination-hover-font-color; + } + } + } } - } - .pagination { - margin: 10px 0; - padding: 0; + @media screen and (max-width: 750px) { + .opcache-gui { + .nav-tab-list { + border-bottom: 0; + display: flex; + flex-direction: column; + padding: 0; + align-items: center; + } - li { - display: inline-block; + .nav-tab { + margin: 0; + border: 0; + border-top: 1px solid $nav-border-color; + width: 100%; + align-items: center; + justify-content: center; + + &:last-child { + border-bottom: 1px solid $nav-border-color; + } + + &.active { + border: 0; + border-top: 1px solid $nav-border-color; + background: rgba($nav-header-color, 0.1); + } + } - a { - display: inline-flex; - align-items: center; - white-space: nowrap; - line-height: 1; - padding: 0.5rem 0.75rem; - border-radius: 3px; - text-decoration: none; - height: 100%; + .nav-tab-link { + display: block; + margin: 0 10px; + padding: 10px 0 10px 30px; + border: 0; + } - &.arrow { - font-size: 1.1rem; + .theme-switcher { + position: static; + height: auto; + margin: 5px; + align-self: center; + transform: none; } - &:active { - transform: translateY(2px); + .tab-content-overview-info { + margin-right: auto; + clear: both; } - &.active { - background-color: $pagination-active-color; - color: $pagination-active-font-color; + .tab-content-overview-counts { + position: relative; + display: block; + width: 100%; } - &:hover:not(.active) { - background-color: $pagination-hover-color; - color: $pagination-hover-font-color; + header { + flex-direction: column-reverse; } } - } - } - @media screen and (max-width: 750px) { - .nav-tab-list { - border-bottom: 0; - padding-right: 8px; - } - .nav-tab { - display: block; - margin: 0; - } - .nav-tab-link { - display: block; - margin: 0 10px; - padding: 10px 0 10px 30px; - border: 0; - } - .nav-tab-link[data-for].active { - border-bottom-color: $nav-border-color; + @media screen and (max-width: 550px) { + .file-filter { + width: 100%; + } + } } - .theme-switcher { - position: static; - height: auto; - margin: 5px; - align-self: center; - transform: none; + + + @keyframes spin-pause { + 0% { + transform: rotate(0deg); } - .tab-content-overview-info { - margin-right: auto; - clear: both; + 50%, 100% { + transform: rotate(360deg); } - .tab-content-overview-counts { - position: relative; - display: block; - width: 100%; + } + + @keyframes spin-all { + 0% { + transform: rotate(0deg); } - header { - flex-direction: column-reverse; + 100% { + transform: rotate(360deg); } } - @media screen and (max-width: 550px) { - .file-filter { - width: 100%; + @mixin dark-theme { + background-color: #121212; + color: #ddd; + + a { + color: dodgerblue; } - } -} -@keyframes pulse { - 0% { - transform: scale(1); - opacity: 1; - } - 50%, 100% { - transform: scale(2); - opacity: 0; - } -} + .nav-tab { + background-color: transparent; + border-color: transparent; -@mixin dark-theme { - background-color: #121212; - color: #ddd; + &:hover { + background-color: #2a2a2a; + } - a { - color: dodgerblue; - } + &.active { + background-color: #1e1e1e; + border-color: transparent; + border-top-color: #84b8ff; + border-bottom-color: #1e1e1e; + } + } - .nav-tab { - background-color: transparent; - border-color: transparent; + .nav-tab-link-reset, + .nav-tab-link-realtime { + background-color: transparent; - &:hover { - background-color: #2a2a2a; + &.is-resetting, + &.live-update { + background-color: transparent; + } + + &.pulse::before { + border-color: #00e600; + } } - &.active { - background-color: #1e1e1e; - border-color: transparent; - border-top-color: #84b8ff; - border-bottom-color: #1e1e1e; + .github-link > svg > path { + fill: $github-icon-color-dark; } - } - .nav-tab-link-reset, - .nav-tab-link-realtime { - background-color: transparent; + .graph-widget .widget-value, + .widget-value span.large, + .widget-value span.large + span { + color: #84b8ff; + } - &.is-resetting, - &.live-update { - background-color: transparent; + .widget-panel { + background-color: #2a2a2a; } - &.pulse::before { - border-color: #00e600; + .widget-header { + background-color: #333; + color: #ccc; } - } - .github-link { - @include github-icon($github-icon-color-dark); - } + .tables { + tr:nth-child(odd) { + background-color: #2a2a2a; + } - .graph-widget .widget-value, - .widget-value span.large, - .widget-value span.large + span { - color: #84b8ff; - } + tr:nth-child(even) { + background-color: #1f1f1f; + } - .widget-panel { - background-color: #2a2a2a; - } + th { + background-color: #3a4a5e; + color: #ddd; + border-color: #444; + } - .widget-header { - background-color: #333; - color: #ccc; - } + td { + border-color: #333; + } + } - .tables { - tr:nth-child(odd) { - background-color: #2a2a2a; + .main-footer { + border-top-color: #444; + color: #ccc; } - tr:nth-child(even) { - background-color: #1f1f1f; + .pagination li a { + &.active { + background-color: #4d75af; + color: #fff; + } + + &:hover:not(.active) { + background-color: #ff7400; + color: #fff; + } } - th { - background-color: #3a4a5e; - color: #ddd; + .theme-toggle { + background-color: #1e1e1e; border-color: #444; } - - td { - border-color: #333; + .theme-toggle-slider { + background-color: #2a2a2a; } - } - - .main-footer { - border-top-color: #444; - color: #ccc; - } - - .pagination li a { - &.active { - background-color: #4d75af; - color: #fff; + .theme-toggle-btn { + color: $nav-icon-color-dark; } - - &:hover:not(.active) { - background-color: #ff7400; - color: #fff; + .theme-toggle-btn.active { + color: #84b8ff; } } - .theme-toggle { - background-color: #1e1e1e; - border-color: #444; - } - .theme-toggle-slider { - background-color: #2a2a2a; - } - .theme-toggle-btn { - color: $nav-icon-color-dark; - } - .theme-toggle-btn.active { - color: #84b8ff; + @media (prefers-color-scheme: dark) { + :root:not(.light) .opcache-gui { + @include dark-theme; + } } -} -@media (prefers-color-scheme: dark) { - :root:not(.light) .opcache-gui { + .dark .opcache-gui { @include dark-theme; } -} - -.dark .opcache-gui { - @include dark-theme; -} - - diff --git a/build/template.phps b/build/template.phps index 57b128d..95531e5 100644 --- a/build/template.phps +++ b/build/template.phps @@ -75,9 +75,9 @@ $opcache = (new Service($options))->handle(); - + -
+
- + -
+