| @@ -118,11 +118,20 @@ | ||
| 118 | 118 | // `<div class="wpdberror">…</div>` to the REST response body and |
| 119 | 119 | // break `await response.json()` on the client. `$wpdb->last_error` |
| 120 | 120 | // still holds the message, so genuine DB failures surface via the |
| 121 | 121 | // `WP_Error` we return when no existing row is found. |
| 122 | - $prev_suppress = $wpdb->suppress_errors( true ); | |
| 123 | - $ok = $wpdb->insert( $tables['placements'], $row, array( '%d', '%d', '%d', '%s', '%s', '%d', '%d', '%d', '%d', '%s' ) ); | |
| 124 | - $wpdb->suppress_errors( $prev_suppress ); | |
| 122 | + $insert = static function () use ( $tables, $row ) { | |
| 123 | + global $wpdb; | |
| 124 | + $prev_suppress = $wpdb->suppress_errors( true ); | |
| 125 | + $ok = $wpdb->insert( $tables['placements'], $row, array( '%d', '%d', '%d', '%s', '%s', '%d', '%d', '%d', '%d', '%s' ) ); | |
| 126 | + $wpdb->suppress_errors( $prev_suppress ); | |
| 127 | + return array( $ok, (int) $wpdb->insert_id ); | |
| 128 | + }; | |
| 129 | + $result = 'upload' === $type ? openstation_stored_files_place_insert( $ref, $insert ) : $insert(); | |
| 130 | + if ( is_wp_error( $result ) ) { | |
| 131 | + return $result; | |
| 132 | + } | |
| 133 | + list( $ok, $id ) = $result; | |
| 125 | 134 | if ( false === $ok ) { |
| 126 | 135 | // Disambiguate the two cases hidden behind a generic `false`: |
| 127 | 136 | // (a) The `placement_unique` index collided |
| 128 | 137 | // with an existing row for this (user, parent, type, |
| @@ -187,10 +196,8 @@ | ||
| 187 | 196 | } |
| 188 | 197 | |
| 189 | 198 | return $existing_id; |
| 190 | 199 | } |
| 191 | - $id = (int) $wpdb->insert_id; | |
| 192 | - | |
| 193 | 200 | $row['id'] = $id; |
| 194 | 201 | |
| 195 | 202 | /** |
| 196 | 203 | * Fires after a placement is created. |
| @@ -604,10 +611,12 @@ | ||
| 604 | 611 | * on first hydrate so plugin shortcuts behave like any |
| 605 | 612 | * other tile (drag, sort, right-click, clean up). |
| 606 | 613 | * |
| 607 | 614 | * Idempotent on both axes: a folder/shortcut that already has |
| 608 | - * any placement is left alone. Coordinates use the column-major | |
| 609 | - * grid that `src/desktop-files/grid.ts` mirrors on the JS side. | |
| 615 | + * any placement is left alone. Coordinates come from | |
| 616 | + * `includes/desktop-files/grid.php`, which mirrors | |
| 617 | + * `src/desktop-files/grid.ts` — pitch, reading order, and the | |
| 618 | + * assumed canvas the scan wraps at. | |
| 610 | 619 | * |
| 611 | 620 | * Called by the placements list endpoint when the requested |
| 612 | 621 | * folder is the root (`parent_id=0`). |
| 613 | 622 | * |
| @@ -682,10 +691,11 @@ | ||
| 682 | 691 | } |
| 683 | 692 | |
| 684 | 693 | // Build an occupied set from EXISTING root placements so |
| 685 | 694 | // we never drop an orphan on top of a tile the user |
| 686 | - // already has. Cell math mirrors `src/desktop-files/grid.ts` | |
| 687 | - // (padding 16 + col 96 + row 110). | |
| 695 | + // already has. Cell math lives in | |
| 696 | + // `includes/desktop-files/grid.php`, the mirror of | |
| 697 | + // `src/desktop-files/grid.ts`. | |
| 688 | 698 | $existing = $wpdb->get_results( |
| 689 | 699 | $wpdb->prepare( |
| 690 | 700 | "SELECT x, y FROM {$tables['placements']} |
| 691 | 701 | WHERE owner_id = %d |
| @@ -694,30 +704,19 @@ | ||
| 694 | 704 | $user_id |
| 695 | 705 | ), |
| 696 | 706 | ARRAY_A |
| 697 | 707 | ); |
| 698 | - $occupied = array(); | |
| 699 | - foreach ( (array) $existing as $row ) { | |
| 700 | - $col = max( 0, (int) round( ( (int) $row['x'] - 16 ) / 96 ) ); | |
| 701 | - $row_idx = max( 0, (int) round( ( (int) $row['y'] - 16 ) / 110 ) ); | |
| 702 | - $occupied[ "$col,$row_idx" ] = true; | |
| 703 | - } | |
| 708 | + $occupied = openstation_files_grid_occupied( $existing ); | |
| 704 | 709 | |
| 705 | - $find_next = function () use ( &$occupied ) { | |
| 706 | - for ( $col = 0; $col < 999; $col++ ) { | |
| 707 | - for ( $row = 0; $row < 999; $row++ ) { | |
| 708 | - $key = "$col,$row"; | |
| 709 | - if ( ! isset( $occupied[ $key ] ) ) { | |
| 710 | - $occupied[ $key ] = true; | |
| 711 | - return array( $col, $row ); | |
| 712 | - } | |
| 713 | - } | |
| 714 | - } | |
| 715 | - return array( 0, 0 ); | |
| 716 | - }; | |
| 710 | + // The desktop reads in columns, and the scan wraps to the next one | |
| 711 | + // at the assumed canvas height rather than running a column 999 | |
| 712 | + // cells deep. The server has no viewport; a slot it invents below | |
| 713 | + // the fold is a tile the user cannot reach, because the layer that | |
| 714 | + // renders it does not scroll. | |
| 715 | + $order = openstation_files_grid_order( 0 ); | |
| 717 | 716 | |
| 718 | - $placed = 0; | |
| 719 | - $emit_at = function ( $type, $ref, $col, $row ) use ( $user_id, &$occupied, &$placed ) { | |
| 717 | + $placed = 0; | |
| 718 | + $emit_at = function ( $type, $ref, $col, $row ) use ( $user_id, &$occupied, &$placed ) { | |
| 720 | 719 | $occupied[ "$col,$row" ] = true; |
| 721 | 720 | $result = openstation_files_place( |
| 722 | 721 | $user_id, |
| 723 | 722 | 0, |
| @@ -722,19 +721,16 @@ | ||
| 722 | 721 | $user_id, |
| 723 | 722 | 0, |
| 724 | 723 | $type, |
| 725 | 724 | (string) $ref, |
| 726 | - array( | |
| 727 | - 'x' => 16 + $col * 96, | |
| 728 | - 'y' => 16 + $row * 110, | |
| 729 | - ) | |
| 725 | + openstation_files_grid_cell_to_point( $col, $row ) | |
| 730 | 726 | ); |
| 731 | 727 | if ( ! is_wp_error( $result ) ) { |
| 732 | 728 | ++$placed; |
| 733 | 729 | } |
| 734 | 730 | }; |
| 735 | - $emit_next = function ( $type, $ref ) use ( $find_next, $emit_at ) { | |
| 736 | - list( $col, $row ) = $find_next(); | |
| 731 | + $emit_next = function ( $type, $ref ) use ( &$occupied, $order, $emit_at ) { | |
| 732 | + list( $col, $row ) = openstation_files_grid_next_free( $occupied, $order ); | |
| 737 | 733 | $emit_at( $type, $ref, $col, $row ); |
| 738 | 734 | }; |
| 739 | 735 | |
| 740 | 736 | // Pinned shortcuts get reserved top-left slots. Anchored to |