11import type { DevOverlayPlugin as DevOverlayPluginDefinition } from '../../../@types/astro.js' ;
22import { type AstroDevOverlay , type DevOverlayPlugin } from './overlay.js' ;
3+
34import { settings } from './settings.js' ;
5+ import type { Icon } from './ui-library/icons.js' ;
46
57let 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
0 commit comments