Files
puter/src/gui/vitest.config.js
T
Nariman Jelveh 14900514b9 Dashboard: drag-to-reorder apps in the Apps tab (#3401)
* feat(dashboard): drag-to-reorder apps in the Apps tab

Let users arrange their My Apps tiles in any order via drag-and-drop.
The order is stored per-user in puter.kv (`dashboard_apps_order`) as a
list of app names and re-applied on load; apps installed since the order
was saved are appended, and uninstalled ones are dropped.

Interaction:
- Mouse/pen: drag past a small threshold to reorder; a plain click still
  opens the app.
- Touch: long-press to pick up a tile (a quick swipe still flips pages).
- Dragging to a scroller edge dwells, then flips to the adjacent page so
  tiles can move across pages; the floating drag image lives on <body> so
  the pager's overflow can't clip it, and displaced tiles reflow with a
  FLIP animation.
- Escape (or losing the window) cancels and reverts; reordering is
  disabled while a search filter is active; prefers-reduced-motion is
  honored.

Also fixes a latent pager bug where programmatic page changes (arrows,
dots, wheel) didn't update the active page indicator, because those
scrolls don't reliably emit 'scroll' events; goToPage now tracks the
page eagerly.

The order-reconciliation logic is extracted into a pure module
(appOrder.js) with unit tests.

* fix(dashboard): ignore stray pointers and no-op drags

- Filter drag pointermove/up/cancel by pointerId so a second finger can't
  hijack or prematurely end an in-progress reorder.
- Only persist a new order when the drop actually changed it, so an
  accidental long-press or drop-in-place doesn't freeze the default
  ordering into a custom one.

* fix(dashboard): address review of app drag-to-reorder

Correctness:
- Preserve the touch long-press → Uninstall path. A long-press now *arms*
  reordering instead of immediately grabbing the tile; moving begins the
  drag, while holding still lets the native context menu fire (which
  cancels the pending pickup). This restores the only touch route to
  Uninstall that the first cut had removed.
- Only swallow the post-drag click when the drop actually reordered
  something, so a small pointer drift or a drop-in-place still opens the
  app like a plain click.
- Guard the pre-start "intent" window: cancel a pending pickup before any
  re-render (ResizeObserver / loadApps), and bail out of _beginDrag if the
  pressed tile was detached, so a layout change mid-press can't reinsert a
  stale node and persist a corrupted order.

Cleanup:
- Reuse reconcileAppOrder for the drop path instead of a duplicate helper.
- Read the saved order in parallel with the app-list fetches.
- Cache the reduced-motion MediaQueryList instead of re-querying per move.
- Fold the touch-scroll cancel into _endDrag(false); drop _abortDragIntent.

* feat(dashboard): smoother, more tolerant app reordering

Make drag-to-reorder feel like the iOS home screen instead of snapping
and flickering:

- Slow the reflow to 320ms with a gentle spring easing.
- Hit-test against each tile's *resting* box, not its live
  getBoundingClientRect. Mid-animation a tile sits between slots, so
  testing its live box was dropping it under the pointer and swapping it
  straight back — the source of the back-and-forth jitter.
- Require the dragged icon's centre to be well inside a tile before it
  becomes the drop target (a deadzone around each tile), so hovering a
  boundary no longer thrashes.
- Probe with the dragged icon's centre rather than the fingertip, and
  batch the FLIP into a single reflow so large grids stay smooth.
2026-07-17 07:57:05 -07:00

35 lines
1.2 KiB
JavaScript

/*
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { defineConfig } from 'vitest/config';
// Pin the root to this directory so the include glob resolves against the GUI
// package regardless of the cwd vitest is invoked from.
const guiDir = dirname(fileURLToPath(import.meta.url));
export default defineConfig({
test: {
root: guiDir,
include: ['src/**/*.test.js'],
environment: 'node',
},
});