Add theme presets, gradient backgrounds, and macOS-style window chrome

Replaces the old light/dark toggle with 10 fixed color themes (plus
Black & Grey and White) that drive a coordinated gradient palette across
the whole admin UI, a Google Fonts picker, and a native-page dark-mode
stylesheet that now covers modals and plugin/theme card grids. Fixes
several background/gradient seams where the sidebar, toolbar, and content
frame were each painting their own independent copy of the page gradient
instead of sharing one continuous plane.

Also adds a macOS-style traffic-light title bar to every page (close,
minimize, maximize) and a persistent bottom taskbar so a minimized page
can be restored from anywhere, plus a Plugins management page.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
nixpanel
2026-07-03 11:49:15 -05:00
co-authored by Claude Sonnet 5
parent db07e21f9e
commit 4a547d2c50
26 changed files with 1816 additions and 223 deletions
+93 -71
View File
@@ -1,7 +1,10 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
import { ref, onMounted } from 'vue';
import Sidebar from './components/Sidebar.vue';
import Toolbar from './components/Toolbar.vue';
import { applyThemePalette, applyFontFamily } from '@/pages/settings/applyLiveTheme.js';
import { initWindowChrome } from './windowChrome.js';
import { getMinimizedWindow, clearMinimizedWindow } from './taskbar.js';
const props = defineProps({
serverData: {
@@ -10,61 +13,6 @@ const props = defineProps({
},
});
// Three-state theme: 'light' | 'dark' | 'system'. Older stored values were
// just 'on'/'off' — map those forward so existing installs keep working.
const normalizeStoredTheme = (value) => {
if (value === 'on') return 'dark';
if (value === 'off') return 'light';
if (value === 'light' || value === 'dark' || value === 'system') return value;
return 'system';
};
const theme = ref(normalizeStoredTheme(props.serverData?.settings?.dark_mode));
const systemPrefersDark = ref(
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
);
let mediaQuery = null;
const onSystemChange = (e) => (systemPrefersDark.value = e.matches);
const isDark = computed(() =>
theme.value === 'dark' || (theme.value === 'system' && systemPrefersDark.value)
);
const applyTheme = () => {
document.documentElement.classList.toggle('aurora-dark', isDark.value);
};
const persistTheme = async () => {
try {
await fetch(`${props.serverData.restUrl}wp/v2/settings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': props.serverData.restNonce,
},
// Partial payload: the option's sanitize callback merges this into
// the stored settings, so we never write back the (secret-stripped)
// settings copy this page received.
body: JSON.stringify({
aurora_admin_settings: {
dark_mode: theme.value,
},
}),
});
} catch {
// Non-critical: theme still applied for this session.
}
};
const setTheme = (next) => {
theme.value = next;
applyTheme();
persistTheme();
};
watch(isDark, applyTheme);
// Minified (icon-only) menu — a per-browser preference kept in
// localStorage; the html class drives all layout via --aurora-sidebar-w.
const collapsed = ref(localStorage.getItem('aurora-menu-collapsed') === '1');
@@ -77,24 +25,52 @@ const toggleMenu = () => {
localStorage.setItem('aurora-menu-collapsed', collapsed.value ? '1' : '0');
};
// The block editor (Gutenberg) renders its own toolbar (undo/redo,
// title, save/publish) directly under Aurora's fixed toolbar — stacking
// both left a sliver of page gradient visible as a seam between them.
// Skip Aurora's toolbar there and let the editor's own bar sit flush
// against the top; shell-frame.css reads the aurora-no-toolbar class to
// also close up the top inset it reserves for the (now-absent) toolbar.
const isBlockEditor = document.body.classList.contains('block-editor-page');
// The one minimized window, if any (taskbar.js persists it in
// sessionStorage across the page load minimizing triggers). Rendered as a
// taskbar item at the bottom of every page since SiteShell mounts on all
// of them — clicking it navigates back and clears the entry.
const minimizedWindow = ref(null);
const restoreWindow = () => {
if (!minimizedWindow.value) return;
const url = minimizedWindow.value.url;
clearMinimizedWindow();
window.location.href = url;
};
onMounted(() => {
applyTheme();
minimizedWindow.value = getMinimizedWindow();
// Reserve bottom space for the taskbar (shell-frame.css) so page content
// stops above it instead of running underneath.
document.documentElement.classList.toggle('aurora-has-taskbar', !!minimizedWindow.value);
const settings = props.serverData?.settings || {};
// Every theme preset is its own self-contained dark-toned palette now —
// no separate light/dark mode, so this class is always on. It's kept
// (rather than renamed) because shell-frame.css and native-dark.css both
// still gate their rules on it.
document.documentElement.classList.add('aurora-dark');
document.documentElement.classList.toggle('aurora-no-toolbar', isBlockEditor);
applyThemePalette(settings.theme_preset || 'indigo');
applyCollapsed();
if (window.matchMedia) {
mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', onSystemChange);
applyFontFamily(settings.font_family);
// Skipped on the block editor — #wpbody-content is bypassed by the
// editor's own fixed skeleton there (see isBlockEditor above), so a
// bar injected into it would just be invisible clutter.
if (!isBlockEditor) {
initWindowChrome(props.serverData?.adminUrl);
}
// Signal the shell is live: reveals the framed content and commits to
// keeping native chrome hidden (the PHP failsafe stops watching once
// aurora-ready is present).
document.documentElement.classList.add('aurora-ready');
});
onUnmounted(() => {
if (mediaQuery) {
mediaQuery.removeEventListener('change', onSystemChange);
}
});
</script>
<template>
@@ -105,23 +81,69 @@ onUnmounted(() => {
:current-url="serverData.currentUrl"
:logo="serverData.settings?.logo || ''"
:dark-logo="serverData.settings?.dark_logo || ''"
:is-dark="isDark"
:collapsed="collapsed"
:server-data="serverData"
:theme="theme"
@set-theme="setTheme"
/>
<Toolbar
v-if="!isBlockEditor"
:server-data="serverData"
@toggle-menu="toggleMenu"
/>
<div v-if="minimizedWindow" class="aurora-taskbar">
<button type="button" class="aurora-taskbar__item" @click="restoreWindow">
<span class="aurora-taskbar__dot" />
{{ minimizedWindow.title }}
</button>
</div>
</div>
</template>
<style scoped>
/* Sidebar and Toolbar are both position:fixed, so this wrapper has no
layout role — it just groups them under one mounted root. */
/* Sidebar, Toolbar and the taskbar are all position:fixed, so this
wrapper has no layout role — it just groups them under one mounted
root. */
.site-shell {
display: contents;
}
/* Full-width docked bar (real taskbar, edge to edge) — page content
reserves space above it via html.aurora-has-taskbar in shell-frame.css
so nothing renders underneath. The item itself stays pill-shaped. */
.aurora-taskbar {
position: fixed;
left: calc(var(--aurora-sidebar-w) + var(--aurora-sidebar-gap) * 2);
right: 0;
bottom: 0;
height: var(--aurora-taskbar-h);
display: flex;
align-items: center;
padding: 0 16px;
background: var(--aurora-frame-bg);
border-top: 1px solid var(--aurora-frame-border);
box-sizing: border-box;
z-index: 200001;
transition: left 0.2s ease;
}
.aurora-taskbar__item {
display: flex;
align-items: center;
gap: 8px;
background: var(--aurora-card-bg);
border: 1px solid var(--aurora-card-border);
border-radius: 999px;
padding: 8px 18px 8px 10px;
color: var(--aurora-text);
font-size: 0.8125rem;
cursor: pointer;
}
.aurora-taskbar__item:hover {
border-color: var(--aurora-accent);
}
.aurora-taskbar__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #febc2e;
flex-shrink: 0;
}
</style>
+16 -21
View File
@@ -19,10 +19,6 @@ const props = defineProps({
type: String,
default: '',
},
isDark: {
type: Boolean,
default: false,
},
collapsed: {
type: Boolean,
default: false,
@@ -35,19 +31,13 @@ const props = defineProps({
type: Object,
required: true,
},
theme: {
type: String,
default: 'system',
},
});
const emit = defineEmits(['set-theme']);
// Custom logo from Settings > General. Dark mode prefers the dark variant
// and falls back to the standard logo; no logo set = "Aurora" wordmark.
const activeLogo = computed(() =>
props.isDark ? props.darkLogo || props.logo : props.logo
);
// Custom logo from Settings > General. Every theme preset is a dark-toned
// palette now (no separate light mode), so the dark-variant logo is always
// preferred when set; falls back to the standard logo, or the "Aurora"
// wordmark if neither is set.
const activeLogo = computed(() => props.darkLogo || props.logo);
// Normalize any admin URL down to the part after /wp-admin/ so we can
// compare the current page against menu targets regardless of host/scheme.
@@ -209,8 +199,6 @@ const isAuroraActive = (item) => normalize(item.url) === current.value;
<UserMenu
:server-data="serverData"
:collapsed="collapsed"
:theme="theme"
@set-theme="(t) => emit('set-theme', t)"
/>
</nav>
</template>
@@ -218,11 +206,18 @@ const isAuroraActive = (item) => normalize(item.url) === current.value;
<style scoped>
.sidebar {
position: fixed;
top: 0;
left: 0;
top: var(--aurora-sidebar-gap);
left: var(--aurora-sidebar-gap);
bottom: var(--aurora-sidebar-gap);
width: var(--aurora-sidebar-w);
height: 100vh;
background: var(--aurora-app-bg);
height: auto;
/* Same surface treatment as the dashboard cards (AuroraCard.vue) —
solid card background + rounded corners, floating over the page
gradient with a gap on every side now that it's inset instead of
flush against the viewport edges. */
background: var(--aurora-card-bg);
border: 1px solid var(--aurora-card-border);
border-radius: 20px;
transition: width 0.2s ease;
z-index: 99999;
box-sizing: border-box;
+3 -2
View File
@@ -129,10 +129,11 @@ onUnmounted(() => document.removeEventListener('keydown', onKeydown));
.toolbar {
position: fixed;
top: 0;
left: var(--aurora-sidebar-w);
left: calc(var(--aurora-sidebar-w) + var(--aurora-sidebar-gap) * 2);
right: 0;
height: var(--aurora-toolbar-h);
background: var(--aurora-app-bg);
/* No background — see Sidebar.vue; the fixed html/body gradient shows
through so the whole shell reads as one continuous background. */
display: flex;
align-items: center;
justify-content: space-between;
-88
View File
@@ -4,11 +4,8 @@ import { ref, computed, onMounted, onUnmounted } from 'vue';
const props = defineProps({
serverData: { type: Object, required: true },
collapsed: { type: Boolean, default: false },
theme: { type: String, default: 'system' }, // 'light' | 'dark' | 'system'
});
const emit = defineEmits(['set-theme']);
const open = ref(false);
const root = ref(null);
@@ -53,36 +50,6 @@ onUnmounted(() => document.removeEventListener('click', onDocClick));
Sign Out
</a>
</div>
<div class="user-menu__theme">
<span class="user-menu__theme-label">Theme</span>
<div class="user-menu__segmented">
<button
type="button"
:class="{ 'is-active': theme === 'light' }"
title="Light"
@click="emit('set-theme', 'light')"
>
<span class="user-menu__swatch user-menu__swatch--light" />
</button>
<button
type="button"
:class="{ 'is-active': theme === 'dark' }"
title="Dark"
@click="emit('set-theme', 'dark')"
>
<span class="user-menu__swatch user-menu__swatch--dark" />
</button>
<button
type="button"
:class="{ 'is-active': theme === 'system' }"
title="Match system"
@click="emit('set-theme', 'system')"
>
<span class="user-menu__swatch user-menu__swatch--system" />
</button>
</div>
</div>
</div>
<button
@@ -224,59 +191,4 @@ onUnmounted(() => document.removeEventListener('click', onDocClick));
color: #ef4444;
}
.user-menu__theme {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
}
.user-menu__theme-label {
font-size: 0.8125rem;
color: var(--aurora-text-muted);
}
.user-menu__segmented {
display: flex;
gap: 2px;
background: var(--aurora-bg-subtle);
border-radius: 8px;
padding: 2px;
}
.user-menu__segmented button {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: none;
background: none;
border-radius: 6px;
cursor: pointer;
padding: 3px;
}
.user-menu__segmented button.is-active {
background: var(--aurora-bg);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}
.user-menu__swatch {
display: block;
width: 100%;
height: 100%;
border-radius: 4px;
border: 1px solid rgba(127, 127, 127, 0.35);
box-sizing: border-box;
}
.user-menu__swatch--light {
background: #ffffff;
}
.user-menu__swatch--dark {
background: #18181b;
}
.user-menu__swatch--system {
background: linear-gradient(135deg, #ffffff 0%, #ffffff 50%, #18181b 50%, #18181b 100%);
}
.user-menu__segmented button.is-active .user-menu__swatch {
border-color: var(--aurora-accent);
box-shadow: 0 0 0 1px var(--aurora-accent);
}
</style>
+149 -3
View File
@@ -14,6 +14,13 @@
:root {
--aurora-sidebar-w: 264px;
--aurora-toolbar-h: 56px;
/* SiteShell.vue's taskbar, shown only while a window is minimized
(html.aurora-has-taskbar toggles the bottom space reserved for it). */
--aurora-taskbar-h: 56px;
/* Space between the sidebar card and the browser's left/top/bottom
edges (Sidebar.vue) — the toolbar and #wpcontent offsets below add
the same amount so nothing overlaps the floating sidebar. */
--aurora-sidebar-gap: 16px;
/* Breathing room between page content and the right browser edge. */
--aurora-gutter-r: 44px;
@@ -22,8 +29,15 @@
/* Light theme */
--aurora-app-bg: #fafafa; /* zinc-50 */
/* Flat fallback for the handful of consumers that need a solid color
(--aurora-app-bg itself may be a gradient once a theme preset applies). */
--aurora-app-bg-solid: #fafafa;
--aurora-bg: #ffffff; /* frame */
--aurora-card-bg: #fafafa; /* zinc-50 */
/* Dashboard cards specifically (AuroraCard.vue) — a step further from
--aurora-frame-bg than --aurora-card-bg so they read as distinct
surfaces against the Dashboard's own frame, not a near-identical tone. */
--aurora-card-bg-deep: #f0f0f2;
--aurora-bg-subtle: #f4f4f5; /* zinc-100 */
--aurora-text: #18181b; /* zinc-900 */
--aurora-text-muted: #71717a; /* zinc-500 */
@@ -34,6 +48,10 @@
--aurora-accent: rgb(71 114 250);
--aurora-accent-soft: rgb(71 114 250 / 0.1);
--aurora-accent-text: #ffffff;
/* Overridden by the font-picker setting (see SiteShell.vue::applyFontFamily);
this is the fallback when no Google Font has been chosen. */
--aurora-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* Minified menu: the toolbar's collapse button toggles this class; every
@@ -46,8 +64,10 @@ html.aurora-collapsed {
html.aurora-dark {
/* Dark theme — the immersive near-black look of the original */
--aurora-app-bg: #09090b; /* zinc-950 */
--aurora-app-bg-solid: #09090b;
--aurora-bg: #18181b; /* zinc-900 frame */
--aurora-card-bg: #1f1f23; /* slightly elevated */
--aurora-card-bg-deep: #131316;
--aurora-bg-subtle: #27272a; /* zinc-800 */
--aurora-text: #f4f4f5; /* zinc-100 */
--aurora-text-muted: #a1a1aa; /* zinc-400 */
@@ -64,6 +84,14 @@ html.aurora-dark {
html.aurora-ready,
html.aurora-ready body {
background: var(--aurora-app-bg);
/* Fixed so the gradient stays pinned to the viewport instead of
scrolling with document height — the sidebar/toolbar are also
position:fixed and paint no background of their own (see
Sidebar.vue/Toolbar.vue), so this one gradient plane has to line up
with them at every scroll position to read as a single unified
background instead of a seam between "sidebar" and "page". */
background-attachment: fixed;
font-family: var(--aurora-font-family);
}
/* !important here defeats WordPress's own responsive #wpcontent margin
@@ -73,12 +101,31 @@ html.aurora-ready body {
rather than on #wpbody, so content rendered above #wpbody — like the
takeover dashboard — also clears the fixed toolbar. */
html.aurora-ready #wpcontent {
margin-left: var(--aurora-sidebar-w) !important;
margin-left: calc(var(--aurora-sidebar-w) + var(--aurora-sidebar-gap) * 2) !important;
padding-left: 0 !important;
padding-top: var(--aurora-toolbar-h);
transition: margin-left 0.2s ease;
}
/* The block editor (Gutenberg) doesn't live inside #wpcontent's normal
box model — it renders its own skeleton as position:fixed, inset:0,
completely independent of the margin-left above. Left unchecked it
spans the full viewport and renders underneath the (higher z-index)
sidebar and toolbar. Inset it the same amount #wpcontent gets. */
html.aurora-ready .interface-interface-skeleton {
left: calc(var(--aurora-sidebar-w) + var(--aurora-sidebar-gap) * 2) !important;
top: var(--aurora-toolbar-h) !important;
transition: left 0.2s ease;
}
/* Aurora's own toolbar is skipped on the block editor (SiteShell.vue) —
its own toolbar sits flush against the top instead, so no toolbar
height needs to be reserved here; just the same top gap the sidebar
gets, so the two line up. */
html.aurora-ready.aurora-no-toolbar .interface-interface-skeleton {
top: var(--aurora-sidebar-gap) !important;
}
html.aurora-ready #wpbody-content {
background: var(--aurora-frame-bg);
color: var(--aurora-text);
@@ -89,12 +136,111 @@ html.aurora-ready #wpbody-content {
padding: 32px;
min-height: calc(100vh - var(--aurora-toolbar-h) - 40px);
box-sizing: border-box;
/* WP core sets #wpbody-content { float: left; width: 100% }, which (with
box-sizing left alone) ignores the margin above and overflows past the
right edge by margin-left + margin-right. Force it back to shrink-to-fit
so the margin is actually respected. */
float: none;
width: auto;
}
/* SiteShell.vue's taskbar docks along the bottom edge only while a window
is minimized — push content up by its height so pages stop above it
instead of rendering underneath. */
html.aurora-has-taskbar #wpbody-content {
margin-bottom: calc(24px + var(--aurora-taskbar-h));
}
html.aurora-has-taskbar .aurora-dashboard-frame {
margin-bottom: calc(24px + var(--aurora-taskbar-h));
}
/* macOS-style title bar (windowChrome.js). Most pages get it injected as
the first child of #wpbody-content — negative margins cancel the 32px
padding above so it spans edge-to-edge like a real window title bar. */
.aurora-winbar {
display: flex;
align-items: center;
gap: 8px;
}
#wpbody-content > .aurora-winbar {
margin: -32px -32px 20px -32px;
padding: 14px 16px;
border-bottom: 1px solid var(--aurora-frame-border);
border-top-left-radius: var(--aurora-radius-lg);
border-top-right-radius: var(--aurora-radius-lg);
}
.aurora-winbar__dot {
width: 12px;
height: 12px;
border-radius: 50%;
border: none;
padding: 0;
cursor: pointer;
}
.aurora-winbar__dot:hover {
filter: brightness(1.15);
}
.aurora-winbar__dot--close {
background: #ff5f57;
}
.aurora-winbar__dot--minimize {
background: #febc2e;
}
.aurora-winbar__dot--maximize {
background: #28c840;
}
/* The Dashboard bypasses #wpbody-content (DashboardTakeover.php renders
it into a separate sibling — see windowChrome.js), so it never got the
card frame every other page has. windowChrome.js wraps the bar + the
dashboard's mount point in this div to give it the same treatment. */
.aurora-dashboard-frame {
background: var(--aurora-frame-bg);
border: 1px solid var(--aurora-frame-border);
border-radius: var(--aurora-radius-lg);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
margin: 16px var(--aurora-gutter-r) 24px 24px;
box-sizing: border-box;
}
.aurora-dashboard-frame > .aurora-winbar {
padding: 14px 20px;
border-bottom: 1px solid var(--aurora-frame-border);
border-top-left-radius: var(--aurora-radius-lg);
border-top-right-radius: var(--aurora-radius-lg);
}
/* Yellow dot: on the Dashboard itself there's nowhere further "home" to
send it, so minimize just collapses the frame down to the title bar in
place — everywhere else, minimize navigates to the dashboard instead
(see windowChrome.js) and SiteShell.vue's taskbar offers a way back. */
html.aurora-minimized #aurora-admin-dashboard-root {
display: none !important;
}
/* Green dot: hide Aurora's own chrome (sidebar/toolbar) and let the
content reclaim the full browser window, like restoring a real OS
window to fullscreen. */
html.aurora-maximized .sidebar,
html.aurora-maximized .toolbar {
display: none !important;
}
html.aurora-ready.aurora-maximized #wpcontent {
margin-left: 0 !important;
}
html.aurora-ready.aurora-maximized .interface-interface-skeleton {
left: 0 !important;
}
html.aurora-maximized .aurora-taskbar {
left: 0 !important;
}
/* The settings page renders its own two boxed panels, so drop the outer
content frame there — leaving just those panels on the dark backdrop. */
content frame there — leaving just those panels on the dark backdrop.
No background here: #wpbody-content is inset from the sidebar/gutter,
not the full viewport, so painting var(--aurora-app-bg) on it computes
its own independent (and visibly mismatched) gradient instance instead
of sharing the one fixed gradient plane painted on html/body. */
html.aurora-ready body.toplevel_page_aurora-admin #wpbody-content {
background: var(--aurora-app-bg);
border: none;
border-radius: 0;
box-shadow: none;
+39
View File
@@ -0,0 +1,39 @@
/**
* Tracks the one "minimized" window across page loads. Aurora is a normal
* multi-page WP admin (each nav is a full page load), so a real taskbar
* needs to survive navigation — sessionStorage (tab-scoped, clears when the
* tab closes) is enough for that. Only one item is tracked at a time: this
* is "minimize sends you home with a way back", not a full window manager.
*/
const STORAGE_KEY = 'aurora-minimized-window';
export function getMinimizedWindow() {
try {
const raw = sessionStorage.getItem(STORAGE_KEY);
return raw ? JSON.parse(raw) : null;
} catch {
return null;
}
}
export function setMinimizedWindow(title, url) {
try {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify({ title, url }));
} catch {
/* sessionStorage unavailable (e.g. private mode) — minimize just won't persist */
}
}
export function clearMinimizedWindow() {
try {
sessionStorage.removeItem(STORAGE_KEY);
} catch {
/* noop */
}
}
/** Best-effort readable label for the current page, used as the taskbar item's title. */
export function currentPageTitle() {
const raw = document.title || 'Window';
return raw.split('‹')[0].trim() || 'Window';
}
+81
View File
@@ -0,0 +1,81 @@
/**
* Adds a macOS-style traffic-light title bar to the top of the page content.
* Most pages render inside #wpbody-content (native WP pages and all of
* Aurora's own Vue pages, since each mounts its root div there). The
* Dashboard is the one exception — DashboardTakeover.php renders it into a
* separate #aurora-admin-dashboard-root sibling, above #wpbody (which it
* hides), to fully replace the native dashboard, so it gets wrapped in its
* own frame to match the card treatment #wpbody-content already has. The
* block editor bypasses both (see SiteShell.vue isBlockEditor), so it's
* skipped there.
*
* Red closes (navigates back to the Aurora dashboard). Yellow minimizes —
* on any page other than the dashboard, that means "save where I am and
* send me home", with SiteShell.vue's taskbar offering a way back; on the
* dashboard itself (already home) it just collapses in place instead,
* since there's nowhere further back to send it. Green hides Aurora's own
* sidebar/toolbar so the content fills the browser window. All of this is
* just classes on <html> plus one sessionStorage entry (taskbar.js) — no
* server-side state.
*/
import { setMinimizedWindow, currentPageTitle } from './taskbar.js';
export function initWindowChrome(adminUrl) {
if (document.querySelector('.aurora-winbar')) {
return;
}
const bar = document.createElement('div');
bar.className = 'aurora-winbar';
const makeDot = (variant, label, onClick) => {
const dot = document.createElement('button');
dot.type = 'button';
dot.className = 'aurora-winbar__dot aurora-winbar__dot--' + variant;
dot.setAttribute('aria-label', label);
dot.title = label;
dot.addEventListener('click', onClick);
return dot;
};
const root = document.documentElement;
const dashboardRoot = document.getElementById('aurora-admin-dashboard-root');
const isDashboard = !!dashboardRoot;
bar.appendChild(
makeDot('close', 'Close', () => {
window.location.href = adminUrl || '/wp-admin/';
})
);
bar.appendChild(
makeDot('minimize', 'Minimize', () => {
if (isDashboard) {
root.classList.toggle('aurora-minimized');
root.classList.remove('aurora-maximized');
} else {
setMinimizedWindow(currentPageTitle(), window.location.href);
window.location.href = adminUrl || '/wp-admin/';
}
})
);
bar.appendChild(
makeDot('maximize', 'Maximize', () => {
root.classList.toggle('aurora-maximized');
root.classList.remove('aurora-minimized');
})
);
if (dashboardRoot) {
const frame = document.createElement('div');
frame.className = 'aurora-dashboard-frame';
dashboardRoot.parentNode.insertBefore(frame, dashboardRoot);
frame.appendChild(bar);
frame.appendChild(dashboardRoot);
return;
}
const wpbodyContent = document.getElementById('wpbody-content');
if (wpbodyContent) {
wpbodyContent.insertBefore(bar, wpbodyContent.firstChild);
}
}