@devix-tecnologia/directus-extension-mapgrid 1.5.1 → 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 +616 -14
- package/dist/index.js +1 -5
- package/package.json +75 -37
- package/src/contract/camera-tracking.contract.ts +14 -0
- package/src/contract/geolocation.contract.ts +5 -0
- package/src/contract/index.ts +4 -0
- package/src/contract/layout-options.contract.ts +54 -0
- package/src/contract/layout-query.test.ts +79 -0
- package/src/contract/layout-query.ts +65 -0
package/README.md
CHANGED
|
@@ -1,26 +1,628 @@
|
|
|
1
1
|
# Directus - MapGrid Layout Extension
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/devix-tecnologia/directus-extension-mapgrid/actions)
|
|
4
|
+
[](https://www.npmjs.com/package/@devix-tecnologia/directus-extension-mapgrid)
|
|
5
|
+
[](https://github.com/devix-tecnologia/directus-extension-mapgrid/blob/main/License.txt)
|
|
4
6
|
|
|
5
|
-
|
|
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
|
-
|
|
8
|
-
- Enable the Layout in the settings menu on the right by selecting "MapGrid" from the dropdown.
|
|
9
|
+

|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
**🇺🇸 English** · [🇧🇷 Português](#-português)
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
---
|
|
13
14
|
|
|
14
|
-
|
|
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
|
|
64
|
+
```
|
|
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
|
|
17
155
|
```
|
|
18
|
-
|
|
156
|
+
|
|
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
|
+

|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
pnpm build
|
|
167
|
+
DEMO_ADMIN_PASSWORD='a strong one' pnpm demo
|
|
19
168
|
```
|
|
20
169
|
|
|
21
|
-
|
|
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
|
+

|
|
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).
|
|
623
|
+
|
|
624
|
+
### Links
|
|
22
625
|
|
|
23
|
-
- [Directus
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [Access Stored Collection Items](https://docs.directus.io/extensions/services/accessing-items.html)
|
|
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)
|