PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.8.9
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.8.9
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / includes / welcome-dialog.php

welcome-dialog.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.8.9, at includes/welcome-dialog.php

734 lines 21.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Desktop Mode — First-run welcome dialog.
4 *
5 * Renders a one-time, self-contained modal inside the *classic* WordPress
6 * admin (never inside the desktop shell or a chromeless iframe) telling
7 * the user where the "Switch to Desktop Mode" button lives in the admin
8 * bar. Dismissal is persisted via the existing seen-intros registry
9 * (`desktop_mode_seen_intros` user meta, slug `activation-welcome`),
10 * which means the "Reset what's-new dialogs" button in OS Settings →
11 * Features brings it back exactly like every other intro dialog.
12 *
13 * The dialog is intentionally self-contained — all HTML, CSS and JS are
14 * inlined into `admin_footer`. We deliberately do NOT use any of the
15 * `<wpd-*>` shell components here because they only ship inside the
16 * desktop bundle, which is precisely *not* loaded on the classic admin
17 * screens where this dialog is allowed to appear.
18 *
19 * @package Desktop_Mode
20 * @since 0.18.5
21 */
22
23 defined( 'ABSPATH' ) || exit;
24
25 /** Slug stored in `desktop_mode_seen_intros` for this dialog. */
26 const DESKTOP_MODE_WELCOME_INTRO_SLUG = 'activation-welcome';
27
28 /**
29 * Decides whether the welcome dialog should render on the current request.
30 *
31 * Five gates:
32 *
33 * 1. We're inside `/wp-admin` (`is_admin()`).
34 * 2. The user is logged in and can `read` (sanity gate — the dialog has
35 * no destructive surface, but anonymous output makes no sense).
36 * 3. The request is NOT chromeless — chromeless pages are iframes
37 * rendering inside the desktop shell; the parent shell already shows
38 * its own UX.
39 * 4. The user has not already dismissed this intro.
40 * 5. The `desktop_mode_show_welcome_dialog` filter returns truthy, so
41 * sites can suppress the dialog entirely (e.g. managed-host onboarding
42 * flows that ship their own).
43 *
44 * @since 0.18.5
45 *
46 * @return bool
47 */
48 function desktop_mode_should_show_welcome_dialog() {
49 if ( ! is_admin() || ! is_user_logged_in() ) {
50 return false;
51 }
52 if ( ! current_user_can( 'read' ) ) {
53 return false;
54 }
55 if ( function_exists( 'desktop_mode_is_chromeless_request' ) && desktop_mode_is_chromeless_request() ) {
56 return false;
57 }
58 $user_id = get_current_user_id();
59 if ( desktop_mode_has_seen_intro( $user_id, DESKTOP_MODE_WELCOME_INTRO_SLUG ) ) {
60 return false;
61 }
62
63 /**
64 * Filters whether the first-run welcome dialog should render for
65 * the current user on the current request. All earlier gates
66 * (admin context, capability, chromeless, seen-state) have
67 * already passed by the time this filter fires.
68 *
69 * @since 0.18.5
70 *
71 * @param bool $show Whether to render the dialog. Default true.
72 * @param int $user_id Current user ID.
73 */
74 return (bool) apply_filters( 'desktop_mode_show_welcome_dialog', true, $user_id );
75 }
76
77 /**
78 * Prints the welcome dialog markup, styles and dismiss script into
79 * `admin_footer`.
80 *
81 * Self-contained on purpose: everything is scoped under the
82 * `.desktop-mode-welcome` namespace so it cannot collide with the host
83 * admin theme. The dismiss button POSTs to the seen-intros REST route,
84 * which is exactly the same endpoint the in-shell intros use.
85 *
86 * @since 0.18.5
87 */
88 function desktop_mode_render_welcome_dialog() {
89 if ( ! desktop_mode_should_show_welcome_dialog() ) {
90 return;
91 }
92
93 $rest_url = esc_url_raw( rest_url( 'desktop-mode/v1/intros/seen' ) );
94 $rest_nonce = wp_create_nonce( 'wp_rest' );
95 $ajax_url = esc_url_raw( admin_url( 'admin-ajax.php' ) );
96 $ajax_nonce = wp_create_nonce( 'save-desktop-mode' );
97 $slug = DESKTOP_MODE_WELCOME_INTRO_SLUG;
98
99 // All user-facing strings are passed through translation; the dialog
100 // is keyboard-dismissible (Escape) and focus-trapped between the
101 // close button and the "Got it" CTA.
102 $title = __( 'Welcome to Desktop Mode', 'desktop-mode' );
103 $subtitle = __( 'A floating, window-based workspace for the WordPress admin — more like a real OS, less like a webpage.', 'desktop-mode' );
104 $body = __( 'Desktop Mode reimagines the WordPress admin as a true desktop environment. Open multiple admin screens side-by-side, drag and drop content between windows, and keep your work in view as you move between tasks.', 'desktop-mode' );
105 $cta = __( 'Got it', 'desktop-mode' );
106 $enable = __( 'Enable it now', 'desktop-mode' );
107 $enabling = __( 'Enabling…', 'desktop-mode' );
108 $close_a = __( 'Close', 'desktop-mode' );
109
110 $features = array(
111 array(
112 'icon' => '🪟',
113 'title' => __( 'Multi-window workspace', 'desktop-mode' ),
114 'desc' => __( 'Open Posts, Media and Plugins in their own draggable, resizable windows. Tile, cascade or stack them however you work best.', 'desktop-mode' ),
115 ),
116 array(
117 'icon' => '',
118 'title' => __( 'Native, table-driven apps', 'desktop-mode' ),
119 'desc' => __( 'Posts, Pages, Users and Plugins are reimplemented as fast native windows with sticky headers, bulk actions, multi-select and inline previews.', 'desktop-mode' ),
120 ),
121 array(
122 'icon' => '🎨',
123 'title' => __( 'Wallpapers, dock & themes', 'desktop-mode' ),
124 'desc' => __( 'Make it yours. Choose a wallpaper, pin your favorite screens to the dock, and switch accent colors — all from OS Settings.', 'desktop-mode' ),
125 ),
126 array(
127 'icon' => '🤖',
128 'title' => __( 'AI Copilot, built in', 'desktop-mode' ),
129 'desc' => __( 'Press ⌘K anywhere to ask the AI Assistant — it can run commands, navigate windows and answer questions about your site.', 'desktop-mode' ),
130 ),
131 );
132 ?>
133 <style id="desktop-mode-welcome-style">
134 .desktop-mode-welcome,
135 .desktop-mode-welcome * {
136 box-sizing: border-box;
137 }
138 .desktop-mode-welcome {
139 position: fixed;
140 inset: 0;
141 z-index: 100000;
142 display: flex;
143 align-items: center;
144 justify-content: center;
145 padding: 24px;
146 background: radial-gradient(
147 ellipse at 30% 20%,
148 rgba( 56, 189, 248, 0.32 ),
149 transparent 60%
150 ),
151 radial-gradient(
152 ellipse at 70% 80%,
153 rgba( 16, 185, 129, 0.26 ),
154 transparent 55%
155 ),
156 rgba( 8, 14, 26, 0.62 );
157 backdrop-filter: blur( 14px ) saturate( 140% );
158 -webkit-backdrop-filter: blur( 14px ) saturate( 140% );
159 animation: desktop-mode-welcome-fade 360ms cubic-bezier( 0.22, 1, 0.36, 1 );
160 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
161 "Helvetica Neue", Arial, sans-serif;
162 -webkit-font-smoothing: antialiased;
163 -moz-osx-font-smoothing: grayscale;
164 }
165 @keyframes desktop-mode-welcome-fade {
166 from { opacity: 0; }
167 to { opacity: 1; }
168 }
169 @keyframes desktop-mode-welcome-pop {
170 0% { opacity: 0; transform: translateY( 14px ) scale( 0.96 ); }
171 100% { opacity: 1; transform: translateY( 0 ) scale( 1 ); }
172 }
173 @keyframes desktop-mode-welcome-arrow {
174 0%, 100% { transform: translateX( 0 ); opacity: 0.85; }
175 50% { transform: translateX( 6px ); opacity: 1; }
176 }
177 @keyframes desktop-mode-welcome-shimmer {
178 0% { background-position: 0% 50%; }
179 100% { background-position: 200% 50%; }
180 }
181 .desktop-mode-welcome__card {
182 position: relative;
183 width: 100%;
184 max-width: 760px;
185 border-radius: 22px;
186 overflow: hidden;
187 background: linear-gradient(
188 145deg,
189 rgba( 255, 255, 255, 0.98 ) 0%,
190 rgba( 244, 250, 253, 0.98 ) 100%
191 );
192 box-shadow:
193 0 1px 0 rgba( 255, 255, 255, 0.85 ) inset,
194 0 30px 60px -20px rgba( 8, 47, 73, 0.55 ),
195 0 18px 36px -18px rgba( 14, 165, 233, 0.45 );
196 animation: desktop-mode-welcome-pop 460ms cubic-bezier( 0.22, 1, 0.36, 1 ) both;
197 animation-delay: 60ms;
198 }
199 .desktop-mode-welcome__card::before {
200 content: "";
201 position: absolute;
202 inset: -1px;
203 border-radius: inherit;
204 padding: 1px;
205 background: linear-gradient(
206 135deg,
207 rgba( 14, 165, 233, 0.65 ),
208 rgba( 6, 182, 212, 0.55 ),
209 rgba( 16, 185, 129, 0.55 )
210 );
211 -webkit-mask: linear-gradient( #000 0 0 ) content-box,
212 linear-gradient( #000 0 0 );
213 -webkit-mask-composite: xor;
214 mask-composite: exclude;
215 pointer-events: none;
216 }
217 .desktop-mode-welcome__hero {
218 position: relative;
219 padding: 36px 36px 24px;
220 background: linear-gradient(
221 135deg,
222 #0f172a 0%,
223 #0e7490 45%,
224 #06b6d4 100%
225 );
226 background-size: 200% 200%;
227 animation: desktop-mode-welcome-shimmer 14s ease infinite alternate;
228 color: #fff;
229 overflow: hidden;
230 }
231 .desktop-mode-welcome__hero::after {
232 content: "";
233 position: absolute;
234 inset: 0;
235 background:
236 radial-gradient( circle at 85% 15%, rgba( 56, 189, 248, 0.35 ), transparent 45% ),
237 radial-gradient( circle at 15% 90%, rgba( 16, 185, 129, 0.22 ), transparent 50% ),
238 linear-gradient( rgba( 255, 255, 255, 0.05 ) 1px, transparent 1px ) 0 0 / 28px 28px,
239 linear-gradient( 90deg, rgba( 255, 255, 255, 0.05 ) 1px, transparent 1px ) 0 0 / 28px 28px;
240 pointer-events: none;
241 mask-image: radial-gradient( ellipse at 50% 40%, #000 35%, transparent 80% );
242 -webkit-mask-image: radial-gradient( ellipse at 50% 40%, #000 35%, transparent 80% );
243 }
244 .desktop-mode-welcome__eyebrow {
245 display: inline-flex;
246 align-items: center;
247 gap: 8px;
248 padding: 5px 12px;
249 font-size: 11px;
250 font-weight: 600;
251 letter-spacing: 0.12em;
252 text-transform: uppercase;
253 color: #fff;
254 background: rgba( 255, 255, 255, 0.18 );
255 border: 1px solid rgba( 255, 255, 255, 0.28 );
256 border-radius: 999px;
257 backdrop-filter: blur( 6px );
258 -webkit-backdrop-filter: blur( 6px );
259 }
260 .desktop-mode-welcome__eyebrow-dot {
261 width: 6px;
262 height: 6px;
263 border-radius: 50%;
264 background: #22d3ee;
265 box-shadow: 0 0 0 4px rgba( 34, 211, 238, 0.28 );
266 }
267 .desktop-mode-welcome__title {
268 margin: 14px 0 6px;
269 font-size: 26px;
270 line-height: 1.2;
271 font-weight: 700;
272 letter-spacing: -0.01em;
273 color: #fff;
274 }
275 .desktop-mode-welcome__subtitle {
276 margin: 0;
277 font-size: 14px;
278 line-height: 1.5;
279 color: rgba( 255, 255, 255, 0.85 );
280 }
281 .desktop-mode-welcome__body {
282 padding: 24px 36px 24px;
283 font-size: 14.5px;
284 line-height: 1.6;
285 color: #1f2937;
286 }
287 .desktop-mode-welcome__lede {
288 margin: 0;
289 font-size: 14.5px;
290 color: #374151;
291 }
292 .desktop-mode-welcome__features {
293 display: grid;
294 grid-template-columns: repeat( 2, minmax( 0, 1fr ) );
295 gap: 14px;
296 margin: 20px 0 0;
297 padding: 0;
298 list-style: none;
299 }
300 .desktop-mode-welcome__feature {
301 position: relative;
302 padding: 14px 14px 14px 52px;
303 background: linear-gradient(
304 145deg,
305 rgba( 255, 255, 255, 0.85 ),
306 rgba( 240, 249, 255, 0.85 )
307 );
308 border: 1px solid rgba( 14, 165, 233, 0.18 );
309 border-radius: 12px;
310 box-shadow: 0 1px 0 rgba( 255, 255, 255, 0.9 ) inset,
311 0 2px 6px -2px rgba( 8, 47, 73, 0.08 );
312 }
313 .desktop-mode-welcome__feature-icon {
314 position: absolute;
315 top: 12px;
316 left: 12px;
317 width: 30px;
318 height: 30px;
319 display: inline-flex;
320 align-items: center;
321 justify-content: center;
322 border-radius: 9px;
323 background: linear-gradient( 135deg, rgba( 14, 165, 233, 0.16 ), rgba( 16, 185, 129, 0.16 ) );
324 border: 1px solid rgba( 14, 165, 233, 0.22 );
325 font-size: 16px;
326 line-height: 1;
327 }
328 .desktop-mode-welcome__feature-title {
329 display: block;
330 margin: 0 0 2px;
331 font-size: 13.5px;
332 font-weight: 700;
333 color: #0f172a;
334 }
335 .desktop-mode-welcome__feature-desc {
336 margin: 0;
337 font-size: 12.5px;
338 line-height: 1.5;
339 color: #475569;
340 }
341 .desktop-mode-welcome__hint {
342 display: flex;
343 align-items: center;
344 gap: 12px;
345 margin: 18px 0 0;
346 padding: 14px 16px;
347 background: linear-gradient(
348 135deg,
349 rgba( 14, 165, 233, 0.08 ),
350 rgba( 16, 185, 129, 0.08 )
351 );
352 border: 1px solid rgba( 14, 165, 233, 0.22 );
353 border-radius: 12px;
354 font-size: 13.5px;
355 color: #0c4a6e;
356 }
357 .desktop-mode-welcome__hint-icon {
358 flex-shrink: 0;
359 width: 24px;
360 height: 24px;
361 border-radius: 50%;
362 display: inline-flex;
363 align-items: center;
364 justify-content: center;
365 background: linear-gradient( 135deg, #0ea5e9, #06b6d4 );
366 color: #fff;
367 font-size: 14px;
368 line-height: 1;
369 animation: desktop-mode-welcome-arrow 1.8s ease-in-out infinite;
370 }
371 .desktop-mode-welcome__hint-icon::before {
372 content: "";
373 font-weight: 700;
374 }
375 .desktop-mode-welcome__hint code {
376 display: inline-block;
377 padding: 2px 6px;
378 margin: 0 2px;
379 background: rgba( 255, 255, 255, 0.85 );
380 border: 1px solid rgba( 14, 165, 233, 0.28 );
381 border-radius: 5px;
382 font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
383 font-size: 12px;
384 color: #0369a1;
385 }
386 .desktop-mode-welcome__actions {
387 display: flex;
388 align-items: center;
389 justify-content: flex-end;
390 gap: 10px;
391 padding: 0 36px 28px;
392 }
393 .desktop-mode-welcome__btn {
394 display: inline-flex;
395 align-items: center;
396 justify-content: center;
397 gap: 6px;
398 min-height: 38px;
399 padding: 8px 18px;
400 font-size: 14px;
401 font-weight: 600;
402 font-family: inherit;
403 line-height: 1;
404 border-radius: 10px;
405 border: 1px solid transparent;
406 cursor: pointer;
407 transition: transform 120ms ease, box-shadow 120ms ease,
408 background 120ms ease, color 120ms ease;
409 }
410 .desktop-mode-welcome__btn:focus-visible {
411 outline: 2px solid #0ea5e9;
412 outline-offset: 2px;
413 }
414 .desktop-mode-welcome__btn--ghost {
415 background: transparent;
416 color: #475569;
417 border-color: transparent;
418 }
419 .desktop-mode-welcome__btn--ghost:hover {
420 background: rgba( 15, 23, 42, 0.06 );
421 color: #0f172a;
422 }
423 .desktop-mode-welcome__btn--secondary {
424 color: #0369a1;
425 background: rgba( 14, 165, 233, 0.10 );
426 border-color: rgba( 14, 165, 233, 0.28 );
427 }
428 .desktop-mode-welcome__btn--secondary:hover {
429 background: rgba( 14, 165, 233, 0.16 );
430 color: #075985;
431 }
432 .desktop-mode-welcome__btn--primary {
433 color: #fff;
434 background: linear-gradient( 135deg, #0369a1, #0ea5e9 55%, #06b6d4 );
435 background-size: 180% 180%;
436 box-shadow: 0 10px 24px -10px rgba( 14, 165, 233, 0.75 ),
437 0 4px 10px -4px rgba( 6, 182, 212, 0.55 );
438 }
439 .desktop-mode-welcome__btn--primary:hover {
440 transform: translateY( -1px );
441 background-position: 100% 50%;
442 box-shadow: 0 14px 30px -12px rgba( 14, 165, 233, 0.85 ),
443 0 6px 14px -4px rgba( 6, 182, 212, 0.65 );
444 }
445 .desktop-mode-welcome__btn--primary:active {
446 transform: translateY( 0 );
447 }
448 .desktop-mode-welcome__btn[disabled] {
449 opacity: 0.65;
450 cursor: progress;
451 }
452 .desktop-mode-welcome__close {
453 position: absolute;
454 top: 14px;
455 right: 14px;
456 width: 32px;
457 height: 32px;
458 display: inline-flex;
459 align-items: center;
460 justify-content: center;
461 padding: 0;
462 background: rgba( 255, 255, 255, 0.18 );
463 color: #fff;
464 border: 1px solid rgba( 255, 255, 255, 0.25 );
465 border-radius: 50%;
466 cursor: pointer;
467 font-size: 18px;
468 line-height: 1;
469 transition: background 120ms ease, transform 120ms ease;
470 }
471 .desktop-mode-welcome__close:hover {
472 background: rgba( 255, 255, 255, 0.32 );
473 transform: rotate( 90deg );
474 }
475 .desktop-mode-welcome__close:focus-visible {
476 outline: 2px solid #fff;
477 outline-offset: 2px;
478 }
479 body.desktop-mode-welcome-open {
480 overflow: hidden;
481 }
482 @media ( max-width: 760px ) {
483 .desktop-mode-welcome__features {
484 grid-template-columns: 1fr;
485 }
486 }
487 @media ( max-width: 600px ) {
488 .desktop-mode-welcome__hero {
489 padding: 28px 24px 20px;
490 }
491 .desktop-mode-welcome__body,
492 .desktop-mode-welcome__actions {
493 padding-left: 24px;
494 padding-right: 24px;
495 }
496 .desktop-mode-welcome__title {
497 font-size: 22px;
498 }
499 .desktop-mode-welcome__actions {
500 flex-direction: column-reverse;
501 align-items: stretch;
502 }
503 .desktop-mode-welcome__btn {
504 width: 100%;
505 }
506 }
507 @media ( prefers-reduced-motion: reduce ) {
508 .desktop-mode-welcome,
509 .desktop-mode-welcome__card,
510 .desktop-mode-welcome__hero,
511 .desktop-mode-welcome__hint-icon {
512 animation: none !important;
513 }
514 }
515 </style>
516 <div
517 class="desktop-mode-welcome"
518 role="dialog"
519 aria-modal="true"
520 aria-labelledby="desktop-mode-welcome-title"
521 aria-describedby="desktop-mode-welcome-desc"
522 data-slug="<?php echo esc_attr( $slug ); ?>"
523 >
524 <div class="desktop-mode-welcome__card">
525 <button
526 type="button"
527 class="desktop-mode-welcome__close"
528 aria-label="<?php echo esc_attr( $close_a ); ?>"
529 data-desktop-mode-welcome-dismiss
530 >&times;</button>
531 <div class="desktop-mode-welcome__hero">
532 <span class="desktop-mode-welcome__eyebrow">
533 <span class="desktop-mode-welcome__eyebrow-dot" aria-hidden="true"></span>
534 <?php echo esc_html__( 'New here', 'desktop-mode' ); ?>
535 </span>
536 <h2 id="desktop-mode-welcome-title" class="desktop-mode-welcome__title">
537 <?php echo esc_html( $title ); ?>
538 </h2>
539 <p class="desktop-mode-welcome__subtitle">
540 <?php echo esc_html( $subtitle ); ?>
541 </p>
542 </div>
543 <div class="desktop-mode-welcome__body">
544 <p id="desktop-mode-welcome-desc" class="desktop-mode-welcome__lede">
545 <?php echo esc_html( $body ); ?>
546 </p>
547 <ul class="desktop-mode-welcome__features">
548 <?php foreach ( $features as $feature ) : ?>
549 <li class="desktop-mode-welcome__feature">
550 <span class="desktop-mode-welcome__feature-icon" aria-hidden="true">
551 <?php echo esc_html( $feature['icon'] ); ?>
552 </span>
553 <strong class="desktop-mode-welcome__feature-title">
554 <?php echo esc_html( $feature['title'] ); ?>
555 </strong>
556 <p class="desktop-mode-welcome__feature-desc">
557 <?php echo esc_html( $feature['desc'] ); ?>
558 </p>
559 </li>
560 <?php endforeach; ?>
561 </ul>
562 <p class="desktop-mode-welcome__hint">
563 <span class="desktop-mode-welcome__hint-icon" aria-hidden="true"></span>
564 <?php
565 printf(
566 /* translators: %s: the label of the admin bar button. */
567 esc_html__( 'Prefer to switch yourself? Click %s in the top-right of your admin bar.', 'desktop-mode' ),
568 '<code>' . esc_html__( 'Switch to Desktop Mode', 'desktop-mode' ) . '</code>'
569 );
570 ?>
571 </p>
572 </div>
573 <div class="desktop-mode-welcome__actions">
574 <button
575 type="button"
576 class="desktop-mode-welcome__btn desktop-mode-welcome__btn--secondary"
577 data-desktop-mode-welcome-cta
578 >
579 <?php echo esc_html( $cta ); ?>
580 </button>
581 <button
582 type="button"
583 class="desktop-mode-welcome__btn desktop-mode-welcome__btn--primary"
584 data-desktop-mode-welcome-enable
585 data-label-idle="<?php echo esc_attr( $enable ); ?>"
586 data-label-busy="<?php echo esc_attr( $enabling ); ?>"
587 >
588 <?php echo esc_html( $enable ); ?>
589 </button>
590 </div>
591 </div>
592 </div>
593 <script id="desktop-mode-welcome-script">
594 ( function () {
595 var root = document.querySelector( '.desktop-mode-welcome' );
596 if ( ! root ) {
597 return;
598 }
599 var cfg = {
600 url: <?php echo wp_json_encode( $rest_url ); ?>,
601 nonce: <?php echo wp_json_encode( $rest_nonce ); ?>,
602 slug: <?php echo wp_json_encode( $slug ); ?>,
603 ajaxUrl: <?php echo wp_json_encode( $ajax_url ); ?>,
604 ajaxNonce: <?php echo wp_json_encode( $ajax_nonce ); ?>,
605 };
606
607 document.body.classList.add( 'desktop-mode-welcome-open' );
608
609 // Focus the primary CTA so keyboard users land somewhere meaningful.
610 var primary = root.querySelector( '[data-desktop-mode-welcome-enable]' )
611 || root.querySelector( '[data-desktop-mode-welcome-cta]' );
612 if ( primary ) {
613 try { primary.focus( { preventScroll: true } ); } catch ( e ) {}
614 }
615
616 function close() {
617 if ( ! root || ! root.parentNode ) {
618 return;
619 }
620 root.parentNode.removeChild( root );
621 document.body.classList.remove( 'desktop-mode-welcome-open' );
622 document.removeEventListener( 'keydown', onKey );
623 }
624
625 function persist() {
626 // Fire-and-forget. The seen-intros endpoint always returns the
627 // post-mutation list, but we don't need it here; if the request
628 // fails (offline, REST disabled) the dialog will simply show
629 // again next page load — exactly the behavior a user would
630 // expect from a "save my dismissal" call that didn't reach the
631 // server.
632 try {
633 var headers = { 'Content-Type': 'application/json' };
634 if ( cfg.nonce ) {
635 headers[ 'X-WP-Nonce' ] = cfg.nonce;
636 }
637 fetch( cfg.url, {
638 method: 'POST',
639 credentials: 'same-origin',
640 headers: headers,
641 body: JSON.stringify( { slug: cfg.slug } ),
642 } ).catch( function () {} );
643 } catch ( e ) {}
644 }
645
646 function dismiss() {
647 persist();
648 close();
649 }
650
651 var enabling = false;
652
653 function enableNow( btn ) {
654 if ( enabling ) {
655 return;
656 }
657 enabling = true;
658 var idle = btn.getAttribute( 'data-label-idle' ) || btn.textContent;
659 var busy = btn.getAttribute( 'data-label-busy' ) || idle;
660 btn.disabled = true;
661 btn.textContent = busy;
662
663 // Persist the dismissal in parallel — even if the AJAX save fails
664 // the user explicitly asked to turn the mode on, so they don't
665 // need to see the welcome again.
666 persist();
667
668 var form = new FormData();
669 form.append( 'action', 'save-desktop-mode' );
670 form.append( 'nonce', cfg.ajaxNonce );
671 form.append( 'enabled', '1' );
672
673 fetch( cfg.ajaxUrl, {
674 method: 'POST',
675 credentials: 'same-origin',
676 body: form,
677 } ).then( function ( r ) {
678 return r.json().catch( function () { return null; } );
679 } ).then( function ( data ) {
680 var redirect = data && data.success && data.data && data.data.redirect
681 ? data.data.redirect
682 : null;
683 if ( redirect ) {
684 window.location.href = redirect;
685 return;
686 }
687 // Fallback — reload so the shell takes over (the AJAX endpoint
688 // already wrote the user meta, so this request will boot
689 // straight into Desktop Mode via the portal flow).
690 window.location.reload();
691 } ).catch( function () {
692 enabling = false;
693 btn.disabled = false;
694 btn.textContent = idle;
695 } );
696 }
697
698 root.addEventListener( 'click', function ( event ) {
699 var target = event.target;
700 if ( ! ( target instanceof Element ) ) {
701 return;
702 }
703 var enableBtn = target.closest( '[data-desktop-mode-welcome-enable]' );
704 if ( enableBtn ) {
705 event.preventDefault();
706 enableNow( enableBtn );
707 return;
708 }
709 if ( target.closest( '[data-desktop-mode-welcome-dismiss]' ) ||
710 target.closest( '[data-desktop-mode-welcome-cta]' ) ) {
711 event.preventDefault();
712 dismiss();
713 return;
714 }
715 // Backdrop click — outside the card.
716 if ( target === root ) {
717 event.preventDefault();
718 dismiss();
719 }
720 } );
721
722 function onKey( event ) {
723 if ( event.key === 'Escape' || event.key === 'Esc' ) {
724 event.preventDefault();
725 dismiss();
726 }
727 }
728 document.addEventListener( 'keydown', onKey );
729 } )();
730 </script>
731 <?php
732 }
733 add_action( 'admin_footer', 'desktop_mode_render_welcome_dialog' );
734