Skip to content

Commit 5ef89ef

Browse files
authored
feat(overlay): Add a settings panel (#9058)
1 parent a7f8123 commit 5ef89ef

12 files changed

Lines changed: 349 additions & 59 deletions

File tree

.changeset/six-chefs-flash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'astro': patch
3+
---
4+
5+
Add a new settings panel to the dev overlay

packages/astro/e2e/dev-overlay.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,23 @@ test.describe('Dev Overlay', () => {
9898
await expect(auditHighlight).not.toBeVisible();
9999
await expect(auditHighlightTooltip).not.toBeVisible();
100100
});
101+
102+
test('can open Settings plugin', async ({ page, astro }) => {
103+
await page.goto(astro.resolveUrl('/'));
104+
105+
const overlay = page.locator('astro-dev-overlay');
106+
const pluginButton = overlay.locator('button[data-plugin-id="astro:settings"]');
107+
await pluginButton.click();
108+
109+
const settingsPluginCanvas = overlay.locator(
110+
'astro-dev-overlay-plugin-canvas[data-plugin-id="astro:settings"]'
111+
);
112+
const settingsWindow = settingsPluginCanvas.locator('astro-dev-overlay-window');
113+
await expect(settingsWindow).toHaveCount(1);
114+
await expect(settingsWindow).toBeVisible();
115+
116+
// Toggle plugin off
117+
await pluginButton.click();
118+
await expect(settingsWindow).not.toBeVisible();
119+
});
101120
});

packages/astro/src/@types/astro.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import type { AstroIntegrationLogger, Logger, LoggerLevel } from '../core/logger
2424
import type { AstroDevOverlay, DevOverlayCanvas } from '../runtime/client/dev-overlay/overlay.js';
2525
import type { DevOverlayHighlight } from '../runtime/client/dev-overlay/ui-library/highlight.js';
2626
import type { Icon } from '../runtime/client/dev-overlay/ui-library/icons.js';
27+
import type { DevOverlayToggle } from '../runtime/client/dev-overlay/ui-library/toggle.js';
2728
import type { DevOverlayTooltip } from '../runtime/client/dev-overlay/ui-library/tooltip.js';
2829
import type { DevOverlayWindow } from '../runtime/client/dev-overlay/ui-library/window.js';
2930
import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server/index.js';
@@ -2578,5 +2579,6 @@ declare global {
25782579
'astro-dev-overlay-plugin-canvas': DevOverlayCanvas;
25792580
'astro-dev-overlay-tooltip': DevOverlayTooltip;
25802581
'astro-dev-overlay-highlight': DevOverlayHighlight;
2582+
'astro-dev-overlay-toggle': DevOverlayToggle;
25812583
}
25822584
}

packages/astro/src/runtime/client/dev-overlay/entrypoint.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@types/astro.js';
22
import { type AstroDevOverlay, type DevOverlayPlugin } from './overlay.js';
3+
import { settings } from './settings.js';
34

45
let overlay: AstroDevOverlay;
56

@@ -9,22 +10,26 @@ document.addEventListener('DOMContentLoaded', async () => {
910
{ default: astroDevToolPlugin },
1011
{ default: astroAuditPlugin },
1112
{ default: astroXrayPlugin },
13+
{ default: astroSettingsPlugin },
1214
{ AstroDevOverlay, DevOverlayCanvas },
1315
{ DevOverlayCard },
1416
{ DevOverlayHighlight },
1517
{ DevOverlayTooltip },
1618
{ DevOverlayWindow },
19+
{ DevOverlayToggle },
1720
] = await Promise.all([
1821
// @ts-expect-error
1922
import('astro:dev-overlay'),
2023
import('./plugins/astro.js'),
2124
import('./plugins/audit.js'),
2225
import('./plugins/xray.js'),
26+
import('./plugins/settings.js'),
2327
import('./overlay.js'),
2428
import('./ui-library/card.js'),
2529
import('./ui-library/highlight.js'),
2630
import('./ui-library/tooltip.js'),
2731
import('./ui-library/window.js'),
32+
import('./ui-library/toggle.js'),
2833
]);
2934

3035
// Register custom elements
@@ -34,6 +39,7 @@ document.addEventListener('DOMContentLoaded', async () => {
3439
customElements.define('astro-dev-overlay-tooltip', DevOverlayTooltip);
3540
customElements.define('astro-dev-overlay-highlight', DevOverlayHighlight);
3641
customElements.define('astro-dev-overlay-card', DevOverlayCard);
42+
customElements.define('astro-dev-overlay-toggle', DevOverlayToggle);
3743

3844
overlay = document.createElement('astro-dev-overlay');
3945

@@ -60,7 +66,9 @@ document.addEventListener('DOMContentLoaded', async () => {
6066
newState = evt.detail.state ?? true;
6167
}
6268

63-
target.querySelector('.notification')?.toggleAttribute('data-active', newState);
69+
if (settings.config.showPluginNotifications === false) {
70+
target.querySelector('.notification')?.toggleAttribute('data-active', newState);
71+
}
6472
});
6573

6674
eventTarget.addEventListener('toggle-plugin', async (evt) => {
@@ -77,8 +85,8 @@ document.addEventListener('DOMContentLoaded', async () => {
7785

7886
const customPluginsDefinitions = (await loadDevOverlayPlugins()) as DevOverlayPluginDefinition[];
7987
const plugins: DevOverlayPlugin[] = [
80-
...[astroDevToolPlugin, astroXrayPlugin, astroAuditPlugin].map((pluginDef) =>
81-
preparePlugin(pluginDef, true)
88+
...[astroDevToolPlugin, astroXrayPlugin, astroAuditPlugin, astroSettingsPlugin].map(
89+
(pluginDef) => preparePlugin(pluginDef, true)
8290
),
8391
...customPluginsDefinitions.map((pluginDef) => preparePlugin(pluginDef, false)),
8492
];

packages/astro/src/runtime/client/dev-overlay/overlay.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable no-console */
22
import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@types/astro.js';
3+
import { settings } from './settings.js';
34
import { getIconElement, isDefinedIcon, type Icon } from './ui-library/icons.js';
45

56
export type DevOverlayPlugin = DevOverlayPluginDefinition & {
@@ -235,14 +236,21 @@ export class AstroDevOverlay extends HTMLElement {
235236
<div id="dev-bar">
236237
<div id="bar-container">
237238
${this.plugins
238-
.filter((plugin) => plugin.builtIn)
239+
.filter((plugin) => plugin.builtIn && plugin.id !== 'astro:settings')
239240
.map((plugin) => this.getPluginTemplate(plugin))
240241
.join('')}
242+
${
243+
this.plugins.filter((plugin) => !plugin.builtIn).length > 0
244+
? `<div class="separator"></div>${this.plugins
245+
.filter((plugin) => !plugin.builtIn)
246+
.map((plugin) => this.getPluginTemplate(plugin))
247+
.join('')}`
248+
: ''
249+
}
241250
<div class="separator"></div>
242-
${this.plugins
243-
.filter((plugin) => !plugin.builtIn)
244-
.map((plugin) => this.getPluginTemplate(plugin))
245-
.join('')}
251+
${this.getPluginTemplate(
252+
this.plugins.find((plugin) => plugin.builtIn && plugin.id === 'astro:settings')!
253+
)}
246254
</div>
247255
</div>
248256
<button id="minimize-button">${getIconElement('arrow-down')?.outerHTML}</button>
@@ -254,7 +262,8 @@ export class AstroDevOverlay extends HTMLElement {
254262
// Create plugin canvases
255263
this.plugins.forEach(async (plugin) => {
256264
if (!this.hasBeenInitialized) {
257-
console.log(`Creating plugin canvas for ${plugin.id}`);
265+
if (settings.config.verbose) console.log(`Creating plugin canvas for ${plugin.id}`);
266+
258267
const pluginCanvas = document.createElement('astro-dev-overlay-plugin-canvas');
259268
pluginCanvas.dataset.pluginId = plugin.id;
260269
this.shadowRoot?.append(pluginCanvas);
@@ -321,7 +330,7 @@ export class AstroDevOverlay extends HTMLElement {
321330
if (this.isHidden()) {
322331
this.hoverTimeout = window.setTimeout(() => {
323332
this.toggleOverlay(true);
324-
}, this.HOVER_DELAY);
333+
}, this.HOVER_DELAY + 200); // Slightly higher delay here to prevent users opening the overlay by accident
325334
} else {
326335
this.hoverTimeout = window.setTimeout(() => {
327336
this.toggleMinimizeButton(true);
@@ -374,7 +383,8 @@ export class AstroDevOverlay extends HTMLElement {
374383
const shadowRoot = this.getPluginCanvasById(plugin.id)!.shadowRoot!;
375384

376385
try {
377-
console.info(`Initializing plugin ${plugin.id}`);
386+
if (settings.config.verbose) console.info(`Initializing plugin ${plugin.id}`);
387+
378388
await plugin.init?.(shadowRoot, plugin.eventTarget);
379389
plugin.status = 'ready';
380390

packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { DevOverlayPlugin } from '../../../../@types/astro.js';
2+
import { createWindowWithTransition, waitForTransition } from './utils/window.js';
23

34
export default {
45
id: 'astro',
@@ -10,38 +11,10 @@ export default {
1011
document.addEventListener('astro:after-swap', createWindow);
1112

1213
function createWindow() {
13-
const style = document.createElement('style');
14-
style.textContent = `
15-
:host {
16-
opacity: 0;
17-
transition: opacity 0.15s ease-in-out;
18-
}
19-
20-
:host([data-active]) {
21-
opacity: 1;
22-
}
23-
24-
@media screen and (prefers-reduced-motion: no-preference) {
25-
:host astro-dev-overlay-window {
26-
transform: translateY(55px) translate(-50%, -50%);
27-
transition: transform 0.15s ease-in-out;
28-
transform-origin: center bottom;
29-
}
30-
31-
:host([data-active]) astro-dev-overlay-window {
32-
transform: translateY(0) translate(-50%, -50%);
33-
}
34-
}
35-
`;
36-
canvas.append(style);
37-
38-
const astroWindow = document.createElement('astro-dev-overlay-window');
39-
40-
astroWindow.windowTitle = 'Astro';
41-
astroWindow.windowIcon = 'astro:logo';
42-
43-
astroWindow.innerHTML = `
44-
<style>
14+
const window = createWindowWithTransition(
15+
'Astro',
16+
'astro:logo',
17+
`<style>
4518
#buttons-container {
4619
display: flex;
4720
gap: 16px;
@@ -91,18 +64,13 @@ export default {
9164
<a href="https://astro.build" target="_blank">Visit the Astro website</a>
9265
</footer>
9366
</div>
94-
`;
67+
`
68+
);
9569

96-
canvas.append(astroWindow);
70+
canvas.append(window);
9771
}
9872
},
9973
async beforeTogglingOff(canvas) {
100-
canvas.host?.removeAttribute('data-active');
101-
102-
await new Promise((resolve) => {
103-
canvas.host.addEventListener('transitionend', resolve);
104-
});
105-
106-
return true;
74+
return await waitForTransition(canvas);
10775
},
10876
} satisfies DevOverlayPlugin;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import type { DevOverlayPlugin } from '../../../../@types/astro.js';
2+
import { settings, type Settings } from '../settings.js';
3+
import { createWindowWithTransition, waitForTransition } from './utils/window.js';
4+
5+
interface SettingRow {
6+
name: string;
7+
description: string;
8+
input: 'checkbox' | 'text' | 'number' | 'select';
9+
settingKey: keyof Settings;
10+
changeEvent: (evt: Event) => void;
11+
}
12+
13+
const settingsRows = [
14+
{
15+
name: 'Disable notifications',
16+
description: 'Notification bubbles will not be shown when this is enabled.',
17+
input: 'checkbox',
18+
settingKey: 'showPluginNotifications',
19+
changeEvent: (evt: Event) => {
20+
if (evt.currentTarget instanceof HTMLInputElement) {
21+
settings.updateSetting('showPluginNotifications', evt.currentTarget.checked);
22+
}
23+
},
24+
},
25+
{
26+
name: 'Verbose logging',
27+
description: 'Log additional information to the console.',
28+
input: 'checkbox',
29+
settingKey: 'verbose',
30+
changeEvent: (evt: Event) => {
31+
if (evt.currentTarget instanceof HTMLInputElement) {
32+
settings.updateSetting('verbose', evt.currentTarget.checked);
33+
}
34+
},
35+
},
36+
] satisfies SettingRow[];
37+
38+
export default {
39+
id: 'astro:settings',
40+
name: 'Overlay settings',
41+
icon: 'gear',
42+
init(canvas) {
43+
createSettingsWindow();
44+
45+
document.addEventListener('astro:after-swap', createSettingsWindow);
46+
47+
function createSettingsWindow() {
48+
const window = createWindowWithTransition(
49+
'Settings',
50+
'gear',
51+
`<style>
52+
h2, h3 {
53+
margin-top: 0;
54+
}
55+
56+
.setting-row {
57+
display: flex;
58+
justify-content: space-between;
59+
align-items: center;
60+
}
61+
62+
h3 {
63+
font-size: 16px;
64+
font-weight: 400;
65+
color: white;
66+
margin-bottom: 0;
67+
}
68+
69+
label {
70+
font-size: 15px;
71+
line-height: 1.5rem;
72+
}
73+
</style>
74+
<h2>General</h2>
75+
`,
76+
settingsRows.flatMap((setting) => [
77+
getElementForSettingAsString(setting),
78+
document.createElement('hr'),
79+
])
80+
);
81+
canvas.append(window);
82+
83+
function getElementForSettingAsString(setting: SettingRow) {
84+
const label = document.createElement('label');
85+
label.classList.add('setting-row');
86+
const section = document.createElement('section');
87+
section.innerHTML = `<h3>${setting.name}</h3>${setting.description}`;
88+
label.append(section);
89+
90+
switch (setting.input) {
91+
case 'checkbox': {
92+
const astroToggle = document.createElement('astro-dev-overlay-toggle');
93+
astroToggle.input.addEventListener('change', setting.changeEvent);
94+
astroToggle.input.checked = settings.config[setting.settingKey];
95+
label.append(astroToggle);
96+
}
97+
}
98+
99+
return label;
100+
}
101+
}
102+
},
103+
async beforeTogglingOff(canvas) {
104+
return await waitForTransition(canvas);
105+
},
106+
} satisfies DevOverlayPlugin;

0 commit comments

Comments
 (0)