feat: task 2.5 MapPositions (clustered + selected sources)
- src/data/devices.ts: useDevicesById() — TanStack Query (5-min stale)
returning Map<deviceId, Device>. deviceLabel() formats human-readable
titles ("FMB920 #3458").
- src/map/layers/map-positions.tsx: <MapPositions /> side-effect-only.
Two GeoJSON sources (clustered non-selected + unclustered selected).
Five layers: non-selected symbol + direction + cluster-bubble,
selected symbol + direction. Click handlers: marker -> selectDevice,
cluster -> getClusterExpansionZoom + easeTo. Hover toggles cursor.
- src/routes/_authed/monitor.tsx: renders <MapPositions /> inside
<MapView>.
Schema overhaul in src/auth/client.ts:
- Made Schema SDK-compatible. Each entry is an *array* of row types
(devices: DeviceRow[], not just DeviceRow). RegularCollections<Schema>
filters on array-like values; non-arrays collapse to never which
broke readItems('devices', ...) with keyof Schema = never.
- Spelled out the composed client type as DirectusClient<Schema> &
RestClient<Schema> & AuthenticationClient<Schema> — without the
explicit annotation Schema didn't flow through .with(...) chain to
request() call sites.
- Added DeviceRow + EventRow types; exported via @/auth.
useDevicesById uses readItems<Schema, 'devices', Query<Schema, DeviceRow>>
— explicit generics because the SDK doesn't infer Schema from the
receiver's type at call sites.
Deviations:
1. Spec referenced device.kind for category — Phase 1 schema doesn't
have it yet; everything maps to 'default'. Refine when kind lands.
2. Cluster bubble uses 'default-neutral' sprite instead of a dedicated
'cluster-background' (not in 2.3's registry). Swap in 3.8.
3. getClusterExpansionZoom is Promise-based in maplibre-gl 5.x (was
callback-style); used .then().
Bundle: main bundle 394KB / 120KB gz, ~1KB up from 2.4. /monitor
chunk includes the new layer module (~10KB).
This commit is contained in:
+53
-8
@@ -1,4 +1,11 @@
|
||||
import { authentication, createDirectus, rest } from '@directus/sdk';
|
||||
import {
|
||||
authentication,
|
||||
createDirectus,
|
||||
rest,
|
||||
type AuthenticationClient,
|
||||
type DirectusClient as SdkDirectusClient,
|
||||
type RestClient,
|
||||
} from '@directus/sdk';
|
||||
import { useRuntimeConfig } from '@/config/context';
|
||||
|
||||
/**
|
||||
@@ -16,14 +23,52 @@ export type DirectusUser = {
|
||||
/**
|
||||
* Typed Directus collection schema for the SDK.
|
||||
*
|
||||
* Will grow as the SPA starts reading TRM-specific collections in Phase 2+.
|
||||
* Empty for now — Phase 1 only needs `/auth/*` and `/users/me`, which the
|
||||
* SDK exposes outside the schema.
|
||||
* Grows as the SPA starts reading TRM-specific collections. The shapes
|
||||
* here intentionally cover only the fields the SPA actually consumes —
|
||||
* a thin subset of the full Directus schema, kept minimal so the SDK's
|
||||
* `readItems` calls compile without dragging in fields we don't use.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export type Schema = {};
|
||||
export type DeviceRow = {
|
||||
id: string;
|
||||
imei: string;
|
||||
model: string | null;
|
||||
serial_number: string | null;
|
||||
notes: string | null;
|
||||
date_created: string | null;
|
||||
date_updated: string | null;
|
||||
};
|
||||
|
||||
type DirectusClient = ReturnType<typeof buildClient>;
|
||||
export type EventRow = {
|
||||
id: string;
|
||||
organization_id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
discipline: 'rally' | 'time-trial' | 'regatta' | 'trail-run' | 'hike';
|
||||
starts_at: string;
|
||||
ends_at: string;
|
||||
regulation_doc_url: string | null;
|
||||
notes: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Directus SDK Schema. Each entry is an *array* of row types — that's
|
||||
* what `RegularCollections<Schema>` filters on; a non-array value
|
||||
* collapses to `never` and `readItems('devices', ...)` complains that
|
||||
* `keyof Schema = never`.
|
||||
*/
|
||||
export type Schema = {
|
||||
devices: DeviceRow[];
|
||||
events: EventRow[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Composed client type. Without the explicit annotation TS sometimes
|
||||
* loses the `Schema` parameter through the chained `.with(...)` calls,
|
||||
* which makes `readItems('devices', ...)` think `keyof Schema = never`.
|
||||
* Spelling out the intersection here makes Schema flow through to every
|
||||
* `directus.request(...)` call site.
|
||||
*/
|
||||
type DirectusClient = SdkDirectusClient<Schema> & RestClient<Schema> & AuthenticationClient<Schema>;
|
||||
|
||||
/**
|
||||
* Resolve a (possibly relative) URL against the current page origin.
|
||||
@@ -40,7 +85,7 @@ function toAbsoluteUrl(maybeRelative: string): string {
|
||||
return new URL(maybeRelative, window.location.origin).toString();
|
||||
}
|
||||
|
||||
function buildClient(directusUrl: string) {
|
||||
function buildClient(directusUrl: string): DirectusClient {
|
||||
return createDirectus<Schema>(toAbsoluteUrl(directusUrl))
|
||||
.with(rest({ credentials: 'include' }))
|
||||
.with(authentication('session', { credentials: 'include' }));
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
export { AuthBootstrap } from './bootstrap';
|
||||
export { getDirectus, initDirectusClient, useDirectus } from './client';
|
||||
export type { DirectusUser, Schema } from './client';
|
||||
export type { DeviceRow, DirectusUser, EventRow, Schema } from './client';
|
||||
export { startCrossTabSync } from './cross-tab-sync';
|
||||
export { useRequireAuth, useRequireRole } from './guard';
|
||||
export { performLogout } from './logout';
|
||||
|
||||
Reference in New Issue
Block a user