PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.10
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.10
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
← All changes | includes/recycle-bin/window.php +152 -208 0.9.31.1.10 View file →
@@ -1,259 +1,203 @@
1 1 <?php
2 2 /**
3 - * Desktop Mode — Recycle Bin: window + icon registration.
3 + * OpenStation — Recycle Bin: tile art, gate, shell config.
4 4 *
5 - * Native window with id `desktop-mode-recycle-bin`, pinned to the taskbar with a
6 - * matching wallpaper icon. Like the code editor, the template body is a
7 - * static skeleton that the JS bundle enhances on first open — the table
8 - * is populated from the 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 - * Both registrations are filterable via the standard
11 - * `desktop_mode_recycle_bin_window_args` / `desktop_mode_recycle_bin_icon_args`
12 - * filters so a plugin can swap the icon, change the dimensions, or
13 - * restrict who sees the bin without touching this file.
14 - *
15 - * @package WPDesktopMode
16 - * @since 0.6.0
16 + * @package OpenStation
17 17 */
18 18
19 19 defined( 'ABSPATH' ) || exit;
20 20
21 21 /**
22 - * Echoes the recycle bin window's template body.
22 + * The bin SVG, used by the app window's icon and its dock tile.
23 23 *
24 - * The shell wraps this in `<template id="desktop-mode-native-window-desktop-mode-recycle-bin">`
25 - * and clones it into the window body BEFORE the JS render callback runs.
26 - * The `data-desktop-mode-recycle-bin-*` hooks below are the contract the JS
27 - * relies on — keep them intact (or rename via the filter) when
28 - * customizing the layout.
24 + * The bin used to be `dashicons-trash`, which worked but wore the
25 + * wrong clothes. Dashicons are WP core's icon set: solid fills on a
26 + * 20-unit grid, tuned for admin-menu sizes. The shell's own icons are
27 + * outlined vessels on a 64-unit grid at stroke 3. Sitting next to
28 + * WP Explorer, Corkboard and Games in the dock, the Dashicon was
29 + * visibly a guest from another system: heavier, tighter, and drawn to
30 + * a different rhythm.
29 31 *
30 - * @since 0.6.0
32 + * So this is the same object, redrawn to the house rule the other
33 + * three follow: an outlined vessel with solid content, three elements
34 + * because it renders as small as 20px in the dock. The lid is the
35 + * solid one, which gives the mark a single dense horizontal to be
36 + * recognised by when the tapered body below it thins out.
37 + *
38 + * Drawn in `currentColor`, so `renderIcon()` paints it as a CSS mask
39 + * and it takes the surface's own text colour. Dashicons already
40 + * inherited colour, being font glyphs; the point of the change is the
41 + * drawing, not the theming.
42 + *
43 + * Note that the row actions inside the bin window, and the "Move to
44 + * trash" entries in context menus, stay on `dashicons-trash`. Those
45 + * are menu glyphs sitting among other menu glyphs, and they should
46 + * match their neighbours rather than this icon.
47 + *
48 + * The bin has two states. Empty is the vessel on its own; full adds
49 + * three crumpled balls inside it and knocks the lid askew.
50 + *
51 + * @param bool $full Whether to draw the bin holding something.
52 + * @return string Raw `<svg>` markup.
31 53 */
32 -function desktop_mode_recycle_bin_render_template() {
33 - ob_start();
34 - ?>
35 - <div class="desktop-mode-recycle-bin" data-desktop-mode-recycle-bin-root>
36 - <header class="desktop-mode-recycle-bin__toolbar" data-desktop-mode-recycle-bin-toolbar>
37 - <div class="desktop-mode-recycle-bin__toolbar-left">
38 - <wpd-segmented data-desktop-mode-recycle-bin-filter>
39 - <wpd-segment value="" selected><?php esc_html_e( 'All', 'desktop-mode' ); ?></wpd-segment>
40 - <wpd-segment value="post"><?php esc_html_e( 'Posts', 'desktop-mode' ); ?></wpd-segment>
41 - <wpd-segment value="page"><?php esc_html_e( 'Pages', 'desktop-mode' ); ?></wpd-segment>
42 - <?php
43 - // The Media segment is only useful when WP itself routes
44 - // attachment deletions through Trash. That gate is the
45 - // `MEDIA_TRASH` constant — defaults to false in core, can
46 - // be flipped to true from `wp-config.php`. Without it,
47 - // attachments permanent-delete on first click and the
48 - // Trash bin will never have anything in this bucket, so
49 - // the tab would always read "0" and confuse users.
50 - if ( defined( 'MEDIA_TRASH' ) && MEDIA_TRASH ) :
51 - ?>
52 - <wpd-segment value="attachment"><?php esc_html_e( 'Media', 'desktop-mode' ); ?></wpd-segment>
53 - <?php
54 - endif;
55 - ?>
56 - <wpd-segment value="comment"><?php esc_html_e( 'Comments', 'desktop-mode' ); ?></wpd-segment>
57 - <wpd-segment value="desktop"><?php esc_html_e( 'Desktop', 'desktop-mode' ); ?></wpd-segment>
58 - </wpd-segmented>
59 - <wpd-text-field
60 - data-desktop-mode-recycle-bin-search
61 - placeholder="<?php esc_attr_e( 'Search trash…', 'desktop-mode' ); ?>"
62 - ></wpd-text-field>
63 - </div>
64 - <div class="desktop-mode-recycle-bin__toolbar-right" data-desktop-mode-recycle-bin-bulk hidden>
65 - <span class="desktop-mode-recycle-bin__count" data-desktop-mode-recycle-bin-count></span>
66 - <wpd-button variant="secondary" data-desktop-mode-recycle-bin-restore-selected>
67 - <span class="dashicons dashicons-image-rotate" aria-hidden="true"></span>
68 - <?php esc_html_e( 'Restore', 'desktop-mode' ); ?>
69 - </wpd-button>
70 - <wpd-button variant="secondary" data-desktop-mode-recycle-bin-pin-to-desktop>
71 - <span class="dashicons dashicons-desktop" aria-hidden="true"></span>
72 - <?php esc_html_e( 'Pin to desktop', 'desktop-mode' ); ?>
73 - </wpd-button>
74 - <wpd-button variant="danger" data-desktop-mode-recycle-bin-purge-selected>
75 - <span class="dashicons dashicons-trash" aria-hidden="true"></span>
76 - <?php esc_html_e( 'Delete forever', 'desktop-mode' ); ?>
77 - </wpd-button>
78 - </div>
79 - <div class="desktop-mode-recycle-bin__toolbar-trailing">
80 - <wpd-button variant="ghost" data-desktop-mode-recycle-bin-refresh title="<?php esc_attr_e( 'Refresh', 'desktop-mode' ); ?>">
81 - <span class="dashicons dashicons-update" aria-hidden="true"></span>
82 - </wpd-button>
83 - <wpd-button variant="danger" data-desktop-mode-recycle-bin-empty>
84 - <span class="dashicons dashicons-trash" aria-hidden="true"></span>
85 - <?php esc_html_e( 'Empty bin', 'desktop-mode' ); ?>
86 - </wpd-button>
87 - </div>
88 - </header>
89 - <div class="desktop-mode-recycle-bin__body" data-desktop-mode-recycle-bin-body>
90 - <wpd-table
91 - data-desktop-mode-recycle-bin-table
92 - selectable="multi"
93 - sticky-header
94 - hover
95 - striped
96 - loading
97 - >
98 - <div slot="empty" class="desktop-mode-recycle-bin__empty">
99 - <span class="dashicons dashicons-trash" aria-hidden="true"></span>
100 - <p><?php esc_html_e( 'The recycle bin is empty.', 'desktop-mode' ); ?></p>
101 - <p class="desktop-mode-recycle-bin__empty-hint">
102 - <?php esc_html_e( 'Deleted posts, pages, and media show up here. Restoring puts them back where they were.', 'desktop-mode' ); ?>
103 - </p>
104 - </div>
105 - </wpd-table>
106 - </div>
107 - </div>
108 - <?php
109 - $html = (string) ob_get_clean();
54 +function openstation_recycle_bin_icon_svg( $full = false ) {
55 + // The lid and the handle travel together. In the full state the
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.
66 + $lid_transform = $full
67 + ? ' transform="translate(0 -4) rotate(12 52 21.5)"'
68 + : '';
110 69
111 - /**
112 - * Filter the recycle bin window's template HTML.
113 - *
114 - * Keep the `data-desktop-mode-recycle-bin-*` hooks intact so the JS render
115 - * callback can find its mount points, or rename them and update the
116 - * matching constants in `src/recycle-bin/index.ts`.
117 - *
118 - * @since 0.6.0
119 - *
120 - * @param string $html Default template HTML.
121 - */
122 - $filtered = (string) apply_filters( 'desktop_mode_recycle_bin_template_html', $html );
123 - echo wp_kses( $filtered, desktop_mode_native_window_allowed_html() );
70 + $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">'
71 + . '<g' . $lid_transform . '>'
72 + // The handle, outlined so it reads as a loop rather than a tab.
73 + . '<path d="M25 19v-2.5a3.5 3.5 0 0 1 3.5-3.5h7a3.5 3.5 0 0 1 3.5 3.5V19" fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"/>'
74 + // The lid: the one solid element, and the widest, so it anchors
75 + // the mark at small sizes.
76 + . '<rect x="10" y="19" width="44" height="5" rx="2.5" fill="currentColor"/>'
77 + . '</g>'
78 + // The body, tapered towards the base the way a real bin is, which
79 + // is also what separates it from a plain bucket.
80 + //
81 + // 27 units tall, narrowing to 71% of its top width. It was 24
82 + // tall at 59%, which drew a shallower, more conical tub than a
83 + // bin actually is, and left an interior too cramped to hold
84 + // anything. Taken further, to a bottom much past 75%, the walls
85 + // go vertical and the mark reads as a bucket; this sits at the
86 + // edge of that.
87 + . '<path d="M15.5 28.5h33l-1.2 24a3.5 3.5 0 0 1-3.5 3H20.2a3.5 3.5 0 0 1-3.5-3z" fill="none" stroke="currentColor" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"/>';
88 +
89 + if ( $full ) {
90 + // Three crumpled balls, one path with three subpaths so the
91 + // mark stays at four elements rather than six.
92 + //
93 + // Each is a seven-point polygon on a radius jittered between
94 + // 0.81 and 1.0, with a same-colour round-joined stroke that
95 + // turns the corners into creases instead of points. Seven
96 + // points rather than nine because a ball this small needs
97 + // deeper, fewer facets to keep any texture at all.
98 + //
99 + // The layout is staggered deliberately. Two balls at the same
100 + // height with a third centred under them reads as a face, and
101 + // it cannot be unseen once noticed, so no two share a y (the
102 + // top pair are 4.2 units apart) and the third sits below-left
103 + // rather than on the centreline. Every gap in the mark clears
104 + // 2 units: 5.1 to the left wall, 4.4 to the right, 2.8 to the
105 + // rim, 2.5 to the base, and 2.5 to 4.2 between the balls. Those
106 + // last three decide how far down the size ladder they stay
107 + // three things instead of one.
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'
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'
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"'
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"/>';
120 + }
121 +
122 + return $svg . '</svg>';
124 123 }
125 124
126 125 /**
126 + * Both bin states as base64 data URIs, ready for `renderIcon()`.
127 + *
128 + * The client swaps between these as the count crosses zero, so both
129 + * have to reach the page on the first paint. Cheap: two string
130 + * builds and two base64 encodes, no queries.
131 + *
132 + * @return array{empty:string,full:string}
133 + */
134 +function openstation_recycle_bin_icon_uris() {
135 + return array(
136 + 'empty' => 'data:image/svg+xml;base64,' . base64_encode( openstation_recycle_bin_icon_svg( false ) ),
137 + 'full' => 'data:image/svg+xml;base64,' . base64_encode( openstation_recycle_bin_icon_svg( true ) ),
138 + );
139 +}
140 +
141 +/**
127 142 * Whether the current user should see the recycle bin at all.
128 143 *
129 144 * Filterable so plugins can hide it from authors/contributors who
130 145 * don't manage trash, or invert the gate to expose it to a custom
131 - * role.
146 + * role. The app's gate (`apps/trash/trash.os.php`) delegates here.
132 147 *
133 - * @since 0.6.0
134 - *
135 148 * @return bool
136 149 */
137 -function desktop_mode_recycle_bin_user_can_use() {
150 +function openstation_recycle_bin_user_can_use() {
138 151 $can = current_user_can( 'edit_posts' );
139 152
140 153 /**
141 154 * Filter whether the current user can see the recycle bin window.
142 155 *
143 - * @since 0.6.0
144 - *
145 156 * @param bool $can Default: edit_posts capability.
146 157 */
147 - return (bool) apply_filters( 'desktop_mode_recycle_bin_user_can_use', $can );
158 + return (bool) apply_filters( 'openstation_recycle_bin_user_can_use', $can );
148 159 }
149 160
150 161 /**
151 - * Register the recycle bin window + desktop icon on `init`.
162 + * Enqueue the bin stylesheet with the shell.
152 163 *
153 - * Hooked at priority 20, after `components.php` has bootstrapped the
154 - * native-window registry — same timing as the code editor.
155 - *
156 - * @since 0.6.0
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.
157 168 */
158 -function desktop_mode_recycle_bin_register_window() {
159 - if ( ! desktop_mode_recycle_bin_user_can_use() ) {
169 +function openstation_recycle_bin_enqueue_style() {
170 + if ( ! openstation_recycle_bin_user_can_use() ) {
160 171 return;
161 172 }
162 -
163 - $window_args = array(
164 - 'title' => __( 'Recycle Bin', 'desktop-mode' ),
165 - 'icon' => 'dashicons-trash',
166 - 'template' => 'desktop_mode_recycle_bin_render_template',
167 - 'script' => 'desktop-mode-recycle-bin',
168 - 'width' => 880,
169 - 'height' => 560,
170 - 'min_width' => 520,
171 - 'min_height' => 360,
172 - 'placement' => 'taskbar',
173 - );
174 -
175 - /**
176 - * Filter the args used to register the recycle bin native window.
177 - *
178 - * @since 0.6.0
179 - *
180 - * @param array $window_args Args passed to `desktop_mode_register_window()`.
181 - */
182 - $window_args = (array) apply_filters( 'desktop_mode_recycle_bin_window_args', $window_args );
183 -
184 - $registered = desktop_mode_register_window( 'desktop-mode-recycle-bin', $window_args );
185 - if ( is_wp_error( $registered ) ) {
186 - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
187 - error_log( '[desktop-mode] Recycle bin window registration failed: ' . $registered->get_error_message() );
188 - return;
189 - }
190 -
191 - $icon_args = array(
192 - 'title' => __( 'Recycle Bin', 'desktop-mode' ),
193 - 'icon' => 'dashicons-trash',
194 - 'window' => 'desktop-mode-recycle-bin',
195 - 'position' => 80,
196 - );
197 -
198 - /**
199 - * Filter the args used to register the recycle bin desktop icon.
200 - *
201 - * @since 0.6.0
202 - *
203 - * @param array $icon_args Args passed to `desktop_mode_register_icon()`.
204 - */
205 - $icon_args = (array) apply_filters( 'desktop_mode_recycle_bin_icon_args', $icon_args );
206 -
207 - desktop_mode_register_icon( 'desktop-mode-recycle-bin', $icon_args );
208 -}
209 -add_action( 'init', 'desktop_mode_recycle_bin_register_window', 20 );
210 -
211 -/**
212 - * Localize REST endpoints for the JS bundle.
213 - *
214 - * Same pattern as the code editor: the bundle reads its config off
215 - * `window.desktopModeRecycleBinConfig` and never hardcodes URLs.
216 - *
217 - * @since 0.6.0
218 - */
219 -function desktop_mode_recycle_bin_localize_config() {
220 - if ( ! desktop_mode_recycle_bin_user_can_use() ) {
221 - return;
222 - }
223 -
224 - wp_localize_script(
225 - 'desktop-mode-recycle-bin',
226 - 'desktopModeRecycleBinConfig',
227 - array(
228 - 'restNonce' => wp_create_nonce( 'wp_rest' ),
229 - 'listUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin' ) ),
230 - 'restoreUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin/restore' ) ),
231 - 'purgeUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin/purge' ) ),
232 - 'emptyUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin/empty' ) ),
233 - 'countUrl' => esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin/count' ) ),
234 - )
235 - );
236 -
237 173 wp_enqueue_style( 'desktop-mode-recycle-bin' );
238 174 }
239 -add_action( 'admin_enqueue_scripts', 'desktop_mode_recycle_bin_localize_config', 30 );
175 +add_action( 'admin_enqueue_scripts', 'openstation_recycle_bin_enqueue_style', 5 );
240 176
241 177 /**
242 - * Inject the initial trash count into the shell config so the
243 - * dock/taskbar tile + desktop icon can paint a badge on the very
244 - * first paint — before the bin window has ever opened.
178 + * Inject the initial trash count and both bin drawings into the
179 + * shell config, so the dock tile (and any icon a user or plugin has
180 + * placed against this window) shows the right one on the very first
181 + * paint, before the bin window has ever opened.
245 182 *
246 - * @since 0.6.0
183 + * Both drawings travel together rather than the server picking one:
184 + * the count changes without a reload, and shipping the pair makes
185 + * crossing zero a local swap instead of a round trip.
247 186 *
248 187 * @param array $config Shell config blob.
249 188 * @return array
250 189 */
251 -function desktop_mode_recycle_bin_inject_shell_config( $config ) {
190 +function openstation_recycle_bin_inject_shell_config( $config ) {
252 191 if ( ! is_array( $config ) ) {
253 192 return $config;
254 193 }
255 - $config['recycleBinCount'] = desktop_mode_recycle_bin_count();
256 - $config['recycleBinCountUrl'] = esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin/count' ) );
194 + $icons = openstation_recycle_bin_icon_uris();
195 +
196 + $config['recycleBinCount'] = openstation_recycle_bin_count();
197 + $config['recycleBinCountUrl'] = esc_url_raw( rest_url( 'desktop-mode/v1/recycle-bin/count' ) );
198 + $config['recycleBinPostTypes'] = openstation_recycle_bin_capture_post_types();
199 + $config['recycleBinIconEmpty'] = $icons['empty'];
200 + $config['recycleBinIconFull'] = $icons['full'];
257 201 return $config;
258 202 }
259 -add_filter( 'desktop_mode_shell_config', 'desktop_mode_recycle_bin_inject_shell_config', 20 );
203 +add_filter( 'openstation_shell_config', 'openstation_recycle_bin_inject_shell_config', 20 );