Skip to content
Open
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
7 changes: 5 additions & 2 deletions docs/guides/unitpreferences.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ Request the metadata for any path:
"formula": "value * 1.94384",
"inverseFormula": "value / 1.94384",
"symbol": "kn",
"displayFormat": "0.0"
"displayFormat": "0.0",
"override": {}
}
}
```
Expand All @@ -129,6 +130,7 @@ The `displayUnits` object provides everything you need to display the value:
- **inverseFormula**: A Math.js expression to convert back from the display unit to SI (useful for user input).
- **symbol**: The symbol to display next to the value.
- **displayFormat**: (Optional) A format pattern for consistency (e.g., "0.0" for one decimal place).
- **override**: The part of the answer the path itself chose rather than the active preset, as `targetUnit` and `displayFormat`. Empty when the path follows the preset. Display code can ignore this; an editor needs it to tell "knots because this path asks for knots" from "knots because the preset says so". A `PUT` of the whole object is read the same way, so saving a response back does not turn the preset's current choices into a path override.

### WebSocket Stream

Expand Down Expand Up @@ -173,7 +175,8 @@ ws.onopen = () => {
"formula": "value * 1.94384",
"inverseFormula": "value / 1.94384",
"symbol": "kn",
"displayFormat": "0.0"
"displayFormat": "0.0",
"override": {}
}
}
}
Expand Down
13 changes: 1 addition & 12 deletions packages/server-admin-ui/src/views/DataBrowser/Meta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -584,22 +584,11 @@ const METAFIELDRENDERERS: Record<
}

const saveMeta = (path: string, meta: MetaData) => {
// Mark displayUnits as explicit (manually set) so patterns don't overwrite
const metaToSave = {
...meta,
displayUnits: meta.displayUnits
? {
...meta.displayUnits,
explicit: true
}
: undefined
}

fetch(`/signalk/v1/api/vessels/self/${pathToUrlSegments(path)}/meta`, {
method: 'PUT',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ value: metaToSave })
body: JSON.stringify({ value: meta })
})
}

Expand Down
57 changes: 50 additions & 7 deletions src/put.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
import * as skConfig from './config/config'
import { ConfigApp } from './config/config'
import { getMetadata } from '@signalk/path-metadata'
import { validateCategoryAssignment } from './unitpreferences'
import {
resolveDisplayUnits,
stripResolvedDisplayUnits,
validateCategoryAssignment
} from './unitpreferences'
import { DisplayUnitsMetadata } from './unitpreferences/types'
import { WithSecurityStrategy } from './security'

const debug = createDebug('signalk-server:put')
Expand Down Expand Up @@ -98,8 +103,18 @@ interface SkRequest extends Request {
}
}

// Metadata is stored per path but resolved per user, so the handler needs the
// requesting user that the generic ActionHandler signature does not carry.
type MetaActionHandler = (
context: string,
path: string,
value: unknown,
callback: (result: ActionResult) => void,
username?: string
) => ActionResult | void

const actionHandlers: ActionHandlers = {}
let putMetaHandler: ActionHandler
let putMetaHandler: MetaActionHandler
let deleteMetaHandler: DeleteHandler
let putNotificationHandler: (
context: string,
Expand Down Expand Up @@ -181,7 +196,7 @@ export function start(app: PutApp): void {
})
})

putMetaHandler = (context, path, value, cb) => {
putMetaHandler = (context, path, value, cb, username) => {
const parts = path.split('.')
let metaPath = path
let metaValue = value as Record<string, unknown>
Expand Down Expand Up @@ -234,6 +249,16 @@ export function start(app: PutApp): void {
}

const previousMeta = app.config.baseDeltaEditor.getMeta(context, metaPath)

if (metaValue.displayUnits) {
metaValue.displayUnits = stripResolvedDisplayUnits(
metaValue.displayUnits as DisplayUnitsMetadata,
(previousMeta as { displayUnits?: DisplayUnitsMetadata } | null)
?.displayUnits,
username
)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

app.config.baseDeltaEditor.setMeta(context, metaPath, metaValue)

// Remove fields that were deleted from the in-memory metadata registry
Expand All @@ -250,14 +275,28 @@ export function start(app: PutApp): void {
}
}

// Clients read displayUnits resolved, so the update they get carries the
// conversion rather than the override that was stored.
const metaUpdate: Record<string, unknown> = { ...full_meta, ...metaValue }
if (metaValue.displayUnits) {
const resolved = resolveDisplayUnits(
metaValue.displayUnits as DisplayUnitsMetadata,
(metaValue.units ?? full_meta?.units) as string | undefined,
username
)
if (resolved) {
metaUpdate.displayUnits = resolved
}
}

app.handleMessage('defaults', {
context: 'vessels.self' as Context,
updates: [
{
meta: [
{
path: metaPath as Path,
value: { ...full_meta, ...metaValue }
value: metaUpdate
}
]
}
Expand All @@ -282,8 +321,10 @@ export function start(app: PutApp): void {
}
}

const pathWithContext = context + '.' + path
_set(data, pathWithContext, value)
// Write the merged, normalized object rather than the request value:
// for a single-field PUT the raw value carries none of the
// displayUnits stripping or empty-array normalization done above.
_set(data, context + '.' + metaPath + '.meta', metaValue)

skConfig.writeDefaultsFile(
app as unknown as ConfigApp,
Expand Down Expand Up @@ -535,7 +576,9 @@ export function putPath(
(parts.length > 1 && parts[parts.length - 1] === 'meta') ||
(parts.length > 1 && parts[parts.length - 2] === 'meta')
) {
handler = putMetaHandler
const username = req?.skPrincipal?.identifier
handler = (metaContext, metaPath, value, cb) =>
putMetaHandler(metaContext, metaPath, value, cb, username)
} else {
const handlers = actionHandlers[context]
? actionHandlers[context][path]
Expand Down
6 changes: 5 additions & 1 deletion src/unitpreferences/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@ export {
saveUserPreferences,
DEFAULT_PRESET
} from './loader'
export { resolveDisplayUnits, validateCategoryAssignment } from './resolver'
export {
resolveDisplayUnits,
stripResolvedDisplayUnits,
validateCategoryAssignment
} from './resolver'
export * from './types'
Loading
Loading