Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7782bc9
added prettier
Aug 12, 2025
39a8f46
feat(map): persist symbol properties preference
gambon2010 Aug 12, 2025
bf641b4
Merge pull request #1 from gambon2010/codex/implement-persistent-sign…
gambon2010 Aug 12, 2025
71e4bab
feat: expose symbolPropertiesShowing signal
gambon2010 Aug 12, 2025
8aae23f
Merge pull request #2 from gambon2010/codex/pass-symbolpropertiesshow…
gambon2010 Aug 12, 2025
f0cb892
test(emitter): assert off without handler
gambon2010 Aug 12, 2025
1cd7655
Merge pull request #3 from gambon2010/codex/update-emitter-tests-to-h…
gambon2010 Aug 12, 2025
335b7d1
Correct level utility JSDoc annotations
gambon2010 Aug 12, 2025
cb20172
Merge pull request #4 from gambon2010/codex/update-jsdoc-comments-in-…
gambon2010 Aug 12, 2025
ad5cdf8
feat: make import async
gambon2010 Aug 12, 2025
a494e90
Fix typos in PDF header comment
gambon2010 Aug 12, 2025
179b256
Merge pull request #6 from gambon2010/codex/fix-typos-in-pdf.js-comments
gambon2010 Aug 12, 2025
579a766
fix cmdOrCtrl modifier detection
gambon2010 Aug 12, 2025
df0362f
Merge pull request #7 from gambon2010/codex/locate-and-fix-an-importa…
gambon2010 Aug 12, 2025
d32466f
Link symbol properties showing to modifiers
gambon2010 Aug 12, 2025
3befabb
Merge pull request #8 from gambon2010/codex/combine-properties-with-s…
gambon2010 Aug 12, 2025
be3608f
feat: add symbol properties preference
gambon2010 Aug 12, 2025
63ec0ad
Merge pull request #9 from gambon2010/codex/add-get-handler-and-event…
gambon2010 Aug 12, 2025
a6ca4bb
Listen for symbol properties toggle
gambon2010 Aug 13, 2025
e3640e2
Merge pull request #10 from gambon2010/codex/implement-renderer-for-v…
gambon2010 Aug 13, 2025
6576180
refactor map effect cleanup
gambon2010 Aug 13, 2025
3950524
Merge pull request #11 from gambon2010/codex/update-map.js-initializa…
gambon2010 Aug 13, 2025
04dc866
Optimize symbol style updates
gambon2010 Aug 19, 2025
1e986e7
Merge pull request #12 from gambon2010/codex/profile-symbol.js-for-pe…
gambon2010 Aug 19, 2025
353dc95
Merge pull request #5 from gambon2010/codex/edit-store.js-to-make-imp…
gambon2010 Aug 19, 2025
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
6 changes: 2 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module.exports = {
node: true,
mocha: true
},
extends: ['eslint:recommended', 'plugin:react/recommended', 'standard'],
extends: ['eslint:recommended', 'plugin:react/recommended', 'standard', 'prettier'],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
Expand All @@ -23,9 +23,7 @@ module.exports = {
ecmaVersion: 2018,
sourceType: 'module'
},
plugins: [
'react', 'react-hooks'
],
plugins: ['react', 'react-hooks'],
rules: {
'no-console': 'off',
'no-multiple-empty-lines': 'off',
Expand Down
19 changes: 19 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"trailingComma": "none",
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"printWidth": 160,
"jsxBracketSameLine": true,
"proseWrap": "never",
"endOfLine": "crlf",
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ Copyright (c) Syncpoint GmbH. All rights reserved.
Licensed under the [GNU Affero GPL v3](LICENSE.md) License.

When using the ODIN or other GitHub logos, be sure to follow the [GitHub logo guidelines](https://github.com/logos).

## Performance

Recent updates improve rendering performance on large maps by caching symbol style modifiers and reusing style instances. Style updates are throttled using a circuit breaker to avoid excessive recomputation when text visibility is toggled rapidly.
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"electron": "^31.2.1",
"electron-builder": "^24.6.4",
"electron-updater": "^6.1.4",
"eslint-config-prettier": "^10.1.8",
"eslint-config-standard": "^17.0.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react-hooks": "^4.6.0",
Expand Down
9 changes: 9 additions & 0 deletions src/main/menu/view-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export default options => {
const graticule = preferences.graticule
const sidebarShowing = preferences['ui.sidebar.showing'] ?? true
const toolbarShowing = preferences['ui.toolbar.showing'] ?? true
const symbolPropertiesShowing = preferences['ui.symbolProperties.showing'] ?? true

return [{
label: 'View',
Expand Down Expand Up @@ -94,6 +95,14 @@ export default options => {
click: ({ checked }, browserWindow) => {
if (browserWindow) browserWindow.webContents.send('VIEW_SHOW_TOOLBAR', checked)
}
},
{
label: 'Show Symbol Properties',
type: 'checkbox',
checked: symbolPropertiesShowing,
click: ({ checked }, browserWindow) => {
if (browserWindow) browserWindow.webContents.send('VIEW_SYMBOL_PROPERTIES', checked)
}
}
]
},
Expand Down
26 changes: 26 additions & 0 deletions src/main/menu/view-menu.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import assert from 'assert'
import viewMenu from './view-menu.js'

describe('view-menu symbol properties item', () => {
const findSymbolItem = (prefs = {}) => {
const menu = viewMenu({ preferences: prefs })[0]
const appearance = menu.submenu.find(item => item.label === 'Appearance')
return appearance.submenu.find(item => item.label === 'Show Symbol Properties')
}

it('reflects preference state', () => {
const unchecked = findSymbolItem({ 'ui.symbolProperties.showing': false })
assert.strictEqual(unchecked.checked, false)

const checked = findSymbolItem({ 'ui.symbolProperties.showing': true })
assert.strictEqual(checked.checked, true)
})

it('sends VIEW_SYMBOL_PROPERTIES on click', () => {
const item = findSymbolItem()
let sent
const browserWindow = { webContents: { send: (...args) => { sent = args } } }
item.click({ checked: false }, browserWindow)
assert.deepStrictEqual(sent, ['VIEW_SYMBOL_PROPERTIES', false])
})
})
74 changes: 32 additions & 42 deletions src/renderer/components/map/Map.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react'
import Signal from '@syncpoint/signal'
import 'ol/ol.css'
import * as ol from 'ol'
import { ScaleLine, Rotate } from 'ol/control'
Expand All @@ -14,6 +15,7 @@ import registerEventHandlers from './eventHandlers'
import registerGraticules from './graticules'
import measure from '../../ol/interaction/measure'
import print from '../print'
import mapEffect from './effect'
import './Map.css'
import './ScaleLine.css'

Expand All @@ -24,54 +26,42 @@ import './ScaleLine.css'
export const Map = () => {
const services = useServices()
const ref = React.useRef()
const symbolPropertiesShowing = React.useMemo(() => Signal.of(true), [])

const effect = async () => {
const view = await createMapView(services)
const sources = await vectorSources(services)
const styles = createLayerStyles(services, sources)
const vectorLayers = createVectorLayers(sources, styles)

const controlsTarget = document.getElementById('osd')
const controls = [
new Rotate({ target: controlsTarget }), // macOS: OPTION + SHIFT + DRAG
new ScaleLine({ bar: true, text: true, minWidth: 128, target: controlsTarget })
]

const tileLayers = await createTileLayers(services)
const layers = [...tileLayers, ...Object.values(vectorLayers)]

const map = new ol.Map({
target: 'map',
controls,
layers,
view,
interactions: []
})
React.useEffect(() => {
const key = 'ui.symbolProperties.showing'

defaultInteractions({
hitTolerance: 3,
map,
services,
sources,
styles
})
;(async () => {
const showing = await services.preferencesStore.getSymbolPropertiesShowing()
symbolPropertiesShowing(showing)
})()

registerEventHandlers({ services, sources, vectorLayers, map })
registerGraticules({ services, map })
print({ map, services })
const handle = ({ value }) => symbolPropertiesShowing(value)
services.preferencesStore.on(key, handle)

// Force map resize on container resize:
const observer = new ResizeObserver(() => map.updateSize())
observer.observe(ref.current)
return () => services.preferencesStore.off(key, handle)
}, [services.preferencesStore, symbolPropertiesShowing])

measure({ services, map })
}
const effect = mapEffect({
services,
ref,
symbolPropertiesShowing,
ol,
ScaleLine,
Rotate,
defaultInteractions,
vectorSources,
createMapView,
createLayerStyles,
createVectorLayers,
createTileLayers,
registerEventHandlers,
registerGraticules,
measure,
print
})

/* eslint-disable react-hooks/exhaustive-deps */
React.useEffect(() => {
(async () => await effect())()
}, [])
/* eslint-enable react-hooks/exhaustive-deps */
React.useEffect(() => effect(), [])

return <div
id='map'
Expand Down
72 changes: 72 additions & 0 deletions src/renderer/components/map/effect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
export default options => {
const {
services,
ref,
symbolPropertiesShowing,
ol,
ScaleLine,
Rotate,
defaultInteractions,
vectorSources,
createMapView,
createLayerStyles,
createVectorLayers,
createTileLayers,
registerEventHandlers,
registerGraticules,
measure,
print
} = options

return () => {
let map
let observer

;(async () => {
const view = await createMapView(services)
const sources = await vectorSources({ ...services, symbolPropertiesShowing })
const styles = createLayerStyles(services, sources)
const vectorLayers = createVectorLayers(sources, styles)

const controlsTarget = document.getElementById('osd')
const controls = [
new Rotate({ target: controlsTarget }),
new ScaleLine({ bar: true, text: true, minWidth: 128, target: controlsTarget })
]

const tileLayers = await createTileLayers(services)
const layers = [...tileLayers, ...Object.values(vectorLayers)]

map = new ol.Map({
target: 'map',
controls,
layers,
view,
interactions: []
})

defaultInteractions({
hitTolerance: 3,
map,
services,
sources,
styles
})

registerEventHandlers({ services, sources, vectorLayers, map })
registerGraticules({ services, map })
print({ map, services })

observer = new ResizeObserver(() => map.updateSize())
observer.observe(ref.current)

measure({ services, map })
})()

return () => {
if (observer) observer.disconnect()
if (map) map.dispose()
}
}
}

52 changes: 52 additions & 0 deletions src/renderer/components/map/effect.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import assert from 'assert'
import effect from './effect'

describe('map effect', () => {
it('disconnects observer and disposes map on cleanup', async function () {
let disposed = false
const map = {
dispose: () => { disposed = true },
updateSize: () => {}
}

let disconnected = false
const OriginalRO = global.ResizeObserver
const OriginalDoc = global.document
class RO {
constructor () {}
observe () {}
disconnect () { disconnected = true }
}
global.ResizeObserver = RO
global.document = { getElementById: () => ({}) }

const init = effect({
services: {},
ref: { current: {} },
symbolPropertiesShowing: () => {},
ol: { Map: function () { return map } },
ScaleLine: function () {},
Rotate: function () {},
defaultInteractions: () => {},
vectorSources: async () => ({}),
createMapView: async () => ({}),
createLayerStyles: () => ({}),
createVectorLayers: () => ({}),
createTileLayers: async () => ([]),
registerEventHandlers: () => {},
registerGraticules: () => {},
measure: () => {},
print: () => {}
})

const cleanup = init()
await new Promise(resolve => setImmediate(resolve))
cleanup()
global.ResizeObserver = OriginalRO
global.document = OriginalDoc

assert.ok(disposed)
assert.ok(disconnected)
})
})

6 changes: 3 additions & 3 deletions src/renderer/components/print/pdf.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading