Skip to content

Commit 60e8210

Browse files
authored
feat(dev-overlay): Hide plugins into a separate menu when there's too many enabled (#9102)
1 parent 6e4dd54 commit 60e8210

7 files changed

Lines changed: 220 additions & 14 deletions

File tree

.changeset/fresh-garlics-film.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+
In the dev overlay, when there's too many plugins enabled at once, some of the plugins will now be hidden in a separate sub menu to avoid the bar becoming too long

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

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

57
let overlay: AstroDevOverlay;
68

@@ -17,6 +19,7 @@ document.addEventListener('DOMContentLoaded', async () => {
1719
{ DevOverlayTooltip },
1820
{ DevOverlayWindow },
1921
{ DevOverlayToggle },
22+
{ getIconElement, isDefinedIcon },
2023
] = await Promise.all([
2124
// @ts-expect-error
2225
import('astro:dev-overlay'),
@@ -30,6 +33,7 @@ document.addEventListener('DOMContentLoaded', async () => {
3033
import('./ui-library/tooltip.js'),
3134
import('./ui-library/window.js'),
3235
import('./ui-library/toggle.js'),
36+
import('./ui-library/icons.js'),
3337
]);
3438

3539
// Register custom elements
@@ -53,6 +57,7 @@ document.addEventListener('DOMContentLoaded', async () => {
5357
builtIn: builtIn,
5458
active: false,
5559
status: 'loading' as const,
60+
notification: { state: false },
5661
eventTarget: eventTarget,
5762
};
5863

@@ -66,7 +71,9 @@ document.addEventListener('DOMContentLoaded', async () => {
6671
newState = evt.detail.state ?? true;
6772
}
6873

69-
if (settings.config.showPluginNotifications === false) {
74+
plugin.notification.state = newState;
75+
76+
if (settings.config.disablePluginNotification === false) {
7077
target.querySelector('.notification')?.toggleAttribute('data-active', newState);
7178
}
7279
});
@@ -83,11 +90,171 @@ document.addEventListener('DOMContentLoaded', async () => {
8390
return plugin;
8491
};
8592

93+
const astromorePlugin = {
94+
id: 'astro:more',
95+
name: 'More',
96+
icon: 'dots-three',
97+
init(canvas, eventTarget) {
98+
const hiddenPlugins = plugins.filter((p) => !p.builtIn).slice(overlay.customPluginsToShow);
99+
100+
createDropdown();
101+
102+
document.addEventListener('astro:after-swap', createDropdown);
103+
104+
function createDropdown() {
105+
const style = document.createElement('style');
106+
style.innerHTML = `
107+
#dropdown {
108+
background: rgba(19, 21, 26, 1);
109+
border: 1px solid rgba(52, 56, 65, 1);
110+
border-radius: 12px;
111+
box-shadow: 0px 0px 0px 0px rgba(19, 21, 26, 0.30), 0px 1px 2px 0px rgba(19, 21, 26, 0.29), 0px 4px 4px 0px rgba(19, 21, 26, 0.26), 0px 10px 6px 0px rgba(19, 21, 26, 0.15), 0px 17px 7px 0px rgba(19, 21, 26, 0.04), 0px 26px 7px 0px rgba(19, 21, 26, 0.01);
112+
width: 180px;
113+
padding: 8px;
114+
z-index: 9999999999;
115+
}
116+
117+
.notification {
118+
display: none;
119+
position: absolute;
120+
top: -4px;
121+
right: -6px;
122+
width: 8px;
123+
height: 8px;
124+
border-radius: 9999px;
125+
border: 1px solid rgba(19, 21, 26, 1);
126+
background: #B33E66;
127+
}
128+
129+
.notification[data-active] {
130+
display: block;
131+
}
132+
133+
#dropdown button {
134+
border: 0;
135+
background: transparent;
136+
color: white;
137+
font-family: system-ui, sans-serif;
138+
font-size: 16px;
139+
line-height: 1.2;
140+
white-space: nowrap;
141+
text-decoration: none;
142+
margin: 0;
143+
display: flex;
144+
align-items: center;
145+
width: 100%;
146+
padding: 8px;
147+
border-radius: 8px;
148+
}
149+
150+
#dropdown button:hover, #dropdown button:focus-visible {
151+
background: rgba(27, 30, 36, 1);
152+
cursor: pointer;
153+
}
154+
155+
#dropdown button.active {
156+
background: rgba(71, 78, 94, 1);
157+
}
158+
159+
#dropdown .icon {
160+
position: relative;
161+
height: 24px;
162+
width: 24px;
163+
margin-right: 0.5em;
164+
}
165+
166+
#dropdown .icon svg {
167+
max-height: 100%;
168+
max-width: 100%;
169+
}
170+
`;
171+
canvas.append(style);
172+
173+
const dropdown = document.createElement('div');
174+
dropdown.id = 'dropdown';
175+
176+
for (const plugin of hiddenPlugins) {
177+
const buttonContainer = document.createElement('div');
178+
buttonContainer.classList.add('item');
179+
const button = document.createElement('button');
180+
button.setAttribute('data-plugin-id', plugin.id);
181+
182+
const iconContainer = document.createElement('div');
183+
const iconElement = getPluginIcon(plugin.icon);
184+
iconContainer.append(iconElement);
185+
186+
const notification = document.createElement('div');
187+
notification.classList.add('notification');
188+
iconContainer.append(notification);
189+
iconContainer.classList.add('icon');
190+
191+
button.append(iconContainer);
192+
button.append(document.createTextNode(plugin.name));
193+
194+
button.addEventListener('click', () => {
195+
overlay.togglePluginStatus(plugin);
196+
});
197+
buttonContainer.append(button);
198+
199+
dropdown.append(buttonContainer);
200+
201+
eventTarget.addEventListener('plugin-toggled', positionDropdown);
202+
window.addEventListener('resize', positionDropdown);
203+
204+
plugin.eventTarget.addEventListener('toggle-notification', (evt) => {
205+
if (!(evt instanceof CustomEvent)) return;
206+
207+
if (settings.config.disablePluginNotification === false) {
208+
notification.toggleAttribute('data-active', evt.detail.state ?? true);
209+
}
210+
211+
eventTarget.dispatchEvent(
212+
new CustomEvent('toggle-notification', {
213+
detail: {
214+
state: hiddenPlugins.some((p) => p.notification.state === true),
215+
},
216+
})
217+
);
218+
});
219+
}
220+
221+
canvas.append(dropdown);
222+
223+
function getPluginIcon(icon: Icon) {
224+
if (isDefinedIcon(icon)) {
225+
return getIconElement(icon)!;
226+
}
227+
228+
return icon;
229+
}
230+
231+
function positionDropdown() {
232+
const moreButtonRect = overlay.shadowRoot
233+
.querySelector('[data-plugin-id="astro:more"]')
234+
?.getBoundingClientRect();
235+
const dropdownRect = dropdown.getBoundingClientRect();
236+
237+
if (moreButtonRect && dropdownRect) {
238+
dropdown.style.position = 'absolute';
239+
dropdown.style.top = `${moreButtonRect.top - dropdownRect.height - 12}px`;
240+
dropdown.style.left = `${
241+
moreButtonRect.left + moreButtonRect.width - dropdownRect.width
242+
}px`;
243+
}
244+
}
245+
}
246+
},
247+
} satisfies DevOverlayPluginDefinition;
248+
86249
const customPluginsDefinitions = (await loadDevOverlayPlugins()) as DevOverlayPluginDefinition[];
87250
const plugins: DevOverlayPlugin[] = [
88-
...[astroDevToolPlugin, astroXrayPlugin, astroAuditPlugin, astroSettingsPlugin].map(
89-
(pluginDef) => preparePlugin(pluginDef, true)
90-
),
251+
...[
252+
astroDevToolPlugin,
253+
astroXrayPlugin,
254+
astroAuditPlugin,
255+
astroSettingsPlugin,
256+
astromorePlugin,
257+
].map((pluginDef) => preparePlugin(pluginDef, true)),
91258
...customPluginsDefinitions.map((pluginDef) => preparePlugin(pluginDef, false)),
92259
];
93260

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

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export type DevOverlayPlugin = DevOverlayPluginDefinition & {
77
builtIn: boolean;
88
active: boolean;
99
status: 'ready' | 'loading' | 'error';
10+
notification: {
11+
state: boolean;
12+
};
1013
eventTarget: EventTarget;
1114
};
1215

@@ -20,6 +23,7 @@ export class AstroDevOverlay extends HTMLElement {
2023
plugins: DevOverlayPlugin[] = [];
2124
HOVER_DELAY = 750;
2225
hasBeenInitialized = false;
26+
customPluginsToShow = 3;
2327

2428
constructor() {
2529
super();
@@ -164,8 +168,8 @@ export class AstroDevOverlay extends HTMLElement {
164168
#dev-bar .item .notification {
165169
display: none;
166170
position: absolute;
167-
top: -2px;
168-
right: 0;
171+
top: -4px;
172+
right: -6px;
169173
width: 8px;
170174
height: 8px;
171175
border-radius: 9999px;
@@ -236,17 +240,27 @@ export class AstroDevOverlay extends HTMLElement {
236240
<div id="dev-bar">
237241
<div id="bar-container">
238242
${this.plugins
239-
.filter((plugin) => plugin.builtIn && plugin.id !== 'astro:settings')
243+
.filter(
244+
(plugin) => plugin.builtIn && !['astro:settings', 'astro:more'].includes(plugin.id)
245+
)
240246
.map((plugin) => this.getPluginTemplate(plugin))
241247
.join('')}
242248
${
243249
this.plugins.filter((plugin) => !plugin.builtIn).length > 0
244250
? `<div class="separator"></div>${this.plugins
245251
.filter((plugin) => !plugin.builtIn)
252+
.slice(0, this.customPluginsToShow)
246253
.map((plugin) => this.getPluginTemplate(plugin))
247254
.join('')}`
248255
: ''
249256
}
257+
${
258+
this.plugins.filter((plugin) => !plugin.builtIn).length > this.customPluginsToShow
259+
? this.getPluginTemplate(
260+
this.plugins.find((plugin) => plugin.builtIn && plugin.id === 'astro:more')!
261+
)
262+
: ''
263+
}
250264
<div class="separator"></div>
251265
${this.getPluginTemplate(
252266
this.plugins.find((plugin) => plugin.builtIn && plugin.id === 'astro:settings')!
@@ -438,9 +452,19 @@ export class AstroDevOverlay extends HTMLElement {
438452
}
439453

440454
plugin.active = newStatus ?? !plugin.active;
441-
const target = this.shadowRoot.querySelector(`[data-plugin-id="${plugin.id}"]`);
442-
if (!target) return;
443-
target.classList.toggle('active', plugin.active);
455+
const mainBarButton = this.shadowRoot.querySelector(`[data-plugin-id="${plugin.id}"]`);
456+
const moreBarButton = this.getPluginCanvasById('astro:more')?.shadowRoot?.querySelector(
457+
`[data-plugin-id="${plugin.id}"]`
458+
);
459+
460+
if (mainBarButton) {
461+
mainBarButton.classList.toggle('active', plugin.active);
462+
}
463+
464+
if (moreBarButton) {
465+
moreBarButton.classList.toggle('active', plugin.active);
466+
}
467+
444468
pluginCanvas.style.display = plugin.active ? 'block' : 'none';
445469

446470
window.requestAnimationFrame(() => {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ const settingsRows = [
1515
name: 'Disable notifications',
1616
description: 'Notification bubbles will not be shown when this is enabled.',
1717
input: 'checkbox',
18-
settingKey: 'showPluginNotifications',
18+
settingKey: 'disablePluginNotification',
1919
changeEvent: (evt: Event) => {
2020
if (evt.currentTarget instanceof HTMLInputElement) {
21-
settings.updateSetting('showPluginNotifications', evt.currentTarget.checked);
21+
settings.updateSetting('disablePluginNotification', evt.currentTarget.checked);
2222
}
2323
},
2424
},

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
export interface Settings {
2-
showPluginNotifications: boolean;
2+
disablePluginNotification: boolean;
33
verbose: boolean;
44
}
55

66
export const defaultSettings = {
7-
showPluginNotifications: true,
7+
disablePluginNotification: false,
88
verbose: false,
99
} satisfies Settings;
1010

packages/astro/src/runtime/client/dev-overlay/ui-library/icons.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@ const icons = {
3131
'check-circle':
3232
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14"><path fill="#fff" d="M10.0306 4.96938c.0699.06967.1254.15247.1633.24363.0378.09116.0573.1889.0573.28762 0 .09871-.0195.19645-.0573.28761-.0379.09116-.0934.17396-.1633.24364L6.53063 9.53187c-.06968.06992-.15247.1254-.24364.16326-.09116.03785-.1889.05734-.28761.05734-.09871 0-.19645-.01949-.28762-.05734-.09116-.03786-.17395-.09334-.24363-.16326l-1.5-1.5c-.06977-.06976-.12511-.15258-.16286-.24373-.03776-.09116-.05719-.18885-.05719-.28752 0-.09866.01943-.19635.05719-.28751.03775-.09115.09309-.17397.16286-.24373.06976-.06977.15259-.12511.24374-.16287.09115-.03775.18885-.05719.28751-.05719s.19636.01944.28751.05719c.09115.03776.17397.0931.24374.16287L6 7.9375l2.96938-2.97c.06978-.06961.15259-.12478.24371-.16237.09111-.03758.18874-.05683.2873-.05666.09856.00018.19612.01978.28711.05768.09098.0379.1736.09337.2431.16323ZM13.75 7c0 1.33502-.3959 2.64007-1.1376 3.7501-.7417 1.11-1.7959 1.9752-3.02928 2.4861-1.23341.5109-2.5906.6446-3.89998.3841-1.30937-.2605-2.5121-.9033-3.45611-1.8473-.944-.944-1.586877-2.14677-1.847328-3.45614-.26045-1.30937-.126777-2.66657.384114-3.89997C1.27471 3.18349 2.13987 2.12928 3.2499 1.38758 4.35994.645881 5.66498.25 7 .25c1.78961.001985 3.5053.713781 4.7708 1.97922C13.0362 3.49466 13.748 5.2104 13.75 7Zm-1.5 0c0-1.03835-.3079-2.05339-.8848-2.91674-.5769-.86336-1.3968-1.53627-2.35611-1.93363-.95931-.39736-2.01491-.50133-3.03331-.29875-1.0184.20257-1.95386.70258-2.68809 1.43681-.73422.73422-1.23424 1.66969-1.43681 2.68809-.20257 1.0184-.0986 2.074.29876 3.03331.39736.95931 1.07026 1.77921 1.93362 2.35611.86336.5769 1.87839.8848 2.91674.8848 1.39193-.0015 2.72643-.5551 3.7107-1.5393C11.6949 9.72642 12.2485 8.39193 12.25 7Z"/></svg>',
3333
gear: '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22"><path fill="#fff" d="M11 6.12507c-.9642 0-1.90671.28592-2.7084.82159-.80169.53567-1.42653 1.29704-1.79551 2.18783-.36898.89081-.46552 1.87101-.27742 2.81661.18811.9457.6524 1.8143 1.33419 2.4961.68178.6818 1.55042 1.1461 2.49604 1.3342.9457.1881 1.9259.0916 2.8167-.2774s1.6521-.9938 2.1878-1.7955c.5357-.8017.8216-1.7442.8216-2.7084-.0015-1.2925-.5156-2.53161-1.4295-3.44553-.9139-.91392-2.153-1.42801-3.4455-1.4295Zm0 7.50003c-.5192 0-1.02669-.154-1.45837-.4424-.43168-.2885-.76813-.6984-.96681-1.1781-.19868-.4796-.25067-1.0074-.14938-1.5166.10129-.50924.35129-.97697.71841-1.34408.36711-.36712.83484-.61712 1.34405-.71841.5092-.10129 1.037-.0493 1.5166.14938.4797.19868.8897.53513 1.1781.96681.2884.43168.4424.9392.4424 1.4584 0 .6962-.2766 1.3638-.7688 1.8561-.4923.4923-1.16.7689-1.8562.7689Zm8.625-2.551v-.1481l1.3125-1.64155c.1102-.13755.1865-.29905.2228-.4715s.0316-.35102-.0137-.52131c-.2369-.89334-.5909-1.75142-1.0528-2.55188-.089-.15264-.2127-.28218-.3611-.37811-.1484-.09594-.3173-.15557-.493-.17408l-2.0888-.23437-.104-.10406-.2344-2.08969c-.0186-.17556-.0783-.34426-.1743-.49247-.0959-.1482-.2254-.27175-.3779-.36066-.8005-.46341-1.6589-.81869-2.5528-1.056559-.1704-.044683-.349-.048704-.5213-.01174-.1723.036965-.3335.113881-.4706.224549l-1.6415 1.3125h-.1482l-1.64152-1.3125C9.14683.9524 8.98532.87608 8.81288.839767c-.17245-.036314-.35102-.031606-.52132.013744-.89357.238319-1.75165.593909-2.55187 1.057499-.15205.08854-.28121.2115-.37712.35901-.0959.14752-.15586.31547-.17507.49037l-.23437 2.08875-.10407.10406-2.08968.23437c-.17556.01865-.34426.07835-.49247.17428-.14821.09593-.27176.22539-.36066.37791-.46211.80072-.81613 1.65912-1.052812 2.55281-.045195.17016-.049823.34855-.013512.52082.03631.17227.112546.33362.222574.47106L2.375 10.926v.1481l-1.3125 1.6416c-.110173.1375-.186492.299-.222806.4715-.036313.1724-.031605.351.013744.5213.238622.8936.594522 1.7517 1.058442 2.5519.08844.1519.21126.281.3586.3769.14734.0959.3151.1559.48983.1753l2.08875.2325.10407.104.23437 2.0916c.01865.1756.07835.3443.17428.4925.09592.1482.22538.2717.37791.3606.80052.4634 1.65893.8187 2.55281 1.0566.17045.0447.349.0487.52129.0117.17228-.0369.33347-.1139.47059-.2245l1.64152-1.3125h.1482l1.6415 1.3125c.1376.1101.2991.1865.4715.2228.1725.0363.351.0316.5213-.0138.8934-.2368 1.7514-.5908 2.5519-1.0528.1524-.0883.2819-.2112.3782-.3587.0962-.1475.1565-.3156.1759-.4907l.2325-2.0887.104-.1041 2.0897-.239c.1751-.0194.3432-.0797.4907-.1759.1475-.0963.2704-.2258.3587-.3782.4634-.8005.8187-1.6589 1.0566-2.5528.0448-.1699.0493-.3479.013-.5198-.0363-.172-.1124-.333-.2221-.4702l-1.3125-1.6416Zm-2.2612-.4584c.015.256.015.5127 0 .7687-.0168.2784.0704.553.2446.7707l1.2038 1.5047c-.1136.3363-.2492.6648-.406.9834l-1.9153.2128c-.2773.0317-.5329.1654-.7171.375-.1704.1919-.3519.3735-.5438.5438-.2096.1842-.3433.4398-.375.7171l-.2119 1.9144c-.3185.1574-.647.2936-.9834.4078l-1.5047-1.2047c-.1997-.1593-.4477-.2459-.7031-.2456h-.0675c-.2561.015-.5127.015-.7688 0-.2781-.0165-.5525.0703-.7706.2438l-1.50469 1.2047c-.33634-.1137-.66486-.2493-.98343-.406l-.21282-1.9153c-.0317-.2773-.16536-.5329-.375-.7172-.19187-.1703-.37344-.3519-.54375-.5437-.18426-.2097-.43988-.3433-.71718-.375l-1.91438-.2119c-.15734-.3185-.29357-.647-.40781-.9834l1.20375-1.5047c.17424-.2177.26144-.4923.24469-.7707-.01501-.256-.01501-.5127 0-.7687.01675-.2783-.07045-.553-.24469-.77063L3.18781 8.34038c.11364-.33634.24924-.66486.40594-.98343l1.91531-.21281c.27731-.03171.53292-.16537.71719-.375.17031-.19188.35188-.37345.54375-.54375.20964-.18427.3433-.43989.375-.71719l.21188-1.91438c.31852-.15734.64704-.29357.98343-.40781L9.845 4.3907c.2181.17343.4925.26023.7706.24375.2561-.015.5127-.015.7688 0 .2782.01701.5528-.06985.7706-.24375l1.5047-1.20469c.3364.11424.6649.25047.9834.40781l.2128 1.91532c.0317.2773.1654.53292.375.71718.1919.17031.3735.35188.5438.54375.1843.20964.4399.3433.7172.375l1.9143.21188c.1574.31852.2936.64704.4079.98343l-1.2038 1.50469c-.1749.21743-.2628.49203-.2465.77063Z"/></svg>',
34+
'dots-three':
35+
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 4"><path fill="#fff" d="M9.5 2c0 .29667-.08797.58668-.2528.83336-.16482.24667-.39908.43893-.67317.55246-.27409.11353-.57569.14324-.86666.08536-.29098-.05788-.55825-.20074-.76803-.41052-.20978-.20978-.35264-.47705-.41052-.76802-.05788-.29098-.02817-.59258.08536-.86666.11353-.27409.30579-.508362.55247-.673184C7.41332.587974 7.70333.5 8 .5c.39783 0 .77936.158036 1.06066.43934C9.34196 1.22064 9.5 1.60218 9.5 2ZM1.625.5c-.29667 0-.58668.087974-.833354.252796-.246674.164822-.438933.399094-.552465.673184-.113531.27408-.1432361.57568-.085358.86666.057878.29097.200739.55824.410518.76802.209778.20978.477049.35264.768029.41052.29097.05788.59257.02817.86666-.08536.27408-.11353.50835-.30579.67318-.55246C3.03703 2.58668 3.125 2.29667 3.125 2c0-.39782-.15803-.77936-.43934-1.06066C2.40436.658036 2.02283.5 1.625.5Zm12.75 0c-.2967 0-.5867.087974-.8334.252796-.2466.164822-.4389.399094-.5524.673184-.1135.27408-.1433.57568-.0854.86666.0579.29097.2008.55824.4105.76802.2098.20978.4771.35264.7681.41052.2909.05788.5925.02817.8666-.08536s.5084-.30579.6732-.55246c.1648-.24668.2528-.53669.2528-.83336 0-.39782-.158-.77936-.4393-1.06066C15.1544.658036 14.7728.5 14.375.5Z"/></svg>',
3436
} as const;

packages/astro/src/runtime/client/dev-overlay/ui-library/toggle.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,12 @@ export class DevOverlayToggle extends HTMLElement {
4949
this.input.type = 'checkbox';
5050
this.shadowRoot.append(this.input);
5151
}
52+
53+
get value() {
54+
return this.input.value;
55+
}
56+
57+
set value(val) {
58+
this.input.value = val;
59+
}
5260
}

0 commit comments

Comments
 (0)