| @@ -1,23 +1,19 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | - * OpenStation — Recycle Bin: window registration. | |
| 3 | + * OpenStation — Recycle Bin: tile art, gate, shell config. | |
| 4 | 4 | * |
| 5 | - * Native window with id `desktop-mode-recycle-bin`, pinned to the dock. | |
| 6 | - * Like the code editor, the template body is a static skeleton that the | |
| 7 | - * JS bundle enhances on first open — the table is populated from the | |
| 8 | - * REST list endpoint at render time. | |
| 5 | + * The bin WINDOW is an App Framework app (`apps/trash/trash.os.php`) | |
| 6 | + * that claims the frozen `desktop-mode-recycle-bin` id, so every | |
| 7 | + * shortcut, dock placement and drop target keeps working. This file | |
| 8 | + * keeps what the shell needs regardless of the window: the two bin | |
| 9 | + * drawings (the dock tile's empty/full swap — `icon-state.ts` — and | |
| 10 | + * the app's own `ctx.host.setIcon()` swap read the same pair), the | |
| 11 | + * capability gate, the shell-config seed that makes the tile correct | |
| 12 | + * on the very first paint, and the stylesheet enqueue (the | |
| 13 | + * drag-to-trash drop-target highlight must exist at boot, not at | |
| 14 | + * window-open). | |
| 9 | 15 | * |
| 10 | - * The bin lands on the dock and nowhere else. It used to also register | |
| 11 | - * a wallpaper icon, which put the same target on two surfaces at once | |
| 12 | - * and made the desktop something the shell furnished rather than | |
| 13 | - * something the user did. That is a default, not a rule: the tile is | |
| 14 | - * `placeable`, so the wallpaper is one pick away in Apps & Plugins. | |
| 15 | - * | |
| 16 | - * The registration is filterable via `openstation_recycle_bin_window_args` | |
| 17 | - * so a plugin can swap the icon, change the dimensions, or restrict who | |
| 18 | - * sees the bin without touching this file. | |
| 19 | - * | |
| 20 | 16 | * @package OpenStation |
| 21 | 17 | */ |
| 22 | 18 | |
| 23 | 19 | defined( 'ABSPATH' ) || exit; |
| @@ -22,9 +18,9 @@ | ||
| 22 | 18 | |
| 23 | 19 | defined( 'ABSPATH' ) || exit; |
| 24 | 20 | |
| 25 | 21 | /** |
| 26 | - * The bin SVG, used by the window icon and its dock tile. | |
| 22 | + * The bin SVG, used by the app window's icon and its dock tile. | |
| 27 | 23 | * |
| 28 | 24 | * The bin used to be `dashicons-trash`, which worked but wore the |
| 29 | 25 | * wrong clothes. Dashicons are WP core's icon set: solid fills on a |
| 30 | 26 | * 20-unit grid, tuned for admin-menu sizes. The shell's own icons are |
| @@ -49,11 +45,9 @@ | ||
| 49 | 45 | * are menu glyphs sitting among other menu glyphs, and they should |
| 50 | 46 | * match their neighbours rather than this icon. |
| 51 | 47 | * |
| 52 | 48 | * The bin has two states. Empty is the vessel on its own; full adds |
| 53 | - * three crumpled balls inside it and knocks the lid askew. See | |
| 54 | - * {@link openstation_recycle_bin_icon_svg()} for why the lid only | |
| 55 | - * moves 8 degrees. | |
| 49 | + * three crumpled balls inside it and knocks the lid askew. | |
| 56 | 50 | * |
| 57 | 51 | * @param bool $full Whether to draw the bin holding something. |
| 58 | 52 | * @return string Raw `<svg>` markup. |
| 59 | 53 | */ |
| @@ -58,12 +52,20 @@ | ||
| 58 | 52 | * @return string Raw `<svg>` markup. |
| 59 | 53 | */ |
| 60 | 54 | function openstation_recycle_bin_icon_svg( $full = false ) { |
| 61 | 55 | // The lid and the handle travel together. In the full state the |
| 62 | - // pair is knocked askew, which is the whole difference at the top | |
| 63 | - // of the mark. | |
| 56 | + // pair is lifted clear of the rim and tipped, hinged at the far | |
| 57 | + // corner, the way a lid sits on a bin that has too much in it. | |
| 58 | + // | |
| 59 | + // It was a 2.5-unit lift at 8 degrees, which on the 20px dock | |
| 60 | + // tile and the 58px home tile was no difference at all: the bin | |
| 61 | + // read as empty while it held things. At 4 units and 12 degrees | |
| 62 | + // (a positive angle turns clockwise about the far corner, so the | |
| 63 | + // near end rises) the lid's near end clears the rim by a fifth | |
| 64 | + // of the body's height and its far end by nine units, and the | |
| 65 | + // wedge of light under it survives every size the mark is drawn. | |
| 64 | 66 | $lid_transform = $full |
| 65 | - ? ' transform="translate(0 -2.5) rotate(8 32 21.5)"' | |
| 67 | + ? ' transform="translate(0 -4) rotate(12 52 21.5)"' | |
| 66 | 68 | : ''; |
| 67 | 69 | |
| 68 | 70 | $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">' |
| 69 | 71 | . '<g' . $lid_transform . '>' |
| @@ -106,8 +108,16 @@ | ||
| 106 | 108 | $svg .= '<path d="M29.7 38 27.4 39.5 24.7 39.6 23.6 37.1 24.1 34.4 26.8 34.1 29.2 35.1' |
| 107 | 109 | . 'ZM39.4 44.1 36.7 43.5 34.8 41.6 35.8 39.1 38.1 37.6 40.2 39.3 41.1 41.8' |
| 108 | 110 | . 'ZM28.1 50.4 27 47.9 27.4 45.2 30 44.6 32.6 45.6 32.4 48.3 31 50.5Z"' |
| 109 | 111 | . ' fill="currentColor" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>'; |
| 112 | + // A fourth, larger sheet rides the rim under the lifted end of | |
| 113 | + // the lid — the one element of the full state that is legible | |
| 114 | + // at 20px, where the balls inside the body are a texture at | |
| 115 | + // best. Same seven-point construction, twice the radius, | |
| 116 | + // straddling the rim (y 28.5) so it reads as spilling out | |
| 117 | + // rather than sitting in. | |
| 118 | + $svg .= '<path d="M30 26 27.3 30 22.7 31.7 19.5 28.2 18.6 23.4 22.8 20.7 27.6 21.6Z"' | |
| 119 | + . ' fill="currentColor" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>'; | |
| 110 | 120 | } |
| 111 | 121 | |
| 112 | 122 | return $svg . '</svg>'; |
| 113 | 123 | } |
| @@ -128,114 +138,13 @@ | ||
| 128 | 138 | ); |
| 129 | 139 | } |
| 130 | 140 | |
| 131 | 141 | /** |
| 132 | - * Echoes the recycle bin window's template body. | |
| 133 | - * | |
| 134 | - * The shell wraps this in `<template id="os-native-window-desktop-mode-recycle-bin">` | |
| 135 | - * and clones it into the window body BEFORE the JS render callback runs. | |
| 136 | - * The `data-os-recycle-bin-*` hooks below are the contract the JS | |
| 137 | - * relies on — keep them intact (or rename via the filter) when | |
| 138 | - * customizing the layout. | |
| 139 | - */ | |
| 140 | -function openstation_recycle_bin_render_template() { | |
| 141 | - ob_start(); | |
| 142 | - ?> | |
| 143 | - <div class="desktop-mode-recycle-bin" data-os-recycle-bin-root> | |
| 144 | - <header class="os-recycle-bin__toolbar" data-os-recycle-bin-toolbar> | |
| 145 | - <div class="os-recycle-bin__toolbar-left"> | |
| 146 | - <os-segmented data-os-recycle-bin-filter> | |
| 147 | - <os-segment value="" selected><?php esc_html_e( 'All', 'desktop-mode' ); ?></os-segment> | |
| 148 | - <os-segment value="post"><?php esc_html_e( 'Posts', 'desktop-mode' ); ?></os-segment> | |
| 149 | - <os-segment value="page"><?php esc_html_e( 'Pages', 'desktop-mode' ); ?></os-segment> | |
| 150 | - <?php | |
| 151 | - // The Media segment is only useful when WP itself routes | |
| 152 | - // attachment deletions through Trash. That gate is the | |
| 153 | - // `MEDIA_TRASH` constant — defaults to false in core, can | |
| 154 | - // be flipped to true from `wp-config.php`. Without it, | |
| 155 | - // attachments permanent-delete on first click and the | |
| 156 | - // Trash bin will never have anything in this bucket, so | |
| 157 | - // the tab would always read "0" and confuse users. | |
| 158 | - if ( defined( 'MEDIA_TRASH' ) && MEDIA_TRASH ) : | |
| 159 | - ?> | |
| 160 | - <os-segment value="attachment"><?php esc_html_e( 'Media', 'desktop-mode' ); ?></os-segment> | |
| 161 | - <?php | |
| 162 | - endif; | |
| 163 | - ?> | |
| 164 | - <os-segment value="comment"><?php esc_html_e( 'Comments', 'desktop-mode' ); ?></os-segment> | |
| 165 | - <os-segment value="desktop"><?php esc_html_e( 'Desktop', 'desktop-mode' ); ?></os-segment> | |
| 166 | - </os-segmented> | |
| 167 | - <os-text-field | |
| 168 | - data-os-recycle-bin-search | |
| 169 | - placeholder="<?php esc_attr_e( 'Search trash…', 'desktop-mode' ); ?>" | |
| 170 | - ></os-text-field> | |
| 171 | - </div> | |
| 172 | - <div class="os-recycle-bin__toolbar-right" data-os-recycle-bin-bulk hidden> | |
| 173 | - <span class="os-recycle-bin__count" data-os-recycle-bin-count></span> | |
| 174 | - <os-button variant="secondary" data-os-recycle-bin-restore-selected> | |
| 175 | - <span class="dashicons dashicons-image-rotate" aria-hidden="true"></span> | |
| 176 | - <?php esc_html_e( 'Restore', 'desktop-mode' ); ?> | |
| 177 | - </os-button> | |
| 178 | - <os-button variant="secondary" data-os-recycle-bin-pin-to-desktop> | |
| 179 | - <span class="dashicons dashicons-desktop" aria-hidden="true"></span> | |
| 180 | - <?php esc_html_e( 'Pin to desktop', 'desktop-mode' ); ?> | |
| 181 | - </os-button> | |
| 182 | - <os-button variant="danger" data-os-recycle-bin-purge-selected> | |
| 183 | - <span class="dashicons dashicons-trash" aria-hidden="true"></span> | |
| 184 | - <?php esc_html_e( 'Delete forever', 'desktop-mode' ); ?> | |
| 185 | - </os-button> | |
| 186 | - </div> | |
| 187 | - <div class="os-recycle-bin__toolbar-trailing"> | |
| 188 | - <os-button variant="ghost" data-os-recycle-bin-refresh title="<?php esc_attr_e( 'Refresh', 'desktop-mode' ); ?>"> | |
| 189 | - <span class="dashicons dashicons-update" aria-hidden="true"></span> | |
| 190 | - </os-button> | |
| 191 | - <os-button variant="danger" data-os-recycle-bin-empty> | |
| 192 | - <span class="dashicons dashicons-trash" aria-hidden="true"></span> | |
| 193 | - <?php esc_html_e( 'Empty Trash', 'desktop-mode' ); ?> | |
| 194 | - </os-button> | |
| 195 | - </div> | |
| 196 | - </header> | |
| 197 | - <div class="os-recycle-bin__body" data-os-recycle-bin-body> | |
| 198 | - <os-table | |
| 199 | - data-os-recycle-bin-table | |
| 200 | - selectable="multi" | |
| 201 | - sticky-header | |
| 202 | - hover | |
| 203 | - striped | |
| 204 | - loading | |
| 205 | - > | |
| 206 | - <div slot="empty" class="os-recycle-bin__empty"> | |
| 207 | - <span class="dashicons dashicons-trash" aria-hidden="true"></span> | |
| 208 | - <p><?php esc_html_e( 'The Trash is empty.', 'desktop-mode' ); ?></p> | |
| 209 | - <p class="os-recycle-bin__empty-hint"> | |
| 210 | - <?php esc_html_e( 'Deleted posts, pages, and media show up here. Restoring puts them back where they were.', 'desktop-mode' ); ?> | |
| 211 | - </p> | |
| 212 | - </div> | |
| 213 | - </os-table> | |
| 214 | - </div> | |
| 215 | - </div> | |
| 216 | - <?php | |
| 217 | - $html = (string) ob_get_clean(); | |
| 218 | - | |
| 219 | - /** | |
| 220 | - * Filter the recycle bin window's template HTML. | |
| 221 | - * | |
| 222 | - * Keep the `data-os-recycle-bin-*` hooks intact so the JS render | |
| 223 | - * callback can find its mount points, or rename them and update the | |
| 224 | - * matching constants in `src/recycle-bin/index.ts`. | |
| 225 | - * | |
| 226 | - * @param string $html Default template HTML. | |
| 227 | - */ | |
| 228 | - $filtered = (string) apply_filters( 'openstation_recycle_bin_template_html', $html ); | |
| 229 | - echo wp_kses( $filtered, openstation_native_window_allowed_html() ); | |
| 230 | -} | |
| 231 | - | |
| 232 | -/** | |
| 233 | 142 | * Whether the current user should see the recycle bin at all. |
| 234 | 143 | * |
| 235 | 144 | * Filterable so plugins can hide it from authors/contributors who |
| 236 | 145 | * don't manage trash, or invert the gate to expose it to a custom |
| 237 | - * role. | |
| 146 | + * role. The app's gate (`apps/trash/trash.os.php`) delegates here. | |
| 238 | 147 | * |
| 239 | 148 | * @return bool |
| 240 | 149 | */ |
| 241 | 150 | function openstation_recycle_bin_user_can_use() { |
| @@ -249,86 +158,22 @@ | ||
| 249 | 158 | return (bool) apply_filters( 'openstation_recycle_bin_user_can_use', $can ); |
| 250 | 159 | } |
| 251 | 160 | |
| 252 | 161 | /** |
| 253 | - * Register the recycle bin window on `init`. | |
| 162 | + * Enqueue the bin stylesheet with the shell. | |
| 254 | 163 | * |
| 255 | - * Hooked at priority 20, after `components.php` has bootstrapped the | |
| 256 | - * native-window registry — same timing as the code editor. | |
| 164 | + * Not a window-open companion: the sheet also paints the | |
| 165 | + * drag-to-trash drop-target highlight on the CLOSED bin's dock tile | |
| 166 | + * (`data-os-trash-drop-active`), which must work before the window | |
| 167 | + * has ever opened. | |
| 257 | 168 | */ |
| 258 | -function openstation_recycle_bin_register_window() { | |
| 169 | +function openstation_recycle_bin_enqueue_style() { | |
| 259 | 170 | if ( ! openstation_recycle_bin_user_can_use() ) { |
| 260 | 171 | return; |
| 261 | 172 | } |
| 262 | - | |
| 263 | - $icon_uris = openstation_recycle_bin_icon_uris(); | |
| 264 | - $icon_uri = $icon_uris['empty']; | |
| 265 | - | |
| 266 | - $window_args = array( | |
| 267 | - 'title' => __( 'Trash', 'desktop-mode' ), | |
| 268 | - 'icon' => $icon_uri, | |
| 269 | - 'template' => 'openstation_recycle_bin_render_template', | |
| 270 | - 'script' => 'desktop-mode-recycle-bin', | |
| 271 | - 'width' => 880, | |
| 272 | - 'height' => 560, | |
| 273 | - 'min_width' => 520, | |
| 274 | - 'min_height' => 360, | |
| 275 | - 'placement' => 'taskbar', | |
| 276 | - // Last on the rail, after the shell's own cluster (Mio 10, | |
| 277 | - // Overview 20, System 30). Trash is where things END UP, and a | |
| 278 | - // dock reads left to right: putting it anywhere but the end | |
| 279 | - // makes it one more app rather than the bottom of the pile. | |
| 280 | - 'dock_order' => 40, | |
| 281 | - // The bin is the one dock tile a user can reasonably not want, | |
| 282 | - // so it gets a row in Apps & Plugins: dock (the default), | |
| 283 | - // desktop, both, or hidden. It registers no desktop icon, so | |
| 284 | - // that row is its only control. | |
| 285 | - 'placeable' => true, | |
| 286 | - ); | |
| 287 | - | |
| 288 | - /** | |
| 289 | - * Filter the args used to register the recycle bin native window. | |
| 290 | - * | |
| 291 | - * @param array $window_args Args passed to `openstation_register_window()`. | |
| 292 | - */ | |
| 293 | - $window_args = (array) apply_filters( 'openstation_recycle_bin_window_args', $window_args ); | |
| 294 | - | |
| 295 | - $registered = openstation_register_window( 'desktop-mode-recycle-bin', $window_args ); | |
| 296 | - if ( is_wp_error( $registered ) ) { | |
| 297 | - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log | |
| 298 | - error_log( '[openstation] Recycle bin window registration failed: ' . $registered->get_error_message() ); | |
| 299 | - } | |
| 300 | -} | |
| 301 | -add_action( 'init', 'openstation_recycle_bin_register_window', 20 ); | |
| 302 | - | |
| 303 | -/** | |
| 304 | - * Localize REST endpoints for the JS bundle. | |
| 305 | - * | |
| 306 | - * Same pattern as the code editor: the bundle reads its config off | |
| 307 | - * `window.openStationRecycleBinConfig` and never hardcodes URLs. | |
| 308 | - */ | |
| 309 | -function openstation_recycle_bin_localize_config() { | |
| 310 | - if ( ! openstation_recycle_bin_user_can_use() ) { | |
| 311 | - return; | |
| 312 | - } | |
| 313 | - | |
| 314 | - wp_localize_script( | |
| 315 | - 'desktop-mode-recycle-bin', | |
| 316 | - 'openStationRecycleBinConfig', | |
| 317 | - array( | |
| 318 | - 'restNonce' => wp_create_nonce( 'wp_rest' ), | |
| 319 | - 'listUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin' ) ), | |
| 320 | - 'restoreUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin/restore' ) ), | |
| 321 | - 'purgeUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin/purge' ) ), | |
| 322 | - 'emptyUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin/empty' ) ), | |
| 323 | - 'countUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin/count' ) ), | |
| 324 | - 'postTypes' => openstation_recycle_bin_capture_post_types(), | |
| 325 | - ) | |
| 326 | - ); | |
| 327 | - | |
| 328 | 173 | wp_enqueue_style( 'desktop-mode-recycle-bin' ); |
| 329 | 174 | } |
| 330 | -add_action( 'admin_enqueue_scripts', 'openstation_recycle_bin_localize_config', 30 ); | |
| 175 | +add_action( 'admin_enqueue_scripts', 'openstation_recycle_bin_enqueue_style', 5 ); | |
| 331 | 176 | |
| 332 | 177 | /** |
| 333 | 178 | * Inject the initial trash count and both bin drawings into the |
| 334 | 179 | * shell config, so the dock tile (and any icon a user or plugin has |