Merge pull request #180 from specklesystems/dogukan/cnx-1978-control-camera-animation-on-change
Build and deploy Connector and Visual / build-connector (push) Has been cancelled
Build and deploy Connector and Visual / build-visual (push) Has been cancelled
Build and deploy Connector and Visual / deploy-installers (push) Has been cancelled

feat (visual): toogle for animation on action
This commit is contained in:
Mucahit Bilal GOKER
2025-07-08 16:14:31 +03:00
committed by GitHub
6 changed files with 95 additions and 11 deletions
+3
View File
@@ -105,6 +105,9 @@
},
"isGhost": {
"type": { "bool": true }
},
"zoomOnFilter": {
"type": { "bool": true }
}
}
},
@@ -5,6 +5,19 @@
<ViewerControlsButtonToggle flat tooltip="Zoom extends" @click="onZoomExtentsClicked">
<ArrowsPointingOutIcon class="h-4 w-4 md:h-5 md:w-5" />
</ViewerControlsButtonToggle>
<!-- Zoom on Filter -->
<ViewerControlsButtonToggle
:tooltip="
visualStore.isZoomOnFilterActive
? 'Move camera on filter'
: 'Keep camera position on filter'
"
flat
@click="toggleZoomOnFilter"
>
<ZoomToFit v-if="visualStore.isZoomOnFilterActive" class="h-5 w-5" />
<ZoomToFit v-else class="h-5 w-5 opacity-30" />
</ViewerControlsButtonToggle>
<!-- Ghost / Hidden -->
<ViewerControlsButtonToggle
:tooltip="
@@ -65,6 +78,7 @@ import Perspective from '../components/global/icon/Perspective.vue'
import PerspectiveMore from '../components/global/icon/PerspectiveMore.vue'
import Ghost from '../components/global/icon/Ghost.vue'
import ZoomToFit from '../components/global/icon/ZoomToFit.vue'
const visualStore = useVisualStore()
@@ -111,6 +125,11 @@ const toggleGhostHidden = () => {
visualStore.writeIsGhostToFile()
}
const toggleZoomOnFilter = () => {
visualStore.setIsZoomOnFilterActive(!visualStore.isZoomOnFilterActive)
visualStore.writeZoomOnFilterToFile()
}
const viewModesOpen = computed({
get: () => activeControl.value === 'viewModes',
set: (value) => {
@@ -0,0 +1,24 @@
<template>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.75 3.75V8.25M3.75 3.75H8.25M3.75 3.75L9 9M20.25 3.75H15.75M20.25 3.75V8.25M20.25 3.75L15 9"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M15.75 15.4028L18.8093 12.3435C18.8772 12.2756 18.9638 12.2294 19.0581 12.2107C19.1523 12.1919 19.25 12.2016 19.3387 12.2383C19.4275 12.2751 19.5034 12.3373 19.5568 12.4172C19.6102 12.4971 19.6388 12.591 19.6389 12.687V20.063C19.6388 20.159 19.6102 20.2529 19.5568 20.3328C19.5034 20.4127 19.4275 20.4749 19.3387 20.5117C19.25 20.5484 19.1523 20.5581 19.0581 20.5393C18.9638 20.5206 18.8772 20.4744 18.8093 20.4065L15.75 17.3472M8.45833 20.75H14.2917C14.6784 20.75 15.0494 20.5964 15.3229 20.3229C15.5964 20.0494 15.75 19.6784 15.75 19.2917V13.4583C15.75 13.0716 15.5964 12.7006 15.3229 12.4271C15.0494 12.1536 14.6784 12 14.2917 12H8.45833C8.07156 12 7.70063 12.1536 7.42714 12.4271C7.15365 12.7006 7 13.0716 7 13.4583V19.2917C7 19.6784 7.15365 20.0494 7.42714 20.3229C7.70063 20.5964 8.07156 20.75 8.45833 20.75Z"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
+10 -6
View File
@@ -37,8 +37,8 @@ export interface Hit {
export interface IViewerEvents {
ping: (message: string) => void
setSelection: (objectIds: string[]) => void
resetFilter: (objectIds: string[], ghost: boolean) => void
filterSelection: (objectIds: string[], ghost: boolean) => void
resetFilter: (objectIds: string[], ghost: boolean, zoom: boolean) => void
filterSelection: (objectIds: string[], ghost: boolean, zoom: boolean) => void
setViewMode: (viewMode: ViewMode) => void
colorObjectsByGroup: (
colorById: {
@@ -149,20 +149,24 @@ export class ViewerHandler {
}
}
public filterSelection = (objectIds: string[], ghost: boolean) => {
public filterSelection = (objectIds: string[], ghost: boolean, zoom: boolean = true) => {
console.log('🔗 Handling filterSelection inside ViewerHandler')
if (objectIds) {
this.unIsolateObjects()
this.filteringState = this.filtering.isolateObjects(objectIds, 'powerbi', true, ghost)
this.zoomObjects(objectIds, true)
if (zoom) {
this.zoomObjects(objectIds, true)
}
}
}
public resetFilter = (objectIds: string[], ghost: boolean) => {
public resetFilter = (objectIds: string[], ghost: boolean, zoom: boolean = true) => {
console.log('🔗 Handling filterSelection inside ViewerHandler')
if (objectIds) {
this.isolateObjects(objectIds, ghost)
this.zoomObjects(objectIds, true)
if (zoom) {
this.zoomObjects(objectIds, true)
}
}
}
+29 -5
View File
@@ -36,6 +36,7 @@ export const useVisualStore = defineStore('visualStore', () => {
const isOrthoProjection = ref<boolean>(false)
const isGhostActive = ref<boolean>(true)
const isNavbarHidden = ref<boolean>(false)
const isZoomOnFilterActive = ref<boolean>(true)
const commonError = ref<string>(undefined)
@@ -165,13 +166,13 @@ export const useVisualStore = defineStore('visualStore', () => {
if (dataInput.value.selectedIds.length > 0) {
isFilterActive.value = true
viewerEmit.value('filterSelection', dataInput.value.selectedIds, isGhostActive.value)
viewerEmit.value('filterSelection', dataInput.value.selectedIds, isGhostActive.value, isZoomOnFilterActive.value)
} else {
isFilterActive.value = false
latestColorBy.value = dataInput.value.colorByIds
// Only apply filtering if object IDs are available, otherwise show all objects normally
if (fieldInputState.value.objectIds && dataInput.value.objectIds && dataInput.value.objectIds.length > 0) {
viewerEmit.value('resetFilter', dataInput.value.objectIds, isGhostActive.value)
viewerEmit.value('resetFilter', dataInput.value.objectIds, isGhostActive.value, isZoomOnFilterActive.value)
} else {
// No object IDs provided - show all objects without any filtering
viewerEmit.value('unIsolateObjects')
@@ -247,6 +248,22 @@ export const useVisualStore = defineStore('visualStore', () => {
})
}
const writeZoomOnFilterToFile = () => {
// NOTE: need skipping the update function, it resets the viewer state unneccessarily.
postFileSaveSkipNeeded.value = true
host.value.persistProperties({
merge: [
{
objectName: 'camera',
properties: {
zoomOnFilter: isZoomOnFilterActive.value
},
selector: null
}
]
})
}
const writeViewModeToFile = (viewMode: ViewMode) => {
// NOTE: need skipping the update function, it resets the viewer state unneccessarily.
postFileSaveSkipNeeded.value = true
@@ -353,6 +370,10 @@ export const useVisualStore = defineStore('visualStore', () => {
isGhostActive.value = val
}
const setIsZoomOnFilterActive = (val: boolean) => {
isZoomOnFilterActive.value = val
}
const setPostFileSaveSkipNeeded = (newValue: boolean) => (postFileSaveSkipNeeded.value = newValue)
const setPostClickSkipNeeded = (newValue: boolean) => (postClickSkipNeeded.value = newValue)
@@ -366,7 +387,7 @@ export const useVisualStore = defineStore('visualStore', () => {
const resetFilters = () => {
// Only apply filtering if object IDs are available, otherwise show all objects normally
if (fieldInputState.value.objectIds && dataInput.value && dataInput.value.objectIds && dataInput.value.objectIds.length > 0) {
viewerEmit.value('resetFilter', dataInput.value.objectIds, isGhostActive.value)
viewerEmit.value('resetFilter', dataInput.value.objectIds, isGhostActive.value, isZoomOnFilterActive.value)
} else {
// No object IDs provided - show all objects without any filtering
viewerEmit.value('unIsolateObjects')
@@ -395,13 +416,13 @@ export const useVisualStore = defineStore('visualStore', () => {
// Restore selection filters if they exist
if (dataInput.value.selectedIds.length > 0) {
isFilterActive.value = true
viewerEmit.value('filterSelection', dataInput.value.selectedIds, isGhostActive.value)
viewerEmit.value('filterSelection', dataInput.value.selectedIds, isGhostActive.value, isZoomOnFilterActive.value)
} else {
isFilterActive.value = false
latestColorBy.value = dataInput.value.colorByIds
// Only apply filtering if object IDs are available, otherwise show all objects normally
if (fieldInputState.value.objectIds && dataInput.value.objectIds && dataInput.value.objectIds.length > 0) {
viewerEmit.value('resetFilter', dataInput.value.objectIds, isGhostActive.value)
viewerEmit.value('resetFilter', dataInput.value.objectIds, isGhostActive.value, isZoomOnFilterActive.value)
} else {
// No object IDs provided - show all objects without any filtering
viewerEmit.value('unIsolateObjects')
@@ -443,6 +464,7 @@ export const useVisualStore = defineStore('visualStore', () => {
isOrthoProjection,
isGhostActive,
isNavbarHidden,
isZoomOnFilterActive,
latestAvailableVersion,
isConnectorUpToDate,
commonError,
@@ -450,6 +472,7 @@ export const useVisualStore = defineStore('visualStore', () => {
setLatestAvailableVersion,
setIsOrthoProjection,
setIsGhost,
setIsZoomOnFilterActive,
setFormattingSettings,
setBrandingHidden,
setNavbarHidden,
@@ -466,6 +489,7 @@ export const useVisualStore = defineStore('visualStore', () => {
writeObjectsToFile,
writeCameraViewToFile,
writeIsGhostToFile,
writeZoomOnFilterToFile,
writeIsOrthoToFile,
writeViewModeToFile,
writeCameraPositionToFile,
+10
View File
@@ -196,6 +196,16 @@ export class Visual implements IVisual {
)
}
if (camera && 'zoomOnFilter' in camera) {
console.log(
`Zoom on filter?: ${options.dataViews[0].metadata.objects.camera?.zoomOnFilter as boolean}`
)
visualStore.setIsZoomOnFilterActive(
options.dataViews[0].metadata.objects.camera?.zoomOnFilter as boolean
)
}
// get receive info from file for mixpanel
try {
const receiveInfoFromFile = JSON.parse(