Files
speckle-server/packages/frontend-2/lib/viewer/composables/ui.ts
T
Kristaps Fabians Geikins 9427686d42 fix(fe2): various follow mode & thread viewer state sync fixes & improvements (#1595)
* fix(fe2): unfollow on camera move

* WIP new state hydration function

* WIP sync state

* minor cleanup

* fix coloring not being tracked

* fix for post thread close camera pos restore

* supporting duplicate users

* preventing guest commenting + state reset fixes

* fixed guests not receiving viewer comment updates

* post-thread creation opens new thread

* removing gap between 'X is typing' and bubble appearing

* reset filters will also reset colors now

* fixed thread full context

* camera reset fix

* thread reset fix

* fixed router concurrency issues

* followed user avatar fix

* TONS OF DEBUGGING FOR ROUTER QUEUING

* removing queued routing debugging stuff + disabling spotlight cancelation

* WIP async URL updates

* missing authLogger fixed

* fix for broken projection

* fix for bubbles positions not updating correctly

* queued routing cleanup

* fixed spotlight mode disabling unnecessarily

* added back stoplight stop on ctrl

* undid spotlight debugging
2023-05-29 15:20:32 +03:00

274 lines
7.0 KiB
TypeScript

import { timeoutAt } from '@speckle/shared'
import { PropertyInfo } from '@speckle/viewer'
import { until } from '@vueuse/shared'
import { difference, isString, uniq } from 'lodash-es'
import { SpeckleObject } from '~~/lib/common/helpers/sceneExplorer'
import { isNonNullable } from '~~/lib/common/helpers/utils'
import {
useInjectedViewer,
useInjectedViewerInterfaceState,
useInjectedViewerState
} from '~~/lib/viewer/composables/setup'
export function useSectionBoxUtilities() {
const { instance } = useInjectedViewer()
const {
sectionBox,
filters: { selectedObjects }
} = useInjectedViewerInterfaceState()
const isSectionBoxEnabled = computed(() => !!sectionBox.value)
const toggleSectionBox = () => {
if (isSectionBoxEnabled.value) {
sectionBox.value = null
return
}
const objectIds = selectedObjects.value.map((o) => o.id).filter(isNonNullable)
const box = instance.getSectionBoxFromObjects(objectIds)
sectionBox.value = box
}
const sectionBoxOn = () => {
if (!isSectionBoxEnabled.value) {
toggleSectionBox()
}
}
const sectionBoxOff = () => {
sectionBox.value = null
}
return {
isSectionBoxEnabled,
toggleSectionBox,
sectionBoxOn,
sectionBoxOff,
sectionBox
}
}
export function useCameraUtilities() {
const { instance } = useInjectedViewer()
const {
filters: { selectedObjects, isolatedObjectIds },
camera
} = useInjectedViewerInterfaceState()
const zoom = (...args: Parameters<typeof instance.zoom>) => instance.zoom(...args)
const setView = (...args: Parameters<typeof instance.setView>) => {
instance.setView(...args)
}
const truck = (...args: Parameters<typeof instance.cameraHandler.controls.truck>) =>
instance.cameraHandler.controls.truck(...args)
const zoomExtentsOrSelection = () => {
const ids = selectedObjects.value.map((o) => o.id).filter(isNonNullable)
if (ids.length > 0) {
return instance.zoom(ids)
}
if (isolatedObjectIds.value.length) {
return instance.zoom(isolatedObjectIds.value)
}
instance.zoom()
}
const toggleProjection = () => {
camera.isOrthoProjection.value = !camera.isOrthoProjection.value
}
const forceViewToViewerSync = () => {
setView({
position: camera.position.value,
target: camera.target.value
})
}
return {
zoomExtentsOrSelection,
toggleProjection,
camera,
truck,
setView,
zoom,
forceViewToViewerSync
}
}
export function useFilterUtilities() {
// const { instance } = useInjectedViewer()
const { filters, explodeFactor } = useInjectedViewerInterfaceState()
const {
viewer: {
metadata: { availableFilters }
}
} = useInjectedViewerState()
const isolateObjects = (
objectIds: string[],
options?: Partial<{
replace: boolean
}>
) => {
filters.isolatedObjectIds.value = uniq([
...(options?.replace ? [] : filters.isolatedObjectIds.value),
...objectIds
])
// instance.isolateObjects(objectIds, 'utilities', true)
}
const unIsolateObjects = (objectIds: string[]) => {
filters.isolatedObjectIds.value = difference(
filters.isolatedObjectIds.value,
objectIds
)
// instance.unIsolateObjects(objectIds, 'utilities', true)
}
const hideObjects = (
objectIds: string[],
options?: Partial<{
replace: boolean
}>
) => {
filters.hiddenObjectIds.value = uniq([
...(options?.replace ? [] : filters.hiddenObjectIds.value),
...objectIds
])
// instance.hideObjects(objectIds, 'utilities', true)
}
const showObjects = (objectIds: string[]) => {
filters.hiddenObjectIds.value = difference(filters.hiddenObjectIds.value, objectIds)
// instance.showObjects(objectIds, 'utilities', true)
}
/**
* Sets the current filter property. Does not apply it (instruct viewer to color objects).
*/
const setPropertyFilter = (property: PropertyInfo) => {
filters.propertyFilter.filter.value = property
}
/**
* Instructs the viewer to apply the current property filter (color objects).
*/
const applyPropertyFilter = () => {
filters.propertyFilter.isApplied.value = true
}
/**
* Unsets the current property filter.
*/
const removePropertyFilter = () => {
filters.propertyFilter.isApplied.value = false
filters.propertyFilter.filter.value = null
}
/**
* Unapplies the current property filter - removes object colouring
*/
const unApplyPropertyFilter = () => {
filters.propertyFilter.isApplied.value = false
}
const resetFilters = () => {
filters.hiddenObjectIds.value = []
filters.isolatedObjectIds.value = []
filters.propertyFilter.filter.value = null
filters.propertyFilter.isApplied.value = false
explodeFactor.value = 0
// filters.selectedObjects.value = []
}
const waitForAvailableFilter = async (
key: string,
options?: Partial<{ timeout: number }>
) => {
const timeout = options?.timeout || 10000
const res = await Promise.race([
until(availableFilters).toMatch(
(filters) => !!filters?.find((p) => p.key === key)
),
timeoutAt(timeout, 'Waiting for available filter timed out')
])
const filter = res?.find((p) => p.key === key)
return filter as NonNullable<typeof filter>
}
return {
isolateObjects,
unIsolateObjects,
hideObjects,
showObjects,
filters,
setPropertyFilter,
applyPropertyFilter,
removePropertyFilter,
unApplyPropertyFilter,
resetFilters,
waitForAvailableFilter
}
}
export function useSelectionUtilities() {
const {
filters: { selectedObjects }
} = useInjectedViewerInterfaceState()
const {
metadata: { worldTree }
} = useInjectedViewer()
const setSelectionFromObjectIds = (objectIds: string[]) => {
const res = worldTree.value
? worldTree.value.findAll((node) => {
const t = node.model as Record<string, unknown>
const raw = t.raw as Record<string, unknown>
const id = raw.id as string
if (!raw || !id) return false
if (objectIds.includes(id)) return true
return false
})
: []
const objs = res.map(
(node) => (node.model as Record<string, unknown>).raw as SpeckleObject
)
selectedObjects.value = objs
}
const addToSelection = (object: SpeckleObject) => {
const idx = selectedObjects.value.findIndex((o) => o.id === object.id)
if (idx !== -1) return
selectedObjects.value = [...selectedObjects.value, object]
}
const removeFromSelection = (objectOrId: SpeckleObject | string) => {
const oid = isString(objectOrId) ? objectOrId : objectOrId.id
const idx = selectedObjects.value.findIndex((o) => o.id === oid)
if (idx === -1) return
const newObjects = selectedObjects.value.slice()
newObjects.splice(idx, 1)
selectedObjects.value = newObjects
}
const clearSelection = () => {
selectedObjects.value = []
}
return {
addToSelection,
removeFromSelection,
clearSelection,
setSelectionFromObjectIds,
objects: selectedObjects
}
}