Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(overlay): Add a settings panel #9058

Merged
merged 8 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/astro/e2e/dev-overlay.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,23 @@ test.describe('Dev Overlay', () => {
await expect(auditHighlight).not.toBeVisible();
await expect(auditHighlightTooltip).not.toBeVisible();
});

test('can open Settings plugin', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));

const overlay = page.locator('astro-dev-overlay');
const pluginButton = overlay.locator('button[data-plugin-id="astro:settings"]');
await pluginButton.click();

const settingsPluginCanvas = overlay.locator(
'astro-dev-overlay-plugin-canvas[data-plugin-id="astro:settings"]'
);
const settingsWindow = settingsPluginCanvas.locator('astro-dev-overlay-window');
await expect(settingsWindow).toHaveCount(1);
await expect(settingsWindow).toBeVisible();

// Toggle plugin off
await pluginButton.click();
await expect(settingsWindow).not.toBeVisible();
});
});
2 changes: 2 additions & 0 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type { AstroIntegrationLogger, Logger, LoggerLevel } from '../core/logger
import type { AstroDevOverlay, DevOverlayCanvas } from '../runtime/client/dev-overlay/overlay.js';
import type { DevOverlayHighlight } from '../runtime/client/dev-overlay/ui-library/highlight.js';
import type { Icon } from '../runtime/client/dev-overlay/ui-library/icons.js';
import type { DevOverlayToggle } from '../runtime/client/dev-overlay/ui-library/toggle.js';
import type { DevOverlayTooltip } from '../runtime/client/dev-overlay/ui-library/tooltip.js';
import type { DevOverlayWindow } from '../runtime/client/dev-overlay/ui-library/window.js';
import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server/index.js';
Expand Down Expand Up @@ -2578,5 +2579,6 @@ declare global {
'astro-dev-overlay-plugin-canvas': DevOverlayCanvas;
'astro-dev-overlay-tooltip': DevOverlayTooltip;
'astro-dev-overlay-highlight': DevOverlayHighlight;
'astro-dev-overlay-toggle': DevOverlayToggle;
}
}
14 changes: 11 additions & 3 deletions packages/astro/src/runtime/client/dev-overlay/entrypoint.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@types/astro.js';
import { type AstroDevOverlay, type DevOverlayPlugin } from './overlay.js';
import { settings } from './settings.js';

let overlay: AstroDevOverlay;

Expand All @@ -9,22 +10,26 @@ document.addEventListener('DOMContentLoaded', async () => {
{ default: astroDevToolPlugin },
{ default: astroAuditPlugin },
{ default: astroXrayPlugin },
{ default: astroSettingsPlugin },
{ AstroDevOverlay, DevOverlayCanvas },
{ DevOverlayCard },
{ DevOverlayHighlight },
{ DevOverlayTooltip },
{ DevOverlayWindow },
{ DevOverlayToggle },
] = await Promise.all([
// @ts-expect-error
import('astro:dev-overlay'),
import('./plugins/astro.js'),
import('./plugins/audit.js'),
import('./plugins/xray.js'),
import('./plugins/settings.js'),
import('./overlay.js'),
import('./ui-library/card.js'),
import('./ui-library/highlight.js'),
import('./ui-library/tooltip.js'),
import('./ui-library/window.js'),
import('./ui-library/toggle.js'),
]);

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

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

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

target.querySelector('.notification')?.toggleAttribute('data-active', newState);
if (settings.config.showPluginNotifications === false) {
target.querySelector('.notification')?.toggleAttribute('data-active', newState);
}
});

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

const customPluginsDefinitions = (await loadDevOverlayPlugins()) as DevOverlayPluginDefinition[];
const plugins: DevOverlayPlugin[] = [
...[astroDevToolPlugin, astroXrayPlugin, astroAuditPlugin].map((pluginDef) =>
preparePlugin(pluginDef, true)
...[astroDevToolPlugin, astroXrayPlugin, astroAuditPlugin, astroSettingsPlugin].map(
(pluginDef) => preparePlugin(pluginDef, true)
),
...customPluginsDefinitions.map((pluginDef) => preparePlugin(pluginDef, false)),
];
Expand Down
26 changes: 18 additions & 8 deletions packages/astro/src/runtime/client/dev-overlay/overlay.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable no-console */
import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@types/astro.js';
import { settings } from './settings.js';
import { getIconElement, isDefinedIcon, type Icon } from './ui-library/icons.js';

export type DevOverlayPlugin = DevOverlayPluginDefinition & {
Expand Down Expand Up @@ -235,14 +236,21 @@ export class AstroDevOverlay extends HTMLElement {
<div id="dev-bar">
<div id="bar-container">
${this.plugins
.filter((plugin) => plugin.builtIn)
.filter((plugin) => plugin.builtIn && plugin.id !== 'astro:settings')
.map((plugin) => this.getPluginTemplate(plugin))
.join('')}
${
this.plugins.filter((plugin) => !plugin.builtIn).length > 0
? `<div class="separator"></div>${this.plugins
.filter((plugin) => !plugin.builtIn)
.map((plugin) => this.getPluginTemplate(plugin))
.join('')}`
: ''
}
<div class="separator"></div>
${this.plugins
.filter((plugin) => !plugin.builtIn)
.map((plugin) => this.getPluginTemplate(plugin))
.join('')}
${this.getPluginTemplate(
this.plugins.find((plugin) => plugin.builtIn && plugin.id === 'astro:settings')!
)}
</div>
</div>
<button id="minimize-button">${getIconElement('arrow-down')?.outerHTML}</button>
Expand All @@ -254,7 +262,8 @@ export class AstroDevOverlay extends HTMLElement {
// Create plugin canvases
this.plugins.forEach(async (plugin) => {
if (!this.hasBeenInitialized) {
console.log(`Creating plugin canvas for ${plugin.id}`);
if (settings.config.verbose) console.log(`Creating plugin canvas for ${plugin.id}`);

const pluginCanvas = document.createElement('astro-dev-overlay-plugin-canvas');
pluginCanvas.dataset.pluginId = plugin.id;
this.shadowRoot?.append(pluginCanvas);
Expand Down Expand Up @@ -321,7 +330,7 @@ export class AstroDevOverlay extends HTMLElement {
if (this.isHidden()) {
this.hoverTimeout = window.setTimeout(() => {
this.toggleOverlay(true);
}, this.HOVER_DELAY);
}, this.HOVER_DELAY + 200); // Slightly higher delay here to prevent users opening the overlay by accident
} else {
this.hoverTimeout = window.setTimeout(() => {
this.toggleMinimizeButton(true);
Expand Down Expand Up @@ -374,7 +383,8 @@ export class AstroDevOverlay extends HTMLElement {
const shadowRoot = this.getPluginCanvasById(plugin.id)!.shadowRoot!;

try {
console.info(`Initializing plugin ${plugin.id}`);
if (settings.config.verbose) console.info(`Initializing plugin ${plugin.id}`);

await plugin.init?.(shadowRoot, plugin.eventTarget);
plugin.status = 'ready';

Expand Down
50 changes: 9 additions & 41 deletions packages/astro/src/runtime/client/dev-overlay/plugins/astro.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { DevOverlayPlugin } from '../../../../@types/astro.js';
import { createWindowWithTransition, waitForTransition } from './utils/window.js';

export default {
id: 'astro',
Expand All @@ -10,38 +11,10 @@ export default {
document.addEventListener('astro:after-swap', createWindow);

function createWindow() {
const style = document.createElement('style');
style.textContent = `
:host {
opacity: 0;
transition: opacity 0.15s ease-in-out;
}

:host([data-active]) {
opacity: 1;
}

@media screen and (prefers-reduced-motion: no-preference) {
:host astro-dev-overlay-window {
transform: translateY(55px) translate(-50%, -50%);
transition: transform 0.15s ease-in-out;
transform-origin: center bottom;
}

:host([data-active]) astro-dev-overlay-window {
transform: translateY(0) translate(-50%, -50%);
}
}
`;
canvas.append(style);

const astroWindow = document.createElement('astro-dev-overlay-window');

astroWindow.windowTitle = 'Astro';
astroWindow.windowIcon = 'astro:logo';

astroWindow.innerHTML = `
<style>
const window = createWindowWithTransition(
'Astro',
'astro:logo',
`<style>
#buttons-container {
display: flex;
gap: 16px;
Expand Down Expand Up @@ -91,18 +64,13 @@ export default {
<a href="https://astro.build" target="_blank">Visit the Astro website</a>
</footer>
</div>
`;
`
);

canvas.append(astroWindow);
canvas.append(window);
}
},
async beforeTogglingOff(canvas) {
canvas.host?.removeAttribute('data-active');

await new Promise((resolve) => {
canvas.host.addEventListener('transitionend', resolve);
});

return true;
return await waitForTransition(canvas);
},
} satisfies DevOverlayPlugin;
101 changes: 101 additions & 0 deletions packages/astro/src/runtime/client/dev-overlay/plugins/settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import type { DevOverlayPlugin } from '../../../../@types/astro.js';
import { settings, type Settings } from '../settings.js';
import { createWindowWithTransition, waitForTransition } from './utils/window.js';

interface SettingRow {
name: string;
description: string;
input: 'checkbox' | 'text' | 'number' | 'select';
settingKey: keyof Settings;
changeEvent: (evt: Event) => void;
}

const settingsRows = [
{
name: 'Disable notifications?',
description: 'Notification bubbles will not be shown when this is enabled.',
input: 'checkbox',
settingKey: 'showPluginNotifications',
changeEvent: (evt: Event) => {
if (evt.currentTarget instanceof HTMLInputElement) {
settings.updateSetting('showPluginNotifications', evt.currentTarget.checked);
}
},
},
{
name: 'Verbose logging?',
description: 'Log additional information to the console.',
input: 'checkbox',
settingKey: 'verbose',
changeEvent: (evt: Event) => {
if (evt.currentTarget instanceof HTMLInputElement) {
settings.updateSetting('verbose', evt.currentTarget.checked);
}
},
},
] satisfies SettingRow[];

export default {
id: 'astro:settings',
name: 'Overlay settings',
icon: 'gear',
init(canvas) {
createSettingsWindow();

document.addEventListener('astro:after-swap', createSettingsWindow);

function createSettingsWindow() {
const window = createWindowWithTransition(
'Settings',
'gear',
`<style>
h2, h3 {
margin-top: 0;
}

.setting-row {
display: flex;
justify-content: space-between;
align-items: center;
}

h3 {
font-size: 16px;
font-weight: 400;
color: white;
margin-bottom: 0.25rem;
}
</style>
<h2>General</h2>
`,
settingsRows.flatMap((setting) => [
getElementForSettingAsString(setting),
document.createElement('hr'),
])
);
canvas.append(window);

function getElementForSettingAsString(setting: SettingRow) {
const label = document.createElement('label');
label.classList.add('setting-row');
const section = document.createElement('section');
section.innerHTML = `<h3>${setting.name}</h3>${setting.description}`;
label.append(section);

switch (setting.input) {
case 'checkbox': {
const astroToggle = document.createElement('astro-dev-overlay-toggle');
astroToggle.input.addEventListener('change', setting.changeEvent);
astroToggle.input.checked = settings.config[setting.settingKey];
label.append(astroToggle);
}
}

return label;
}
}
},
async beforeTogglingOff(canvas) {
return await waitForTransition(canvas);
},
} satisfies DevOverlayPlugin;
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Icon } from '../../ui-library/icons.js';

export function createWindowWithTransition(
title: string,
icon: Icon,
windowContent: string,
addedNodes: Node[] = []
): DocumentFragment {
const fragment = document.createDocumentFragment();

const style = document.createElement('style');
style.textContent = `
:host {
opacity: 0;
transition: opacity 0.15s ease-in-out;
}

:host([data-active]) {
opacity: 1;
}

@media screen and (prefers-reduced-motion: no-preference) {
:host astro-dev-overlay-window {
transform: translateY(55px) translate(-50%, -50%);
transition: transform 0.15s ease-in-out;
transform-origin: center bottom;
}

:host([data-active]) astro-dev-overlay-window {
transform: translateY(0) translate(-50%, -50%);
}
}
`;
fragment.append(style);

const window = document.createElement('astro-dev-overlay-window');
window.windowTitle = title;
window.windowIcon = icon;
window.innerHTML = windowContent;

window.append(...addedNodes);

fragment.append(window);

return fragment;
}

export async function waitForTransition(canvas: ShadowRoot): Promise<boolean> {
canvas.host?.removeAttribute('data-active');

await new Promise((resolve) => {
canvas.host.addEventListener('transitionend', resolve);
});

return true;
}
Loading
Loading