Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
d757445
Add Gutenberg block editor with Leaflet map preview and dev environment
timogiese-ads Mar 11, 2026
b1f0554
Merge pull request #14 from YaroShkvorets/patch-1
techtimo Mar 9, 2026
f290456
update deps
timogiese-ads Mar 11, 2026
58cad67
docs: add Development section to README with first-time setup instruc…
claude Mar 8, 2026
c0a44f1
update npm run commands
timogiese-ads Mar 11, 2026
b5b21b0
fix: replace deprecated jQuery .click() and .change() with .on()
timogiese-ads Mar 12, 2026
d22982f
fix: improve Leaflet map cleanup with proper destroy flag and map.rem…
timogiese-ads Mar 12, 2026
08ed458
feat: add source map handling and improve build scripts
timogiese-ads Mar 12, 2026
7d371a0
prepare for WP 7.0
timogiese-ads Mar 12, 2026
347fa08
delete old block.js, add source mapping
timogiese-ads Mar 12, 2026
dd1a9a6
change year so it's easier to use the filter
timogiese-ads Mar 12, 2026
f051324
big refactor
timogiese-ads Mar 13, 2026
f57192e
dependency management
timogiese-ads Mar 13, 2026
c4df3ff
jquery still needed fpr public frontend
timogiese-ads Mar 13, 2026
fb26e3f
remove legacy maphandler.js :')
timogiese-ads Mar 18, 2026
f66d532
refactor UI part 1
timogiese-ads Mar 18, 2026
1ad4ba6
refactor UI part 2 (gpx)
timogiese-ads Mar 18, 2026
1c14db0
UI redesign part 3
timogiese-ads Mar 19, 2026
fc02b51
change maps config from json to yaml
timogiese-ads Mar 19, 2026
da00635
better center the settings popup
timogiese-ads Mar 19, 2026
6737d15
every deps is now a npm package
timogiese-ads Mar 19, 2026
9e60b0b
fix easybutton issues
timogiese-ads Mar 19, 2026
c823233
verify cronjob is always executed
timogiese-ads Mar 20, 2026
fc4b2f5
introduce option management
timogiese-ads Mar 21, 2026
d5c0dac
adding unit tests
timogiese-ads Mar 21, 2026
0781c11
add .gitattributes to enforce LF line endings
timogiese-ads Mar 21, 2026
496e11b
harden feed password handling
timogiese-ads Mar 21, 2026
f991fcf
apply wp-scripts formatting and fix settings.js bugs
timogiese-ads Mar 22, 2026
6068296
improve autreload
timogiese-ads Mar 22, 2026
1e7ffce
bump to 1.0.0, migrate feeds to unified option, REST-ready options layer
timogiese-ads Mar 23, 2026
7e0e23c
admin rest api
timogiese-ads Mar 23, 2026
5e402bf
fixes and imnprovements
timogiese-ads Mar 23, 2026
61af6b4
enhance security with storing passwords
timogiese-ads Mar 23, 2026
7895264
use react in backend
techtimo Mar 23, 2026
893e7cc
block should handle defaults like shortcode
techtimo Mar 23, 2026
94f728d
improve
techtimo Mar 23, 2026
0ee9aa9
prevent SQL injections
techtimo Mar 23, 2026
cdd6a93
format
techtimo Mar 23, 2026
bf23617
add tests for UI
techtimo Mar 24, 2026
adf565c
add package
techtimo Mar 24, 2026
cf87f1d
improva parity between shortcode and block
techtimo Mar 24, 2026
9a0680e
fix feed mismatch in new installation
techtimo Mar 24, 2026
e52c77b
migration from 0.11.2 to 1.0.0
techtimo Mar 24, 2026
1eeefe8
more sample data
techtimo Mar 24, 2026
2cc8bd6
format + fix zoom to hidden feeds
techtimo Mar 25, 2026
91fb376
fix france map, add tests for maps, sort example data
techtimo Mar 25, 2026
b568f75
decrease map reloads for in place update of attributes
techtimo Mar 25, 2026
ebd7b65
try to stick to WP desings better
techtimo Mar 26, 2026
c3b07bc
remove openseamap depth
techtimo Mar 26, 2026
a8d9ac2
check if feed is valid
techtimo Mar 26, 2026
c37970f
modify empty value is ignored
techtimo Mar 26, 2026
a9c4849
add security section
techtimo Mar 26, 2026
b400d7a
feat: edit points
techtimo Mar 26, 2026
d787f63
fix date picker
techtimo Mar 26, 2026
c0a1621
imrpove debug of map engine
techtimo Mar 26, 2026
f047a20
feat: add OsmAnd
techtimo Mar 26, 2026
403be1e
refactor to using spaces
techtimo Mar 27, 2026
9848f13
fix js linting issues
techtimo Mar 27, 2026
61fa1f3
fix to check exif only for pictures
techtimo Mar 27, 2026
8771756
fix test with correct settings
techtimo Mar 27, 2026
2362921
feat: pause/unpause
techtimo Mar 27, 2026
10911eb
feat: dedup of new points being added
techtimo Mar 27, 2026
9b5d657
fix: prefix AJAX action with spotmap_ namespace
claude Mar 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# E2E test tokens — copy this file to .env.e2e and fill in your values.
# .env.e2e is gitignored and must never be committed.

SPOTMAP_TOKEN_MAPBOX=
SPOTMAP_TOKEN_THUNDERFOREST=
SPOTMAP_TOKEN_TIMEZONEDB=
SPOTMAP_TOKEN_LINZ=
SPOTMAP_TOKEN_GEOPORTAIL=ign_scan_ws
SPOTMAP_TOKEN_OSDATAHUB=
4 changes: 4 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
coverage/
build/
public/
vendor/
52 changes: 52 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
module.exports = {
extends: [ 'plugin:@wordpress/eslint-plugin/recommended' ],
rules: {
// @wordpress/* packages are WordPress externals, not bundled deps
'import/no-unresolved': [ 'error', { ignore: [ '^@wordpress/' ] } ],
// @wordpress/* packages are WordPress-provided externals; not bundled
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: true,
packageDir: [
'.',
'./node_modules/@wordpress/icons',
'./node_modules/@wordpress/env',
],
},
],
// Allow @returns as alias for @return (both are valid JSDoc)
'jsdoc/check-tag-names': [ 'error', { definedTags: [ 'returns' ] } ],
// Allow console.error/warn for legitimate error reporting
'no-console': [ 'error', { allow: [ 'error', 'warn' ] } ],
// __experimentalUnitControl is the only option for this API;
// no stable UnitControl exists in the WordPress runtime externals
'@wordpress/no-unsafe-wp-apis': [
'error',
{ '@wordpress/components': [ '__experimentalUnitControl' ] },
],
},
overrides: [
{
// TypeScript types already document params — @param is redundant
files: [ '**/*.ts', '**/*.tsx' ],
rules: {
'jsdoc/require-param': 'off',
'jsdoc/check-tag-names': 'off',
},
},
{
files: [
'**/__tests__/**/*.js',
'**/__tests__/**/*.jsx',
'**/__tests__/**/*.ts',
'**/__tests__/**/*.tsx',
'**/*.test.js',
'**/*.test.jsx',
'**/*.test.ts',
'**/*.test.tsx',
],
extends: [ 'plugin:@wordpress/eslint-plugin/test-unit' ],
},
],
};
14 changes: 14 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Normalize all text files to LF on commit
* text=auto eol=lf

# Binaries — no conversion
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.zip binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary
*.otf binary
26 changes: 13 additions & 13 deletions .github/workflows/deploy_assets-readme.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
name: Plugin asset/readme update
on:
push:
branches:
- master
push:
branches:
- master
jobs:
master:
name: Push to master
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: WordPress.org plugin asset/readme update
uses: 10up/action-wordpress-plugin-asset-update@master
env:
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
master:
name: Push to master
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: WordPress.org plugin asset/readme update
uses: 10up/action-wordpress-plugin-asset-update@master
env:
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
28 changes: 14 additions & 14 deletions .github/workflows/deploy_plugin.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
name: Deploy to WordPress.org
on:
push:
tags:
- "*"
push:
tags:
- '*'
jobs:
tag:
name: New tag
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: WordPress Plugin Deploy
uses: 10up/action-wordpress-plugin-deploy@master
env:
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
SLUG: spotmap
tag:
name: New tag
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: WordPress Plugin Deploy
uses: 10up/action-wordpress-plugin-deploy@master
env:
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
SLUG: spotmap
15 changes: 0 additions & 15 deletions .github/workflows/install_plugin.yaml

This file was deleted.

22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
node_modules/
build/

# Composer (both regenerated by pipeline via `npm run composer install`)
vendor/
vendor-prefixed/
.claude/
*.js.map

# public/ has its own .gitignore — only the two authored PHP files are tracked
includes/css/
includes/webfonts/

# Test artifacts
.phpunit.result.cache
coverage/

# E2E — private tokens, temp files, and Playwright output
.env
tests/e2e/.inject.php
playwright-report/
test-results/
18 changes: 18 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

$finder = PhpCsFixer\Finder::create()
->in( __DIR__ )
->exclude( 'vendor' )
->exclude( 'vendor-prefixed' )
->exclude( 'node_modules' )
->exclude( 'build' )
->exclude( 'public' );

$config = new PhpCsFixer\Config();

return $config
->setRules( [
'@PSR12' => true,
'indentation_type' => true,
] )
->setFinder( $finder );
7 changes: 7 additions & 0 deletions .prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const wordpressConfig = require( '@wordpress/prettier-config' );

module.exports = {
...wordpressConfig,
useTabs: false,
tabWidth: 4,
};
6 changes: 6 additions & 0 deletions .stylelintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
coverage/
build/
public/
node_modules/
vendor/
includes/css/
14 changes: 14 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"pathMappings": {
"/var/www/html/wp-content/plugins/Spotmap": "${workspaceFolder}"
}
}
]
}
21 changes: 21 additions & 0 deletions .wp-env.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"core": null,
"plugins": [ "." ],
"config": {
"WP_DEBUG": true,
"WP_DEBUG_LOG": true,
"SCRIPT_DEBUG": true
},
"env": {
"development": {
"config": {
"WP_DEBUG": true,
"WP_DEBUG_LOG": true,
"SCRIPT_DEBUG": true
}
}
},
"lifecycleScripts": {
"afterStart": "node examples/setup-dev.js"
}
}
128 changes: 128 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What This Is

Spotmap is a WordPress plugin that displays GPS tracking data from SPOT devices on interactive Leaflet maps. It provides a Gutenberg block and shortcodes for embedding maps in posts/pages.

## Editing conventions

- This project uses **4 spaces** for indentation throughout — never tabs.
- When using the Edit tool, use 4-space indentation in `old_string`/`new_string`.
- Run `npm run format` (JS/TS/CSS) and `npm run format:php` (PHP) to auto-format code.
- add phpunit test with sample data to rename a feed - what happens if the feedname already exists? (this might wanted but on the UI we should get a warning that must be accepted)

## Build Commands

```bash
# Development (watch mode, keeps source maps)
npm run start

# Production build
npm run build

# WordPress environment (Docker)
npm run env:start
npm run env:stop

# After env:destroy + env:start, create the test DB once:
npm run wp-env -- run cli bash -- -c "mysql -h mysql -u root -ppassword -e 'CREATE DATABASE IF NOT EXISTS wordpress_test'"

# Tests (run inside the wp-env Docker container)
npm run test:js # Jest unit tests (TypeScript/JS)
npm run test:php # PHP unit tests (~31 s)
npm run composer -- run test:coverage # PHP coverage report

# Lint
npm run lint:js
npm run lint:css
npm run lint:php

# Format
npm run format # JS/TS/CSS (Prettier via wp-scripts)
npm run format:php # PHP (php-cs-fixer via wp-env; requires env:start + composer install)

# Package plugin zip
npm run plugin-zip
```

`npm run build` runs `copy-deps:prod` (strips source maps) then `wp-scripts build`.
`npm run start` runs `copy-deps` (preserves source maps) then `wp-scripts start`.

PHP tests live in `tests/` and run against a real WordPress environment (requires `npm run env:start`). JS tests in `src/**/__tests__/` run standalone via Jest.

## Architecture Overview

### PHP Backend (`includes/`, `admin/`, `public/`)

| File | Role |
|------|------|
| `spotmap.php` | Entry point — instantiates `Spotmap` class |
| `includes/class-spotmap.php` | Orchestrator — loads dependencies, registers hooks via `Spotmap_Loader` |
| `includes/class-spotmap-options.php` | Admin options and marker defaults |
| `includes/class-spotmap-database.php` | DB layer — table `wp_spotmap_points` |
| `includes/class-spotmap-api-crawler.php` | Fetches data from SPOT API |
| `admin/class-spotmap-admin.php` | Admin settings page |
| `public/class-spotmap-public.php` | Enqueues scripts, registers shortcodes |
| `public/render-block.php` | Server-side renderer for the Gutenberg dynamic block |

Composer dependency `symfony/yaml` is vendor-prefixed under the `Spotmap\` namespace via Strauss.

### Frontend Build (`src/`)

Two webpack entry points (configured in `webpack.config.js`):

1. **`src/spotmap/`** — Gutenberg block (React/JSX)
- `block.json` — block definition, 17 attributes, API v3, dynamic block (save returns null)
- `edit.jsx` — ~33KB React editor component with live preview
- Block is registered via `build/spotmap/` by `register_block_type()`

2. **`src/map-engine/`** — TypeScript map engine compiled to `build/spotmap-map/index.js`
- `index.ts` — exposes `window.Spotmap`
- `Spotmap.ts` — main class, `initMap()` entry point
- `DataFetcher.ts`, `LayerManager.ts`, `MarkerManager.ts`, `LineManager.ts`, `BoundsManager.ts`, `ButtonManager.ts`, `TableRenderer.ts`
- `types.ts` — all TypeScript interfaces (`SpotmapOptions`, `SpotPoint`, `FeedStyle`, `GpxTrackConfig`, etc.)

### Runtime Flow

`render-block.php` outputs a `<div>` with an inline `<script>` that calls `new Spotmap(options).initMap()`. Options are JSON-encoded block attributes. The map engine fetches GPS points via AJAX (`wp_ajax_spotmap`).

The editor (`edit.jsx`) calls `window.Spotmap` directly for the live preview, using `spotmapjsobj` (localized via `wp_localize_script`) which contains `ajaxUrl`, `maps`, `overlays`, `feeds`, `defaultValues`, and marker config.

### Frontend Dependencies (`public/`)

`scripts/copy-deps.js` copies npm packages (Leaflet, Leaflet plugins, Font Awesome) from `node_modules/` into `public/`. The `public/` directory is generated — do not edit files there directly.

Key deps: `leaflet`, `leaflet-fullscreen`, `leaflet-gpx`, `leaflet-easybutton`, `leaflet-textpath`, `leaflet.tilelayer.swiss`, `beautifymarker`, `@fortawesome/fontawesome-free`.

### Database Schema

Table `wp_spotmap_points`:
`id, type, time, latitude, longitude, altitude, battery_status, message, custom_message, feed_name, feed_id, model, device_name, local_timezone`

### Tile Layer Config

`config/maps.yaml` defines 25+ tile layer providers. Loaded server-side, passed to the block editor and frontend via `spotmapjsobj`.

## Known TODOs

- **`latestUnixtimeByFeed` redundant state** (`Spotmap.ts`): The `Map<string, number>` tracking the latest unixtime per feed duplicates `feed.points.at(-1)?.unixtime`, since `MarkerManager.addPoint()` already pushes to `feed.points`. Refactor the auto-reload polling loop to read `feed.points` directly and remove the Map.
- **Duplicated polling pattern**: `Spotmap.ts` and `TableRenderer.ts` share ~70 lines of identical timeout/visibility-change polling logic. Consider extracting a `VisibilityAwarePoller` utility class into `utils.ts`.
- **Feed style defaults should move to the map engine**: `render-block.php` and the shortcode both pre-populate per-feed `styles` (color, splitLines) from WP admin defaults in PHP. Ideally `LayerManager.getFeedColor()` and `getFeedSplitLines()` would fall back to `spotmapjsobj.defaultValues` (already available at runtime) and cycle colors by feed index from `options.feeds`, allowing both renderers to pass a sparse/empty `styles`. The PHP pre-population in `render-block.php` was added to match shortcode behaviour for now.
- move maps.yaml into wp_options table? and potentially make it so that the user can modify/add via GUI?
- the ajax call to retrieve points should be prefixed with spotmap_
- feat: use blog metadata to store lat/lng / inject in every post a small map where the user can select the location of this post.
- **[perf T1] Insertion-time deduplication for stationary trackers**: In `insert_row()`, before inserting check if the new point is within ~25 m of the last stored point for the same feed AND < 10 min apart → skip insert (or update the existing point's `time`). Mirrors the existing client-side `removeClosePoints()` logic in `DataFetcher.ts`. Handles Teltonika/OsmAnd parked-vehicle flooding.
- **[perf T1] Server-side point decimation**: When the queried range returns too many rows, reduce to a target (e.g. 5,000 pts) before sending JSON. Simple approaches (MOD timestamp, N-th row, time-bucket) all have flaws for GPS tracks with irregular density — they can drop geometrically significant points on sparse stretches. **Douglas-Peucker** (simplify by perpendicular deviation, ε configurable) is the right algorithm: it preserves shape-defining points regardless of time distribution. Needs PHP implementation since MySQL has no native DP. Consider running DP per feed per query, or as a pre-simplification pass stored back to DB.
- **[perf T2] Block-level `maxPoints` attribute**: Add a `maxPoints` block attribute (default e.g. 5000). The server decimates to that count using time-based sampling before responding. Let editors tune the trade-off between detail and load time per map.
- **[perf T2] Admin: DB pruning action**: Add an admin button that previews how many points would be removed per feed by a Douglas-Peucker simplification pass (ε configurable), then executes the prune on confirmation. Run against points older than N days to preserve recent full-resolution data.


## Key Conventions

- **Dynamic block**: `save()` returns `null`; all rendering is in `render-block.php`.
- **Marker shape/icon config** belongs in WP admin (Options), not the block editor UI.
- **`public/` is generated** — edit source files in `src/` and `node_modules/` and rebuild.
- **No `"type": "commonjs"`** in `package.json` — this is intentional.
- **Vendor prefix**: Composer packages live under `Spotmap\` namespace via Strauss; regenerate with `composer install` then `composer exec strauss`.
Loading