Files
speckle-server/packages/frontend-2/lib/viewer/composables/setup/embed.ts
T
Kristaps Fabians Geikins 4d67ac41b7 feat(fe2): safe logger composable for logging anywhere (#5288)
* feat(fe2): safe logger composable for logging anywhere

* devlogger adjusted too

* allow suppressing fallback logger usage

* report fallback only once
2025-08-21 13:58:22 +03:00

137 lines
3.9 KiB
TypeScript

import { writableAsyncComputed } from '~/lib/common/composables/async'
import { useScopedState } from '~/lib/common/composables/scopedState'
import { ViewerHashStateKeys } from '~/lib/viewer/composables/setup/urlHashState'
import { useConditionalViewerRendering } from '~/lib/viewer/composables/ui'
import { useRouteHashState } from '~~/lib/common/composables/url'
export type EmbedOptions = {
isEnabled?: boolean
isTransparent?: boolean
hideControls?: boolean
hideSelectionInfo?: boolean
disableModelLink?: boolean
noScroll?: boolean
manualLoad?: boolean
hideSpeckleBranding?: boolean
}
export function isEmbedOptions(obj: unknown): obj is EmbedOptions {
if (typeof obj === 'object' && obj !== null) {
const possibleOptions = obj as Partial<EmbedOptions>
return Object.keys(possibleOptions).every(
(key) =>
[
'isEnabled',
'isTransparent',
'hideControls',
'hideSelectionInfo',
'disableModelLink',
'noScroll',
'manualLoad',
'hideSpeckleBranding'
].includes(key) &&
typeof possibleOptions[key as keyof EmbedOptions] === 'boolean'
)
}
return false
}
export function deserializeEmbedOptions(embedString: string | null): EmbedOptions {
const { logger } = useSafeLogger()
if (!embedString) {
return { isEnabled: false }
}
try {
const parsed: unknown = JSON.parse(embedString)
if (isEmbedOptions(parsed)) {
return { ...parsed, isEnabled: true }
}
logger().error('Parsed object is not of type EmbedOptions')
} catch (error) {
logger().error(error)
}
return { isEnabled: false }
}
export function useEmbedState() {
const { hashState } = useRouteHashState()
const embedOptions = writableAsyncComputed({
get: () => {
const embedString = hashState.value[ViewerHashStateKeys.EmbedOptions]
return deserializeEmbedOptions(embedString)
},
set: async (newOptions) => {
const embedString = newOptions ? JSON.stringify(newOptions) : null
await hashState.update({
...hashState.value,
[ViewerHashStateKeys.EmbedOptions]: embedString
})
},
initialState: null,
asyncRead: false
})
return { embedOptions }
}
const embedStateScopedKey = Symbol('EmbedStateScopedKey')
export function useEmbed() {
const { embedOptions } = useEmbedState()
const { showControls } = useConditionalViewerRendering()
// useScopedState so that we don't keep creating new computeds
return useScopedState(embedStateScopedKey, () => {
const createComputed = <K extends keyof EmbedOptions>(key: K) =>
writableAsyncComputed({
get: () => embedOptions.value?.[key],
set: async (newVal) => {
await embedOptions.update({
...(embedOptions.value ?? {}),
...{
[key]: newVal
}
})
},
initialState: null,
asyncRead: false
})
const isEnabled = createComputed('isEnabled')
const isTransparent = createComputed('isTransparent')
const disableModelLink = createComputed('disableModelLink')
const hideSpeckleBranding = createComputed('hideSpeckleBranding')
const hideSelectionInfo = createComputed('hideSelectionInfo')
const noScroll = createComputed('noScroll')
const manualLoad = createComputed('manualLoad')
const showControlsNew = writableAsyncComputed({
get: () => showControls.value,
set: async (newVal) =>
await embedOptions.update({
...(embedOptions.value ?? {}),
...{
hideControls: !(newVal || undefined)
}
}),
initialState: null,
asyncRead: false
})
return {
isEnabled,
isEmbedEnabled: isEnabled,
isTransparent,
showControls: showControlsNew,
hideSelectionInfo,
disableModelLink,
hideSpeckleBranding,
noScroll,
manualLoad
}
})
}