PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.0
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.0
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 / mio.php

mio.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.0, at includes/mio.php

301 lines 10.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — Mio.
4 *
5 * Server side of the desk companion: the appearance / physics
6 * defaults shipped to the shell, and the filter plugins use to
7 * restyle or re-tune it.
8 *
9 * Mio itself is a lazy JS bundle (`assets/js/mio[.min].js`)
10 * that the shell injects the first time a user switches it on from
11 * the wallpaper context menu. Nothing here enqueues anything — the
12 * bundle URL travels in the shell config as `mioBundleUrl`, and
13 * the on/off preference lives in OS Settings as `mioEnabled`.
14 *
15 * Every value is re-clamped client-side in
16 * `src/mio/config.ts::sanitizeMioConfig()`, so a filter that
17 * returns nonsense produces a plain-looking Mio rather than a
18 * broken shell.
19 *
20 * @package OpenStation
21 */
22
23 defined( 'ABSPATH' ) || exit;
24
25 /**
26 * Returns Mio configuration for the current user.
27 *
28 * Shape mirrors `MioConfig` in `src/mio/types.ts`:
29 *
30 * array(
31 * 'appearance' => array( radius, bodyColor, bodyAlpha, hueStart,
32 * hueSpan, hueDrift, hueLoop, hueAngle,
33 * saturation, lightness, iridescence,
34 * outlineWidth, glow, glowBlur,
35 * eyeColor, eyeScale ),
36 * 'physics' => array( points, shapePreset, shapeLobes,
37 * shapeAmount, shapeAngle, shapeShuffle,
38 * radialStiffness, edgeStiffness,
39 * bendStiffness, pressure, damping,
40 * airDamping, magnetStrength, magnetRange,
41 * magnetGrip, magnetDamping, floatAmplitude,
42 * floatSpeed, idleWobble, idleWobbleSpeed,
43 * speedStretch, friction, restitution,
44 * dragStiffness, throwBoost, minStretch,
45 * maxStretch, minAngularGap,
46 * limitIterations, dragMaxAccel, subStep,
47 * maxSubSteps ),
48 * )
49 *
50 * Colours may be given as integers (`0x05050a`) or CSS hex strings
51 * (`'#05050a'`); the client accepts both.
52 *
53 * @return array Mio configuration.
54 */
55 function openstation_mio_config() {
56 $defaults = array(
57 'appearance' => array(
58 'radius' => 56,
59 // Void, the palette's base. The brand's own Mio is
60 // `fill="none"` over the Void page; the shell floats over
61 // whatever wallpaper the user picked, so it fills the body
62 // with the colour that background is. Not '#000000', which
63 // is not in the palette.
64 'bodyColor' => '#0c0b0f',
65 'bodyAlpha' => 1,
66 // Read off Miomesh, Mio's own gradient in the OpenStation
67 // brand guidelines: four stops from #F252FC (Pulse, hue
68 // 296.5) through #AA67FF and #A580FF to #4B3EFF (hue 244).
69 // `hueAngle` pins Pulse where `mioGrad` starts, on the
70 // upper-left shoulder — 225 degrees clockwise from 3
71 // o'clock.
72 'hueStart' => 296.5,
73 'hueSpan' => -52.5,
74 'hueAngle' => 225,
75 // The official Mio holds still; hueLoop is what lets it,
76 // by walking the span out and back so the ring meets
77 // itself instead of ending a span away with a visible seam.
78 // Two kinds of still. hueDrift rewrites the hues, so Mio
79 // cycles through colours that are not its own — the one
80 // thing the official palette must never do. hueSpin turns
81 // the same sweep around the ring, keeping the palette, and
82 // is the most a default Mio should ever animate.
83 'hueDrift' => 0,
84 'hueSpin' => 0,
85 'hueLoop' => true,
86 'saturation' => 1,
87 // The ring's brightest point, not its average — the
88 // renderer rides a cosine hump from 0.72x to 1x over this.
89 // Miomesh's brightest stop, #A580FF, is 0.751.
90 'lightness' => 0.75,
91 // The official artwork has no hologram and no interior
92 // sheen — a flat gradient over dead black. One number here
93 // turns both back on for a whole site.
94 'iridescence' => 0,
95 'outlineWidth' => 3,
96 // Reach of the light, as a multiple of Mio's own radius:
97 // `10` carries the wash about one and a half radii past the
98 // outline. Deliberately generous — Mio sits on a dark desk
99 // and the glow is the thing that makes her read as lit
100 // rather than drawn. The slider runs to `20`.
101 //
102 // Must match `MIO_DEFAULTS` in `src/mio/config.ts`; this is
103 // the value the shell renders before a user has a look of
104 // their own, and the two disagreeing means Mio changes
105 // appearance the first time anything is saved.
106 'glow' => 10,
107 // No UI switches this off. Each glow pass is a ramp of
108 // concentric shells, and unblurred that ramp shows as the
109 // contour rings it is built from. It is here so a site that
110 // needs the two filter passes back for performance can drop
111 // them.
112 'glowBlur' => true,
113 // Starlight, the palette's white — what the brand's mascot
114 // fills its two eye pills with. Not '#ffffff'.
115 'eyeColor' => '#fffbff',
116 'eyeScale' => 0.3,
117 ),
118 'physics' => array(
119 'points' => 12,
120 // Silhouette: 'circle', 'blob', 'ghost', 'potato' or
121 // 'custom'. Nearly round, with a shallow dimple at the
122 // bottom centre.
123 'shapePreset' => 'blob',
124 // Only read by the 'custom' preset.
125 'shapeLobes' => 3,
126 'shapeAmount' => 1,
127 'shapeAngle' => 0,
128 // Seconds between Mio picking a new silhouette at
129 // random and morphing into it. 0 holds shapePreset.
130 'shapeShuffle' => 60,
131 'radialStiffness' => 460,
132 'edgeStiffness' => 540,
133 'bendStiffness' => 170,
134 'pressure' => 2400,
135 'damping' => 9,
136 'airDamping' => 0.5,
137 'magnetStrength' => 2200,
138 'magnetRange' => 260,
139 'magnetGrip' => 0.24,
140 'magnetDamping' => 7,
141 'floatAmplitude' => 10,
142 'floatSpeed' => 1.1,
143 'idleWobble' => 0.085,
144 'idleWobbleSpeed' => 0.55,
145 'speedStretch' => 0.3,
146 'friction' => 0.86,
147 'restitution' => 0.2,
148 'dragStiffness' => 480,
149 'throwBoost' => 1,
150 'minStretch' => 0.55,
151 'maxStretch' => 1.7,
152 'minAngularGap' => 0.25,
153 'limitIterations' => 3,
154 'dragMaxAccel' => 9000,
155 'subStep' => 1 / 240,
156 'maxSubSteps' => 8,
157 ),
158 );
159
160 /**
161 * Filters Mio's appearance and physics.
162 *
163 * Runs once per shell render. Returning a partial array is fine —
164 * anything missing falls back to the reference design, and every
165 * value is clamped client-side before it reaches the simulation.
166 *
167 * Example — a slower, heavier, teal mio:
168 *
169 * add_filter( 'openstation_mio_config', function ( $config ) {
170 * $config['appearance']['hueStart'] = 170;
171 * $config['appearance']['hueSpan'] = 40;
172 * $config['physics']['magnetStrength'] = 3400;
173 * return $config;
174 * } );
175 *
176 * @param array $defaults Default configuration, as documented above.
177 */
178 $config = apply_filters( 'openstation_mio_config', $defaults );
179
180 return is_array( $config ) ? $config : $defaults;
181 }
182
183 /**
184 * Appearance keys a stored user look may carry.
185 *
186 * Mirrors `APPEARANCE_KEYS` in `src/mio/look.ts`. A whitelist rather
187 * than "whatever the client sent", because this lands in user meta:
188 * an unbounded key set is an unbounded row.
189 *
190 * @return string[]
191 */
192 function openstation_mio_look_appearance_keys() {
193 return array(
194 'radius',
195 'bodyColor',
196 'bodyAlpha',
197 'hueStart',
198 'hueSpan',
199 'hueDrift',
200 'hueLoop',
201 'hueAngle',
202 'hueSpin',
203 'saturation',
204 'lightness',
205 'iridescence',
206 'outlineWidth',
207 'glow',
208 'glowBlur',
209 'eyeColor',
210 'eyeScale',
211 );
212 }
213
214 /**
215 * Physics keys a stored user look may carry.
216 *
217 * Mirrors `LOOK_PHYSICS_KEYS` in `src/mio/look.ts`. Every one of them
218 * modulates a rest length. The spring constants are deliberately
219 * absent: they are the site's, they interact, and a stored preference
220 * that could reach them would be a way for a corrupt row to make Mio
221 * unstable.
222 *
223 * @return string[]
224 */
225 function openstation_mio_look_physics_keys() {
226 return array(
227 'shapePreset',
228 'shapeLobes',
229 'shapeAmount',
230 'shapeAngle',
231 'shapeShuffle',
232 'idleWobble',
233 'idleWobbleSpeed',
234 );
235 }
236
237 /**
238 * Sanitizes a user's saved Mio look for storage in user meta.
239 *
240 * **A shape check, not a clamp.** It answers "are these the right keys
241 * carrying the right kinds of value" and nothing more. Deciding what a
242 * legal hue, silhouette or spring constant is stays with
243 * `sanitizeMioConfig()` in `src/mio/config.ts`, which runs on
244 * everything headed for the simulation whatever route it arrived by.
245 * Two validators with overlapping opinions about ranges is how ranges
246 * drift apart.
247 *
248 * Only the keys the user actually changed are kept, so a site that
249 * later ships a different Mio still shows through everywhere its users
250 * have no opinion.
251 *
252 * @param mixed $raw Raw look from the client or user meta.
253 * @return array {
254 * @type array $appearance Partial appearance overrides.
255 * @type array $physics Partial silhouette + idle overrides.
256 * }
257 */
258 function openstation_sanitize_mio_look( $raw ) {
259 $clean = array(
260 'appearance' => array(),
261 'physics' => array(),
262 );
263
264 if ( ! is_array( $raw ) ) {
265 return $clean;
266 }
267
268 $groups = array(
269 'appearance' => openstation_mio_look_appearance_keys(),
270 'physics' => openstation_mio_look_physics_keys(),
271 );
272
273 foreach ( $groups as $group => $keys ) {
274 if ( ! isset( $raw[ $group ] ) || ! is_array( $raw[ $group ] ) ) {
275 continue;
276 }
277 foreach ( $keys as $key ) {
278 if ( ! isset( $raw[ $group ][ $key ] ) ) {
279 continue;
280 }
281 $value = $raw[ $group ][ $key ];
282 if ( is_bool( $value ) ) {
283 $clean[ $group ][ $key ] = $value;
284 } elseif ( is_int( $value ) || is_float( $value ) ) {
285 // Reject non-finite floats outright: they survive JSON
286 // round-trips as `null` and would land in the blob as a
287 // key the client then has to defend against.
288 if ( is_finite( (float) $value ) ) {
289 $clean[ $group ][ $key ] = 0 + $value;
290 }
291 } elseif ( is_string( $value ) ) {
292 // The only string-valued keys are `shapePreset` and the
293 // two colours in `#rrggbb` form.
294 $clean[ $group ][ $key ] = sanitize_text_field( $value );
295 }
296 }
297 }
298
299 return $clean;
300 }
301