Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions admin/class-spotmap-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ public function enqueue_scripts($hook)
$map_asset['version'],
true
);
if (file_exists(plugin_dir_path(__DIR__) . 'build/spotmap-map.css')) {
wp_enqueue_style(
'spotmap-map-admin-css',
plugin_dir_url(__DIR__) . 'build/spotmap-map.css',
[ 'spotmap-leaflet' ],
$map_asset['version']
);
}
wp_localize_script('spotmap-map-admin', 'spotmapjsobj', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'maps' => $this->get_maps(),
Expand Down
9 changes: 5 additions & 4 deletions pluginreadme.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,22 +195,23 @@ Head over to the [support forum](https://wordpress.org/support/plugin/spotmap/)
### 1.0.0

* New: **Garmin inReach** device support — receive positions via MapShare feed
* New: **OsmAnd** device support — receive positions via HTTP
* New: **Teltonika** device support — direct integration
* New: **OsmAnd** device support — receive positions via HTTPS
* New: **Teltonika** device support — receive positions via HTTPS
* New: **Post Locations** — assign GPS coordinates to any post or page via the block editor sidebar; posts appear on the map as markers that link to the article
* New: **Photo EXIF GPS** — images with GPS data from the media library can appear on the map if configured
* New: Built-in **GPX manager** — upload and manage GPX files
* New: Rich **time filtering** with relative ranges (last X hours/days) and absolute date ranges
* New: Interactive **data table** linked to map — click a row to zoom to that position
* New: **GPX export** — convert tracked positions to GPX files
* Improved: Full **Gutenberg block** with live preview and block sidebar settings
* Improved: Full **Gutenberg block** with live preview and block settings
* Improved: In unlikely cases WP simply deleted the cron job to fetch new points, this will not happen anymore
* Improved: Map engine rewritten to support **faster map rendering** with large numbers of points
* Improved: last-point marker is now customizable via additional CSS
* Fix: `id` column gains `AUTO_INCREMENT` (was missing in 0.11.2); migration runs automatically on update
* And many more fixes ...

## Upgrade Notice

### 1.0.0

Major update with multi-device support (OsmAnd, Teltonika), GPX manager, time filtering, data table, photo EXIF display, and significant performance improvements. The database is migrated automatically — no manual SQL required. Your existing GPS data is preserved.
Major update with multi-device support (OsmAnd, Garmin InReach), GPX manager, time filtering, data table, photo EXIF display, and significant performance improvements. The database is migrated automatically — no manual SQL required. Your existing GPS data is preserved.
9 changes: 9 additions & 0 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
font-size: 12px;
}

/* Prevent themes that set svg { max-width: 100%; height: auto; } from
collapsing Leaflet renderer SVGs to 0×0. The pane <div> is position:absolute
with no explicit width, so 100% resolves to 0, wiping out the SVG dimensions
that Leaflet sets via attributes. */
.leaflet-pane > svg {
max-width: none !important;
height: auto !important;
}

/* map toggle work with twenty twenty template */

/* .leaflet-control-layers-list {
Expand Down
7 changes: 6 additions & 1 deletion src/map-engine/Spotmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,12 @@ export class Spotmap {
this.layers,
dbg
);
const canvasRenderer = L.svg( { pane: 'markerPane' } );
// Use a dedicated pane (z-index 450, above overlayPane 400) so that
// circle-dot markers render above polylines. Avoids theme CSS targeting
// .leaflet-marker-pane which can set the SVG to 0×0.
this.map.createPane( 'spotmapCirclePane' );
( this.map.getPane( 'spotmapCirclePane' ) as HTMLElement ).style.zIndex = '450';
const canvasRenderer = L.svg( { pane: 'spotmapCirclePane' } );
this.markerManager = new MarkerManager(
this.map,
this.layers,
Expand Down
6 changes: 6 additions & 0 deletions src/map-engine/__tests__/Spotmap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ let capturedGpxEmitter: ReturnType< typeof createEventEmitter > | null;
function buildLeafletMock() {
capturedGpxEmitter = null;

const mockPane = Object.assign( document.createElement( 'div' ), {
style: { zIndex: '' },
} );

const mockMap = {
scrollWheelZoom: { enable: jest.fn(), disable: jest.fn() },
once: jest.fn(),
Expand All @@ -69,6 +73,8 @@ function buildLeafletMock() {
invalidateSize: jest.fn(),
removeLayer: jest.fn(),
attributionControl: { setPrefix: jest.fn() },
createPane: jest.fn(),
getPane: jest.fn().mockReturnValue( mockPane ),
};

const mockLayerControl = {
Expand Down
3 changes: 3 additions & 0 deletions src/spotmap-admin/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import MarkersTab from './tabs/MarkersTab';
import TokensTab from './tabs/TokensTab';
import DefaultsTab from './tabs/DefaultsTab';
import EditPointsTab from './tabs/EditPointsTab';
import ShortcodeTab from './tabs/ShortcodeTab';

const TABS = [
{ name: 'feeds', title: 'Feeds' },
{ name: 'markers', title: 'Markers' },
{ name: 'tokens', title: 'API Tokens' },
{ name: 'defaults', title: 'Defaults' },
{ name: 'edit-points', title: 'Edit Points' },
{ name: 'shortcode', title: 'Shortcode Generator' },
];

const TAB_NAMES = TABS.map( ( t ) => t.name );
Expand Down Expand Up @@ -125,6 +127,7 @@ export default function App() {
onNoticeChange={ handleNoticeChange }
/>
),
shortcode: <ShortcodeTab />,
} )[ tab.name ]
}
</TabPanel>
Expand Down
111 changes: 111 additions & 0 deletions src/spotmap-admin/components/FeedsToolbarGroup.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
Button,
CheckboxControl,
Dropdown,
Flex,
FlexItem,
ToolbarButton,
ToolbarGroup,
} from '@wordpress/components';
import { brush } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';

/**
* Toolbar group for selecting which feeds appear on the map.
*
* @param {Object} props
* @param {string[]} props.feeds Selected feed names.
* @param {string[]} props.allFeeds All available feed names.
* @param {Object} props.styles Map of feedName → { color, ... }.
* @param {Function} props.onToggle Called with (feedName, checked).
* @param {Function|null} props.onStyle Optional. When provided, shows a
* brush button per selected feed and
* calls onStyle(feedName) on click.
*/
export default function FeedsToolbarGroup( {
feeds,
allFeeds,
styles,
onToggle,
onStyle = null,
} ) {
return (
<ToolbarGroup>
<Dropdown
popoverProps={ { placement: 'bottom-start' } }
renderToggle={ ( { isOpen, onToggle: toggleDropdown } ) => (
<ToolbarButton
icon="rss"
label={ __( 'Feeds' ) }
onClick={ toggleDropdown }
isPressed={ isOpen }
>
{ __( 'Feeds' ) }
</ToolbarButton>
) }
renderContent={ ( { onClose } ) => (
<div
style={ {
padding: '8px',
minWidth: '220px',
} }
>
{ allFeeds.map( ( feed ) => {
const isSelected = feeds.includes( feed );
return (
<Flex
key={ feed }
gap={ 2 }
align="center"
style={ { marginBottom: '4px' } }
>
<FlexItem isBlock>
<CheckboxControl
__nextHasNoMarginBottom
label={ feed }
checked={ isSelected }
onChange={ ( checked ) =>
onToggle( feed, checked )
}
/>
</FlexItem>
{ onStyle && (
<Button
icon={ brush }
label={
__( 'Style' ) + ' ' + feed
}
size="small"
variant="tertiary"
style={ {
visibility: isSelected
? 'visible'
: 'hidden',
} }
onClick={ () => {
onClose();
onStyle( feed );
} }
/>
) }
<span
style={ {
display: 'block',
width: '16px',
height: '16px',
borderRadius: '50%',
background:
styles?.[ feed ]?.color ||
'blue',
flexShrink: 0,
} }
/>
</Flex>
);
} ) }
</div>
) }
/>
</ToolbarGroup>
);
}
10 changes: 10 additions & 0 deletions src/spotmap-admin/mapDefaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function getDefaultMaps() {
const dv = window.spotmapjsobj?.defaultValues?.maps;
if ( dv ) {
return dv
.split( ',' )
.map( ( m ) => m.trim() )
.filter( Boolean );
}
return Object.keys( window.spotmapjsobj?.maps ?? {} ).slice( 0, 1 );
}
88 changes: 8 additions & 80 deletions src/spotmap-admin/tabs/EditPointsTab.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { useState, useEffect, useRef, useCallback } from '@wordpress/element';
import {
Button,
CheckboxControl,
Dropdown,
Flex,
FlexItem,
Notice,
Toolbar,
ToolbarButton,
ToolbarGroup,
} from '@wordpress/components';
import { undo } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';
import MapsToolbarGroup from '../../spotmap/components/MapsToolbarGroup';
import TimeToolbarGroup from '../../spotmap/components/TimeToolbarGroup';
import FeedsToolbarGroup from '../components/FeedsToolbarGroup';
import * as api from '../api';
import { getDefaultMaps } from '../mapDefaults';

const ALL_FEEDS = ( window.spotmapAdminData.feeds ?? [] ).filter( Boolean );

Expand All @@ -26,17 +22,6 @@ const DEFAULT_FEED_STYLE = {
visible: true,
};

function getDefaultMaps() {
const dv = window.spotmapjsobj?.defaultValues?.maps;
if ( dv ) {
return dv
.split( ',' )
.map( ( m ) => m.trim() )
.filter( Boolean );
}
return Object.keys( window.spotmapjsobj?.maps ?? {} ).slice( 0, 1 );
}

export default function EditPointsTab( { onNoticeChange } ) {
const [ feeds, setFeeds ] = useState( ALL_FEEDS.slice( 0, 1 ) );
const [ styles, setStyles ] = useState( () => {
Expand Down Expand Up @@ -361,69 +346,12 @@ export default function EditPointsTab( { onNoticeChange } ) {
} }
>
<Toolbar label={ __( 'Edit points controls' ) }>
{ /* Feeds */ }
<ToolbarGroup>
<Dropdown
popoverProps={ { placement: 'bottom-start' } }
renderToggle={ ( { isOpen, onToggle } ) => (
<ToolbarButton
icon="rss"
label={ __( 'Feeds' ) }
onClick={ onToggle }
isPressed={ isOpen }
>
{ __( 'Feeds' ) }
</ToolbarButton>
) }
renderContent={ () => (
<div
style={ {
padding: '8px',
minWidth: '200px',
} }
>
{ ALL_FEEDS.map( ( feed ) => (
<Flex
key={ feed }
gap={ 2 }
align="center"
style={ { marginBottom: '4px' } }
>
<FlexItem isBlock>
<CheckboxControl
__nextHasNoMarginBottom
label={ feed }
checked={ feeds.includes(
feed
) }
onChange={ ( checked ) =>
toggleFeed(
feed,
checked
)
}
/>
</FlexItem>
<span
style={ {
display: 'block',
width: '16px',
height: '16px',
borderRadius: '50%',
background:
styles?.[ feed ]
?.color || 'blue',
flexShrink: 0,
} }
/>
</Flex>
) ) }
</div>
) }
/>
</ToolbarGroup>

{ /* Maps + overlays */ }
<FeedsToolbarGroup
feeds={ feeds }
allFeeds={ ALL_FEEDS }
styles={ styles }
onToggle={ toggleFeed }
/>
<MapsToolbarGroup
maps={ maps }
mapOverlays={ mapOverlays }
Expand Down
Loading
Loading