@devix-tecnologia/directus-extension-mapgrid 1.5.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,38 +1,628 @@
1
1
  # Directus - MapGrid Layout Extension
2
2
 
3
- This project is a Layout-type extension for Directus, designed for viewing content in both map and grid formats simultaneously. Developed by [Devix Tecnologia](https://devix.co).
3
+ [![Build](https://img.shields.io/github/actions/workflow/status/devix-tecnologia/directus-extension-mapgrid/release.yml?branch=main)](https://github.com/devix-tecnologia/directus-extension-mapgrid/actions)
4
+ [![Version](https://img.shields.io/npm/v/@devix-tecnologia/directus-extension-mapgrid)](https://www.npmjs.com/package/@devix-tecnologia/directus-extension-mapgrid)
5
+ [![License](https://img.shields.io/npm/l/@devix-tecnologia/directus-extension-mapgrid)](https://github.com/devix-tecnologia/directus-extension-mapgrid/blob/main/License.txt)
4
6
 
5
- ## 💎 Using the Extension
7
+ A Layout-type extension for Directus that displays a collection in map and grid form at the same time. Built by [Devix Tecnologia](https://devix.co).
6
8
 
7
- - Ensure that your collection has a field of type "Map" and select the field in the Layout Options: Geolocation.
8
- - Enable the Layout in the settings menu on the right by selecting "MapGrid" from the dropdown.
9
+ ![Extension visualization screen](https://raw.githubusercontent.com/devix-tecnologia/directus-extension-mapgrid/develop/docs/tela.jpg)
9
10
 
10
- ![Extension visualization screen](https://github.com/devix-tecnologia/directus-extension-mapgrid/raw/main/docs/tela.jpg)
11
+ **🇺🇸 English** · [🇧🇷 Português](#-português)
11
12
 
12
- ## 🚀 Running Directus with Docker Compose
13
+ ---
13
14
 
14
- - Download this project or copy the `docker-compose.yml` file and start a fresh installation.
15
- - With Docker installed on your machine ([learn more](https://docs.docker.com/get-docker/)), run the command:
15
+ ## 🇺🇸 English
16
16
 
17
+ ### Features
18
+
19
+ - **Map and grid together**: the Directus map layout and the Directus table layout side by side,
20
+ over the same query and the same selection
21
+ - **Click a row, the map goes there**: the map flies to the item, keeping the current zoom — or
22
+ zooming in, with *Zoom on focus*
23
+ - **Click a marker, the row is marked**: instead of leaving the layout for the item page. The mark
24
+ is not the selection, so it does not arm the bulk actions
25
+ - **Walk the records**: first, previous, next and last, in the query's order. When the record is on
26
+ another page, the page turns first
27
+ - **Play them back**: the camera walks the records on its own, at a configurable interval. With a
28
+ vehicle tracking collection sorted by date, it drives the route in the order it happened
29
+ - **Camera tracking**: *free* leaves the map where you left it, *follow* moves only when the record
30
+ leaves the visible area, *keep centred* keeps it in the middle at every step
31
+ - **What the Directus layouts already do**: basemap, clustering and display template from the map
32
+ layout; columns, sorting, pagination and spacing from the table layout
33
+ - **Where it is heading**: an arrow on the current record, from a course field of the data
34
+ (degrees from north, signed values included) or from the record visited before
35
+ - **The trail**: the records already visited, joined in order with arrows repeated along the line,
36
+ the older legs fading. It joins what was visited, not the road between: a jump to a distant row
37
+ draws a straight leg
38
+ - **The data's clock**: playback can wait the real time between the records, sped up (1× to 600×),
39
+ instead of a fixed beat. The movement is still from record to record, not continuous
40
+ - **Timeline**: the trip on a time axis between the map and the grid, with a tick per loaded
41
+ record. Dragging it jumps to that moment — on any page when the query is sorted by that date
42
+ - **Capture card**: the focused record over the map, with its photo — which enlarges on click — its
43
+ fields and its coordinates
44
+ - **Measuring**: click the map to mark points and read the distance of the last leg and the total;
45
+ a click near a record snaps to it
46
+ - **Reset view**: re-fit the map to the current result set at any time
47
+ - **Bulk actions**: select rows and edit or delete them all at once from the header
48
+ - **Guided tour**: a balloon per feature — map, grid, filters, navigation, playback, tracking,
49
+ direction, card, timeline, measuring and reset — from the **?** of the toolbar, or by itself on
50
+ the first visit of each browser
51
+ - **Room for the map**: on Directus 10, the right sidebar starts collapsed the first time a browser
52
+ opens the collection; opened again, it stays as you left it. Directus 11 keeps its own choice
53
+ - **Map and grid, resizable**: drag the handle between them to share the height (or use ↑ ↓ on
54
+ it); the share is kept in your preset, and a double click puts the default back
55
+ - **Dates to the second**: the card and the timeline write moments as `27/03/2026 10:30:47`
56
+ - **Bilingual interface**: follows the Directus app locale (en-US, pt-BR)
57
+
58
+ ### Installation
59
+
60
+ Tested with Directus 11.17 and 10.13.
61
+
62
+ ```bash
63
+ npm install @devix-tecnologia/directus-extension-mapgrid
17
64
  ```
18
- docker compose up
65
+
66
+ ### Configuration
67
+
68
+ 1. Make sure your collection has a field of type **Map** (geolocation).
69
+ 2. In the layout dropdown on the right, select **MapGrid**.
70
+ 3. The layout options have these sections:
71
+ - **Map**: the Directus map layout's own options — basemap, geospatial field, display template
72
+ and clustering
73
+ - **Grid**: the Directus table layout's own options
74
+ - **Zoom on focus**: zoom in on the focused record instead of keeping the current zoom. It says
75
+ nothing about *whether* the camera moves — that is the camera tracking's call
76
+ - **Playback**: how many seconds to wait between records while playing back. One second is the
77
+ floor, because each step asks the map for a camera animation. The pace can instead follow
78
+ **the data clock**, at a chosen speed, reading a date/time field — the first date of the
79
+ sort is used when none is chosen, and only shown, never stored
80
+ - **Direction**: where the current record's arrow points — nowhere, a numeric course field, or
81
+ the bearing from the record visited before
82
+ - **Trail**: how many visited records the trail joins; `0` turns it off
83
+ - **Capture card**: show the focused record in a card, with a photo field and the fields to list
84
+ (the grid's columns when none are chosen)
85
+ - **Tour**: open the guided tour by itself on the first visit of each browser
86
+
87
+ The geospatial field is detected from the collection, so the layout works before anything is set.
88
+
89
+ #### Walking the records
90
+
91
+ The controls sit on the map, in the MapGrid's own toolbar:
92
+
93
+ | Control | What it does |
94
+ | --- | --- |
95
+ | ⏮ First / ⏭ Last | The ends of the **query**, not of the page: they turn the page when they have to |
96
+ | ◀ Previous / ▶ Next | One record. At the edge of a page, the page turns and the record is the one at the other end of it |
97
+ | ▶ Play / ⏹ Stop | Walks forward on its own, and stops by itself at the last record |
98
+ | GPS | Cycles the camera tracking: free → follow → keep centred |
99
+ | 📏 Measure | Turns the ruler on: clicks on the map mark points, and **Esc** turns it off |
100
+ | ⛶ Reset | Fits the map to the whole query again |
101
+ | ? Tour | Opens the guided tour; **←** and **→** walk it, **Esc** closes it |
102
+
103
+ The same steps answer to the keyboard while the layout has the focus: **Home** and **End** for the
104
+ ends of the query, **←** and **→** for one record, and **Space** for play/stop. Every Directus
105
+ shortcut carries the meta key — `meta+s`, and the `meta+a` of the table below — so holding any
106
+ modifier hands the key back to them, and so does typing in a field. Each toolbar control shows its
107
+ key in the tooltip.
108
+
109
+ The order that defines "next" is the query's `sort`, which is the one showing in the grid header.
110
+ Sorting by a date/time field is what makes playback drive a route in the order it happened; with
111
+ any other sort, it walks that order instead. While a page is being fetched the controls wait, so no
112
+ record is skipped.
113
+
114
+ The current record is marked on its grid row and scrolled into view. It is deliberately **not** the
115
+ selection: that one also arms the bulk actions, and "I am looking at this" should not read as "I
116
+ marked this to be deleted". On the map it gets a ring of its own, drawn over the canvas: with
117
+ **Cluster data** on, the record's point would otherwise be inside a cluster and the playback would
118
+ be invisible. The ring is placed by projecting the geometry with the camera the map publishes,
119
+ which it only does when a movement ends — so it appears once the camera lands.
120
+
121
+ The basemaps offered are the ones in **Project Settings → Map**, as in every map of the app. The
122
+ chosen basemap is the app's choice, not the MapGrid's: it is shared with the other map layouts and
123
+ is not stored in the MapGrid preset — which is why a screen may look different from the one above.
124
+
125
+ #### Columns
126
+
127
+ Columns are chosen in the grid's own header, the way the Directus tabular layout does it — not in
128
+ the sidebar. The **+** at the end of the header row opens the collection's field list, and dragging
129
+ a header moves the column. Clicking a header opens that column's menu, which sorts it ascending or
130
+ descending and hides the field — in the Directus tabular layout sorting lives in that menu rather
131
+ than in the click itself. Every one of those choices is stored in the preset, so it is still there
132
+ on the next visit.
133
+
134
+ There is no limit on how many columns the grid can show.
135
+
136
+ #### Upgrading from 1.x to 2.0
137
+
138
+ 2.0 composes the Directus map and table layouts instead of drawing its own, and three of the four
139
+ options a 1.x preset stored are no longer read. Nothing is lost from the data; after upgrading, set
140
+ these again once per collection:
141
+
142
+ | 1.x option | In 2.0 |
143
+ | --- | --- |
144
+ | `coluna1` … `coluna5` | The grid shows the Directus default columns until they are chosen again in its header (the **+**) |
145
+ | `geolocation` | Detected from the collection; to pick another field, use **Map → Geospatial field** in the options |
146
+ | `title` (popup template) | **Map → Display template** in the options |
147
+ | `zoomOnClick` | Still read, as **Zoom on focus** |
148
+
149
+ ### Running Directus with Docker Compose
150
+
151
+ Download this project, or copy its `docker-compose.yaml`, and start a clean install. With [Docker installed](https://docs.docker.com/get-docker/), run:
152
+
153
+ ```bash
154
+ docker compose up
19
155
  ```
20
156
 
21
- > [!IMPORTANT]
22
- > _The docker-compose used in this project is configured to allow iframes from any domain. In production, you should only allow trusted domains._
23
- >
24
- > In the previous version, the Leaflet map was used. For it to work properly, the following CSP (Content Security Policy) settings must be configured correctly (this is no longer necessary in the current version that uses Maplibre-gl, but here's a note):
157
+ ### Read-only demo
158
+
159
+ A stack to publish, where visitors try the MapGrid without changing anything. It brings the
160
+ licence plate readings of the tests — fixed cameras, patrol cars and buses across Grande Vitória,
161
+ with a photo each — in Portuguese, with every feature on and the tour opening on the first visit.
162
+
163
+ ![Demo sign-in](TASKS/assets/task-026-demo-login-depois.jpg)
25
164
 
26
- ```yaml
27
- CONTENT_SECURITY_POLICY_DIRECTIVES__FRAME_SRC: "*" # allows iframes from any domain
28
- CONTENT_SECURITY_POLICY_DIRECTIVES__IMG_SRC: "self http://0.0.0.0:8055 https: https://*.tile.openstreetmap.org data:" # allows map images
29
- DIRECTUS_HTTP_HEADERS__CONTENT_SECURITY_POLICY: "default-src *; img-src * 'self' data: https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' *; style-src 'self' 'unsafe-inline' *" # sets a global security policy for your application via the Content-Security-Policy HTTP header
165
+ ```bash
166
+ pnpm build
167
+ DEMO_ADMIN_PASSWORD='a strong one' pnpm demo
30
168
  ```
31
169
 
32
- ## 📌 Important Links
170
+ It comes up on <http://localhost:8070>; visitors sign in as `publico@example.com` /
171
+ `senha-publica` — public on purpose, so nobody takes them for a leak — which the login page shows. The seed ends by checking, as the visitor, that reading
172
+ works and writing is refused, and that anonymous requests are refused — a demo that turned writable
173
+ fails there.
174
+
175
+ | Variable | Default | |
176
+ | --- | --- | --- |
177
+ | `DEMO_ADMIN_PASSWORD` | — | Required: the demo never starts with a known admin password |
178
+ | `DEMO_ADMIN_EMAIL` | `admin@example.com` | |
179
+ | `DEMO_PORT` / `DEMO_PUBLIC_URL` | `8070` / `http://localhost:8070` | Set the URL to the published address |
180
+ | `DEMO_USER_EMAIL` / `DEMO_USER_PASSWORD` | `publico@example.com` / `senha-publica` | The visitor |
181
+ | `DEMO_KEY` / `DEMO_SECRET` / `DEMO_DB_PASSWORD` | demo values | Replace them before publishing |
182
+
183
+ Publishing it:
184
+
185
+ - put it behind HTTPS (a reverse proxy) and set `DEMO_PUBLIC_URL` to that address;
186
+ - the visitors share one login, and Directus lets a signed-in user edit their own profile — the
187
+ password included. `pnpm demo seed` puts everything back (the visitor's password, the role, the
188
+ layout), so schedule it, hourly for instance;
189
+ - the rate limiter is on, and the role reads only the readings and their photos.
190
+
191
+ `pnpm demo down` stops it and keeps the data; `docker compose -f docker-compose.demo.yml down -v`
192
+ wipes it.
193
+
194
+ ### Development
195
+
196
+ **Prerequisites:** Node.js >= 22.13.1 and pnpm >= 10.15.0.
197
+
198
+ ```bash
199
+ git clone https://github.com/devix-tecnologia/directus-extension-mapgrid.git
200
+ cd directus-extension-mapgrid
201
+ pnpm install
202
+ docker compose up
203
+ ```
204
+
205
+ #### Available scripts
206
+
207
+ ```bash
208
+ # Development
209
+ pnpm dev # Build in watch mode
210
+ pnpm storybook # Start the Storybook dev server
211
+
212
+ # Build
213
+ pnpm build # Build the extension
214
+ pnpm build-storybook # Build Storybook
215
+
216
+ # Code quality
217
+ pnpm lint # Run the Biome linter
218
+ pnpm lint:fix # Fix what the linter can fix
219
+ pnpm format # Format the source
220
+ pnpm typecheck # Typecheck the source and the tests
221
+
222
+ # Testing
223
+ pnpm test # Unit tests
224
+ pnpm test:watch # Unit tests in watch mode
225
+ pnpm check:stories # Open every story in a browser, fail on console output
226
+ pnpm screenshot # Regenerate docs/tela.jpg from a running Directus
227
+ pnpm test:integration # Integration tests (requires Docker)
228
+ pnpm test:e2e # End-to-end tests (requires Docker)
229
+ pnpm test:all # Everything above that needs Docker
230
+
231
+ # Stacks to try by hand (Docker)
232
+ pnpm test:collection # The e2e Directus with the test collections, left running (pt-BR)
233
+ pnpm demo # The read-only demo (needs DEMO_ADMIN_PASSWORD)
234
+ ```
235
+
236
+ #### Project structure
237
+
238
+ ```
239
+ src/
240
+ ├── contract/ # Shapes crossing the Directus boundary
241
+ ├── components/
242
+ │ ├── atoms/ # DeleteAction
243
+ │ ├── molecules/ # MapToolbar, CurrentMark, MapTrail, TripTimelineBar, CaptureCard,
244
+ │ │ # MeasureOverlay, TourBalloon, PaneSplitter
245
+ │ └── templates/ # MapgridLayout, MapgridOptions
246
+ ├── services/ # One class per folder, behind an I<Name> interface
247
+ │ ├── map-centerer/ # Moves the embedded Directus map (works around its limitation)
248
+ │ ├── current-point/ # Projects records and coordinates onto the map pane, and back
249
+ │ ├── embedded-layout/ # Runs a Directus layout's setup() inside the MapGrid
250
+ │ ├── record-heading/, trail-path/, visited-path/ # direction and trail
251
+ │ ├── playback-clock/, playback-tempo/, trip-timeline/ # playback and timeline
252
+ │ ├── record-card/, distance-measure/, instant-format/ # card, measuring, dates
253
+ │ ├── pane-split/ # The height shared between map and grid
254
+ │ ├── onboarding-tour/, balloon-placement/, browser-memory/ # the tour, first visits
255
+ │ └── optimistic-sync/ # Two writes to the preset in the same tick no longer erase each other
256
+ ├── shared/ # Extension strings (en-US, pt-BR)
257
+ ├── mocks/ # Directus environment and the mappable-collection catalogue
258
+ └── index.ts # Extension entry point
259
+ ```
260
+
261
+ The `contract/` layer is also published as a subpath (`@devix-tecnologia/directus-extension-mapgrid/contract`), so another extension in the same project can import the same types instead of redrawing them.
262
+
263
+ ### Testing
264
+
265
+ | Suite | Needs Docker | What it covers |
266
+ | --- | --- | --- |
267
+ | `pnpm test` | no | Pure logic and components, with Vitest |
268
+ | `pnpm check:stories` | no | Every story opened in Chromium; fails on any console message |
269
+ | `pnpm test:integration` | yes | Extension logic against a real Directus instance |
270
+ | `pnpm test:e2e` | yes | The whole UI in a browser, with Playwright |
271
+
272
+ `RUNNER_ARGS` narrows a Docker run to one spec, which is what a measurement
273
+ usually wants: `RUNNER_ARGS=tests/e2e/mapgrid-playback-anticipation.spec.ts pnpm test:e2e`.
274
+
275
+ ```
276
+ tests/
277
+ ├── stories/ # Storybook console check (Playwright, no Docker)
278
+ ├── e2e/ # End-to-end tests (Playwright)
279
+ │ ├── mapgrid-layout.spec.ts # Map/grid rendering and interaction
280
+ │ ├── global-setup.ts # Seeds the MapGrid preset before the suite
281
+ │ └── helpers/map-projection.ts # Web Mercator projection for canvas clicks
282
+ ├── helpers/ # Shared API/wait/preset helpers
283
+ ├── helper-collection.ts # Test collection with a Map field
284
+ ├── run-docker-tests.js # Docker runner (integration + e2e)
285
+ └── mapgrid.spec.ts # Integration tests (Vitest)
286
+ ```
287
+
288
+ ### Engineering standards
289
+
290
+ Technical standard records live under [dev-docs/padroes/](dev-docs/padroes/). The codebase follows the Total TypeScript principles: honest types over assertions, derived types over duplicated shapes, and compiler and linter strictness kept on rather than silenced. Values crossing the Directus boundary are parsed in `contract/`, never asserted with `as`.
291
+
292
+ ### Contributing
293
+
294
+ The README shows a screenshot of the running layout (`docs/tela.jpg`) right at
295
+ the top, and it is the first thing anyone sees on GitHub and on the npm page. Any
296
+ change that alters how a component looks has to refresh that screenshot as part
297
+ of the same work — a stale image advertises a product that no longer exists.
298
+ `pnpm screenshot` regenerates it against a real Directus, so this is a command
299
+ rather than a chore. The README is two complete documents, one per language, so
300
+ behaviour described in prose has to be corrected in both.
301
+
302
+ 1. Fork the repository
303
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
304
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
305
+ 4. Push the branch (`git push origin feature/amazing-feature`)
306
+ 5. Open a pull request
307
+
308
+ ### License
309
+
310
+ MIT — see [License.txt](License.txt).
311
+
312
+ ### Links
313
+
314
+ - [Directus documentation](https://docs.directus.io/)
315
+ - [Directus extensions guide](https://docs.directus.io/extensions/overview.html)
316
+ - [Report an issue](https://github.com/devix-tecnologia/directus-extension-mapgrid/issues)
317
+
318
+ ---
319
+
320
+ ## 🇧🇷 Português
321
+
322
+ Extensão de layout para o Directus que exibe uma coleção simultaneamente em **mapa** e **grade**. Desenvolvida pela [Devix Tecnologia](https://devix.co).
323
+
324
+ [🇺🇸 English](#-english) · **🇧🇷 Português**
325
+
326
+ ### Recursos
327
+
328
+ - **Mapa e grade juntos**: o layout de mapa e o layout de tabela do Directus lado a lado, sobre a
329
+ mesma consulta e a mesma seleção
330
+ - **Clicar na linha leva o mapa até o item**: o mapa voa até ele mantendo o zoom de agora — ou
331
+ aproximando, com *Zoom ao focar*
332
+ - **Clicar no marcador marca a linha**: em vez de sair do layout para a página do item. A marca não
333
+ é a seleção, então não arma as ações em lote
334
+ - **Percorrer os registros**: primeiro, anterior, próximo e último, na ordem da consulta. Se o
335
+ registro está em outra página, a página é trocada antes
336
+ - **Reproduzir**: a câmera percorre os registros sozinha, num intervalo configurável. Numa coleção
337
+ de rastreamento veicular ordenada por data, ela refaz o trajeto na ordem em que ele aconteceu
338
+ - **Acompanhamento da câmera**: *livre* deixa o mapa onde você deixou, *seguir* move só quando o
339
+ registro sai da área visível, *centralizar* mantém o registro sempre no meio
340
+ - **O que os layouts do Directus já fazem**: mapa base, agrupamento e template de exibição vêm do
341
+ layout de mapa; colunas, ordenação, paginação e espaçamento vêm do layout de tabela
342
+ - **Para onde vai**: uma seta no registro atual, a partir de um campo de rumo do dado (graus a
343
+ partir do norte, valores negativos inclusive) ou do registro visitado antes
344
+ - **O rastro**: os registros já visitados, ligados em ordem, com setas repetidas ao longo da linha e
345
+ os trechos mais antigos esmaecendo. Liga o que foi visitado, não a rua entre eles: um salto para
346
+ uma linha distante desenha um trecho reto
347
+ - **O relógio dos dados**: a reprodução pode esperar o tempo real entre os registros, acelerado (1×
348
+ a 600×), em vez de um intervalo fixo. O movimento continua de registro em registro, não contínuo
349
+ - **Linha do tempo**: a viagem num eixo de tempo entre o mapa e a grade, com uma marca por registro
350
+ carregado. Arrastar leva àquele momento — em qualquer página quando a consulta está ordenada por
351
+ aquela data
352
+ - **Card da captura**: o registro em foco sobre o mapa, com a foto — que amplia ao clicar —, os
353
+ campos e as coordenadas
354
+ - **Medição**: clique no mapa para marcar pontos e ler a distância do último trecho e a total; um
355
+ clique perto de um registro vai até ele
356
+ - **Reenquadrar**: reajustar o mapa ao resultado atual a qualquer momento
357
+ - **Ações em lote**: selecionar linhas e editá-las ou excluí-las de uma vez pelo cabeçalho
358
+ - **Apresentação guiada**: um balão por recurso — mapa, grade, filtros, navegação, reprodução,
359
+ acompanhamento, sentido, card, linha do tempo, medição e reenquadrar — pelo **?** da barra, ou
360
+ sozinha na primeira visita de cada navegador
361
+ - **Espaço para o mapa**: no Directus 10, a barra lateral direita começa recolhida na primeira vez
362
+ que o navegador abre a coleção; reaberta, fica como você deixou. O Directus 11 mantém a escolha dele
363
+ - **Mapa e grade redimensionáveis**: arraste a divisória entre eles para dividir a altura (ou use
364
+ ↑ ↓ nela); a divisão fica no seu preset, e um duplo clique volta ao padrão
365
+ - **Datas até o segundo**: o card e a linha do tempo escrevem os momentos como `27/03/2026 10:30:47`
366
+ - **Interface bilíngue**: acompanha o idioma do app do Directus (en-US, pt-BR)
367
+
368
+ ### Instalação
369
+
370
+ Testado com o Directus 11.17 e o 10.13.
371
+
372
+ ```bash
373
+ npm install @devix-tecnologia/directus-extension-mapgrid
374
+ ```
375
+
376
+ ### Configuração
377
+
378
+ 1. Garanta que a coleção possua um campo do tipo **Map** (geolocalização).
379
+ 2. No menu de layouts à direita, selecione **MapGrid**.
380
+ 3. As opções do layout têm estas seções:
381
+ - **Mapa**: as opções do próprio layout de mapa do Directus — mapa base, campo geoespacial,
382
+ template de exibição e agrupamento
383
+ - **Grade**: as opções do próprio layout de tabela do Directus
384
+ - **Zoom ao focar**: aproximar do registro focado em vez de manter o zoom de agora. Não diz nada
385
+ sobre *se* a câmera se move — isso é do acompanhamento da câmera
386
+ - **Reprodução**: quantos segundos esperar entre um registro e o próximo durante a reprodução. O
387
+ piso é um segundo, porque cada passo pede uma animação de câmera ao mapa. O ritmo pode, em vez
388
+ disso, seguir **o relógio dos dados**, numa velocidade escolhida, lendo um campo de data/hora —
389
+ sem escolha, vale a primeira data da ordenação, que só é mostrada, nunca gravada
390
+ - **Sentido**: para onde a seta do registro atual aponta — para lugar nenhum, um campo numérico
391
+ de rumo, ou o rumo a partir do registro visitado antes
392
+ - **Rastro**: quantos registros visitados o rastro liga; `0` desliga
393
+ - **Card da captura**: mostrar o registro em foco num card, com um campo de foto e os campos a
394
+ listar (as colunas da grade quando nenhum é escolhido)
395
+ - **Apresentação**: abrir a apresentação guiada sozinha na primeira visita de cada navegador
396
+
397
+ O campo geoespacial é detectado a partir da coleção, então o layout funciona antes de qualquer
398
+ opção ser preenchida.
399
+
400
+ #### Percorrendo os registros
401
+
402
+ Os controles ficam sobre o mapa, na barra do próprio MapGrid:
403
+
404
+ | Controle | O que faz |
405
+ | --- | --- |
406
+ | ⏮ Primeiro / ⏭ Último | As pontas da **consulta**, não da página: trocam de página quando precisam |
407
+ | ◀ Anterior / ▶ Próximo | Um registro. Na borda da página, a página vira e o registro é o da outra ponta dela |
408
+ | ▶ Play / ⏹ Stop | Avança sozinho, e para no último registro por conta própria |
409
+ | GPS | Cicla o acompanhamento da câmera: livre → seguir → centralizar |
410
+ | 📏 Medir | Liga a régua: cliques no mapa marcam pontos, e **Esc** desliga |
411
+ | ⛶ Reenquadrar | Ajusta o mapa de novo à consulta inteira |
412
+ | ? Apresentação | Abre a apresentação guiada; **←** e **→** percorrem, **Esc** fecha |
413
+
414
+ Os mesmos passos atendem ao teclado enquanto o layout está em foco: **Home** e **End** para as
415
+ pontas da consulta, **←** e **→** para um registro, e **Espaço** para play/stop. Todo atalho do
416
+ Directus leva a tecla meta — `meta+s`, e o `meta+a` da tabela abaixo —, então segurar qualquer
417
+ modificador devolve a tecla para eles, e digitar num campo também. Cada controle da barra mostra a
418
+ sua tecla na dica.
419
+
420
+ A ordem que define "próximo" é o `sort` da consulta, que é o que está à vista no cabeçalho da
421
+ grade. Ordenar por um campo de data/hora é o que faz a reprodução refazer um trajeto na ordem em
422
+ que ele aconteceu; com qualquer outra ordem, ela percorre essa outra ordem. Enquanto uma página
423
+ está sendo buscada, os controles esperam — nenhum registro é pulado.
424
+
425
+ O registro atual é marcado na linha da grade e rolado até a vista. Ele deliberadamente **não** é a
426
+ seleção: ela também arma as ações em lote, e "estou vendo este" não pode aparecer como "marquei
427
+ este para apagar". No mapa ele ganha um anel próprio, desenhado sobre a tela: com o **Cluster
428
+ data** ligado, o ponto do registro estaria dentro do agrupamento e a reprodução ficaria invisível.
429
+ O anel é posicionado projetando a geometria com a câmera que o mapa publica, e ele só a publica
430
+ quando um movimento termina — por isso a marca aparece quando a câmera pousa.
431
+
432
+ Os mapas base oferecidos são os de **Project Settings → Map**, como em todo mapa do app. O mapa base
433
+ escolhido é uma escolha do app, e não do MapGrid: vale também para os outros layouts de mapa e não
434
+ fica gravado no preset do MapGrid — por isso uma tela pode parecer diferente da que está acima.
435
+
436
+ #### Colunas
437
+
438
+ As colunas se escolhem no próprio cabeçalho da grade, como no layout tabular do Directus — e não no
439
+ painel lateral. O **+** no fim da linha de cabeçalho abre a lista de campos da coleção, e arrastar um
440
+ cabeçalho move a coluna. Clicar num cabeçalho abre o menu daquela coluna, que a ordena em ordem
441
+ crescente ou decrescente e oculta o campo — no layout tabular do Directus a ordenação mora nesse
442
+ menu, e não no clique em si. Cada uma dessas escolhas fica gravada no preset, então continua lá na
443
+ próxima visita.
444
+
445
+ Não há limite de quantas colunas a grade exibe.
446
+
447
+ #### Atualizando da 1.x para a 2.0
448
+
449
+ A 2.0 compõe os layouts de mapa e de tabela do Directus em vez de desenhar os seus, e três das
450
+ quatro opções que um preset da 1.x guardava não são mais lidas. Nada se perde dos dados; depois de
451
+ atualizar, configure-as de novo, uma vez por coleção:
452
+
453
+ | Opção da 1.x | Na 2.0 |
454
+ | --- | --- |
455
+ | `coluna1` … `coluna5` | A grade mostra as colunas padrão do Directus até que sejam escolhidas de novo no cabeçalho dela (o **+**) |
456
+ | `geolocation` | Detectada a partir da coleção; para escolher outro campo, use **Mapa → Campo geoespacial** nas opções |
457
+ | `title` (modelo do popup) | **Mapa → Template de exibição** nas opções |
458
+ | `zoomOnClick` | Continua sendo lida, como **Zoom ao focar** |
459
+
460
+ ### Rodando o Directus com Docker Compose
461
+
462
+ Baixe este projeto, ou copie o `docker-compose.yaml` dele, e inicie uma instalação limpa. Com o [Docker instalado](https://docs.docker.com/get-docker/), execute:
463
+
464
+ ```bash
465
+ docker compose up
466
+ ```
467
+
468
+ ### Demonstração somente leitura
469
+
470
+ Um stack para publicar, onde visitantes experimentam o MapGrid sem mudar nada. Traz as leituras de
471
+ placas dos testes — câmeras fixas, viaturas e ônibus na Grande Vitória, cada uma com foto — em
472
+ português, com todos os recursos ligados e a apresentação abrindo na primeira visita.
473
+
474
+ ![Tela de entrada](TASKS/assets/task-026-demo-login-depois.jpg)
475
+
476
+ ```bash
477
+ pnpm build
478
+ DEMO_ADMIN_PASSWORD='uma senha forte' pnpm demo
479
+ ```
480
+
481
+ Sobe em <http://localhost:8070>; visitantes entram com `publico@example.com` / `senha-publica` — públicas de propósito, para
482
+ ninguém tomá-las por vazamento —,
483
+ que a tela de entrada mostra. A semente termina conferindo, como visitante, que ler funciona e
484
+ escrever é recusado, e que requisições anônimas são recusadas — uma demonstração que virou
485
+ gravável falha ali.
486
+
487
+ | Variável | Padrão | |
488
+ | --- | --- | --- |
489
+ | `DEMO_ADMIN_PASSWORD` | — | Obrigatória: a demonstração nunca sobe com senha de admin conhecida |
490
+ | `DEMO_ADMIN_EMAIL` | `admin@example.com` | |
491
+ | `DEMO_PORT` / `DEMO_PUBLIC_URL` | `8070` / `http://localhost:8070` | Aponte a URL para o endereço publicado |
492
+ | `DEMO_USER_EMAIL` / `DEMO_USER_PASSWORD` | `publico@example.com` / `senha-publica` | O visitante |
493
+ | `DEMO_KEY` / `DEMO_SECRET` / `DEMO_DB_PASSWORD` | valores de demonstração | Troque antes de publicar |
494
+
495
+ Para publicar:
496
+
497
+ - coloque atrás de HTTPS (um proxy reverso) e aponte `DEMO_PUBLIC_URL` para esse endereço;
498
+ - os visitantes dividem um login, e o Directus deixa quem está logado editar o próprio perfil —
499
+ senha inclusive. `pnpm demo seed` põe tudo de volta (a senha do visitante, o papel, o layout),
500
+ então agende-o, de hora em hora por exemplo;
501
+ - o limitador de requisições está ligado, e o papel só lê as leituras e as fotos delas.
502
+
503
+ `pnpm demo down` para e mantém os dados; `docker compose -f docker-compose.demo.yml down -v` apaga.
504
+
505
+ ### Desenvolvimento
506
+
507
+ **Pré-requisitos:** Node.js >= 22.13.1 e pnpm >= 10.15.0.
508
+
509
+ ```bash
510
+ git clone https://github.com/devix-tecnologia/directus-extension-mapgrid.git
511
+ cd directus-extension-mapgrid
512
+ pnpm install
513
+ docker compose up
514
+ ```
515
+
516
+ #### Scripts disponíveis
517
+
518
+ ```bash
519
+ # Desenvolvimento
520
+ pnpm dev # Build em modo watch
521
+ pnpm storybook # Sobe o servidor de desenvolvimento do Storybook
522
+
523
+ # Build
524
+ pnpm build # Compila a extensão
525
+ pnpm build-storybook # Compila o Storybook
526
+
527
+ # Qualidade de código
528
+ pnpm lint # Roda o linter (Biome)
529
+ pnpm lint:fix # Corrige o que o linter consegue corrigir
530
+ pnpm format # Formata o código-fonte
531
+ pnpm typecheck # Checa os tipos do código e dos testes
532
+
533
+ # Testes
534
+ pnpm test # Testes unitários
535
+ pnpm test:watch # Testes unitários em modo watch
536
+ pnpm check:stories # Abre cada story num navegador e falha se houver console
537
+ pnpm screenshot # Refaz docs/tela.jpg a partir de um Directus em execução
538
+ pnpm test:integration # Testes de integração (requer Docker)
539
+ pnpm test:e2e # Testes ponta a ponta (requer Docker)
540
+ pnpm test:all # Tudo acima que precisa de Docker
541
+
542
+ # Stacks para experimentar à mão (Docker)
543
+ pnpm test:collection # O Directus do e2e com as coleções de teste, deixado de pé (pt-BR)
544
+ pnpm demo # A demonstração somente leitura (pede DEMO_ADMIN_PASSWORD)
545
+ ```
546
+
547
+ #### Estrutura do projeto
548
+
549
+ ```
550
+ src/
551
+ ├── contract/ # Formas que cruzam a fronteira com o Directus
552
+ ├── components/
553
+ │ ├── atoms/ # DeleteAction
554
+ │ ├── molecules/ # MapToolbar, CurrentMark, MapTrail, TripTimelineBar, CaptureCard,
555
+ │ │ # MeasureOverlay, TourBalloon, PaneSplitter
556
+ │ └── templates/ # MapgridLayout, MapgridOptions
557
+ ├── services/ # Uma classe por pasta, atrás de uma interface I<Nome>
558
+ │ ├── map-centerer/ # Move o mapa embutido do Directus (contorna a limitação dele)
559
+ │ ├── current-point/ # Projeta registros e coordenadas no painel do mapa, e de volta
560
+ │ ├── embedded-layout/ # Roda o setup() de um layout do Directus dentro do MapGrid
561
+ │ ├── record-heading/, trail-path/, visited-path/ # sentido e rastro
562
+ │ ├── playback-clock/, playback-tempo/, trip-timeline/ # reprodução e linha do tempo
563
+ │ ├── record-card/, distance-measure/, instant-format/ # card, medição, datas
564
+ │ ├── pane-split/ # A altura dividida entre mapa e grade
565
+ │ ├── onboarding-tour/, balloon-placement/, browser-memory/ # apresentação, primeiras visitas
566
+ │ └── optimistic-sync/ # Duas escritas no preset no mesmo tick deixam de se apagar
567
+ ├── shared/ # Textos da extensão (en-US, pt-BR)
568
+ ├── mocks/ # Ambiente do Directus e catálogo de coleções mapeáveis
569
+ └── index.ts # Ponto de entrada da extensão
570
+ ```
571
+
572
+ A camada `contract/` também é publicada como subcaminho (`@devix-tecnologia/directus-extension-mapgrid/contract`), para outra extensão do mesmo projeto importar os mesmos tipos em vez de redesenhá-los.
573
+
574
+ ### Testes
575
+
576
+ | Suíte | Precisa de Docker | O que cobre |
577
+ | --- | --- | --- |
578
+ | `pnpm test` | não | Lógica pura e componentes, com Vitest |
579
+ | `pnpm check:stories` | não | Cada story aberta no Chromium; falha a qualquer mensagem de console |
580
+ | `pnpm test:integration` | sim | Lógica da extensão contra um Directus real |
581
+ | `pnpm test:e2e` | sim | A interface inteira num navegador, com Playwright |
582
+
583
+ O `RUNNER_ARGS` restringe uma execução no Docker a um spec só, que é o que uma
584
+ medição costuma querer: `RUNNER_ARGS=tests/e2e/mapgrid-playback-anticipation.spec.ts pnpm test:e2e`.
585
+
586
+ ```
587
+ tests/
588
+ ├── stories/ # Checagem do console das stories (Playwright, sem Docker)
589
+ ├── e2e/ # Testes ponta a ponta (Playwright)
590
+ │ ├── mapgrid-layout.spec.ts # Renderização e interação de mapa/grade
591
+ │ ├── global-setup.ts # Semeia o preset do MapGrid antes da suíte
592
+ │ └── helpers/map-projection.ts # Projeção Web Mercator para cliques no canvas
593
+ ├── helpers/ # Auxiliares de API, espera e preset
594
+ ├── helper-collection.ts # Coleção de teste com campo Map
595
+ ├── run-docker-tests.js # Executor Docker (integração + e2e)
596
+ └── mapgrid.spec.ts # Testes de integração (Vitest)
597
+ ```
598
+
599
+ ### Padrões de engenharia
600
+
601
+ Os registros de padrão técnico ficam em [dev-docs/padroes/](dev-docs/padroes/). O código segue a filosofia Total TypeScript: tipos honestos em vez de asserções, tipos derivados em vez de formas duplicadas, e a severidade do compilador e do linter mantida ligada em vez de silenciada. Valores que cruzam a fronteira com o Directus são parseados em `contract/`, nunca asseverados com `as`.
602
+
603
+ ### Contribuindo
604
+
605
+ O README exibe logo no topo uma captura do layout em funcionamento
606
+ (`docs/tela.jpg`), e ela é a primeira coisa que alguém vê no GitHub e na página
607
+ do npm. Toda mudança que altere a aparência de um componente precisa refazer essa
608
+ captura como parte do mesmo trabalho — uma imagem desatualizada anuncia um
609
+ produto que não existe mais. O `pnpm screenshot` refaz a imagem contra um
610
+ Directus de verdade, então isso é um comando e não uma tarefa manual. O README
611
+ são dois documentos completos, um por idioma, então comportamento descrito em
612
+ texto precisa ser corrigido nos dois.
613
+
614
+ 1. Faça um fork do repositório
615
+ 2. Crie sua branch (`git checkout -b feature/recurso-incrivel`)
616
+ 3. Faça o commit (`git commit -m 'feat: adiciona recurso incrível'`)
617
+ 4. Envie a branch (`git push origin feature/recurso-incrivel`)
618
+ 5. Abra um pull request
619
+
620
+ ### Licença
621
+
622
+ MIT — veja [License.txt](License.txt).
33
623
 
34
- - [Directus Quickstart](https://docs.directus.io/getting-started/quickstart.html) (under the Docker Installation tab)
35
- - [How to Create an Extension](https://docs.directus.io/extensions/creating-extensions.html)
36
- - [Access Directus Services](https://docs.directus.io/extensions/services/introduction.html)
37
- - [Access Stored Collection Items](https://docs.directus.io/extensions/services/accessing-items.html)
624
+ ### Links
38
625
 
626
+ - [Documentação do Directus](https://docs.directus.io/)
627
+ - [Guia de extensões do Directus](https://docs.directus.io/extensions/overview.html)
628
+ - [Relatar um problema](https://github.com/devix-tecnologia/directus-extension-mapgrid/issues)