Handle force closing logic on mobile

This commit is contained in:
Mike Tasset
2025-08-04 18:44:50 +02:00
parent 158ce9cd12
commit 1acd511ea3
5 changed files with 122 additions and 6 deletions
@@ -144,17 +144,25 @@ import {
useInjectedViewerState
} from '~~/lib/viewer/composables/setup'
import { useThreadUtilities, useFilterUtilities } from '~~/lib/viewer/composables/ui'
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
import { useBreakpoints } from '@vueuse/core'
const emit = defineEmits<{
forceClosePanels: []
}>()
const parentEl = ref(null as Nullable<HTMLElement>)
const { isLoggedIn } = useActiveUser()
const viewerState = useInjectedViewerState()
const { sessionId } = viewerState
const { users } = useViewerUserActivityTracking({ parentEl })
const { isOpenThread, open } = useThreadUtilities()
const { isOpenThread, open, closeAllThreads } = useThreadUtilities()
const {
filters: { hasAnyFiltersApplied }
} = useFilterUtilities({ state: viewerState })
const canPostComment = useCheckViewerCommentingAccess()
const breakpoints = useBreakpoints(TailwindBreakpoints)
const isMobile = breakpoints.smaller('sm')
const { isEnabled: isEmbedEnabled } = useEmbed()
@@ -275,4 +283,23 @@ function setUserSpotlight(sessionId: string) {
source: 'navbar'
})
}
const forceCloseThreads = async () => {
await closeAllThreads()
}
// Watch for thread opening on mobile and emit event
watch(
() => openThread.value,
(newThread, oldThread) => {
// If a thread opened (wasn't open before) on mobile, emit event
if (newThread && !oldThread && isMobile.value) {
emit('forceClosePanels')
}
}
)
defineExpose({
forceCloseThreads
})
</script>
@@ -41,7 +41,10 @@
enter-from-class="opacity-0"
enter-active-class="transition duration-1000"
>
<ViewerAnchoredPoints />
<ViewerAnchoredPoints
ref="anchoredPoints"
@force-close-panels="() => closeAllPanels('threads')"
/>
</Transition>
</div>
@@ -54,8 +57,14 @@
<!-- Controls -->
<!-- <ViewerControls v-if="showControls" class="relative z-20" /> -->
<template v-if="showControls">
<ViewerControlsLeft />
<ViewerControlsBottom />
<ViewerControlsLeft
ref="leftControls"
@force-close-panels="() => closeAllPanels('left')"
/>
<ViewerControlsBottom
ref="bottomControls"
@force-close-panels="() => closeAllPanels('bottom')"
/>
<ViewerControlsRight v-if="isMobile" />
</template>
@@ -73,7 +82,11 @@
enter-from-class="opacity-0"
enter-active-class="transition duration-1000"
>
<ViewerSelectionSidebar class="z-20" />
<ViewerSelectionSidebar
ref="selectionSidebar"
class="z-20"
@force-close-panels="() => closeAllPanels('selection')"
/>
</Transition>
<div
class="absolute z-10 w-screen px-8 grid grid-cols-1 sm:grid-cols-3 gap-2 top-[3.75rem]"
@@ -156,6 +169,11 @@ const isWorkspacesEnabled = useIsWorkspacesEnabled()
const breakpoints = useBreakpoints(TailwindBreakpoints)
const isMobile = breakpoints.smaller('sm')
const leftControls = ref()
const bottomControls = ref()
const selectionSidebar = ref()
const anchoredPoints = ref()
const resourceIdString = computed(() => route.params.modelId as string)
const projectId = writableAsyncComputed({
get: () => route.params.id as string,
@@ -294,4 +312,19 @@ watch(
},
{ immediate: true }
)
const closeAllPanels = (except?: 'left' | 'bottom' | 'selection' | 'threads') => {
if (except !== 'left' && leftControls.value?.forceClosePanels) {
leftControls.value.forceClosePanels()
}
if (except !== 'bottom' && bottomControls.value?.forceClosePanels) {
bottomControls.value.forceClosePanels()
}
if (except !== 'selection' && selectionSidebar.value?.forceClose) {
selectionSidebar.value.forceClose()
}
if (except !== 'threads' && anchoredPoints.value?.forceCloseThreads) {
anchoredPoints.value.forceCloseThreads()
}
}
</script>
@@ -51,8 +51,9 @@ import {
useViewerShortcuts,
useFilterUtilities
} from '~~/lib/viewer/composables/ui'
import { onKeyStroke } from '@vueuse/core'
import { onKeyStroke, useBreakpoints } from '@vueuse/core'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
enum ActivePanel {
none = 'none',
@@ -63,6 +64,10 @@ enum ActivePanel {
lightControls = 'lightControls'
}
const emit = defineEmits<{
forceClosePanels: []
}>()
const { getShortcutDisplayText, shortcuts, registerShortcuts } = useViewerShortcuts()
const { toggleSectionBox, resetSectionBox, closeSectionBox } = useSectionBoxUtilities()
const { getActiveMeasurement, removeMeasurement, enableMeasurements } =
@@ -70,8 +75,11 @@ const { getActiveMeasurement, removeMeasurement, enableMeasurements } =
const { resetExplode } = useFilterUtilities()
const { getTooltipProps } = useSmartTooltipDelay()
const { isEnabled: isEmbedEnabled } = useEmbed()
const breakpoints = useBreakpoints(TailwindBreakpoints)
const isMobile = breakpoints.smaller('sm')
const activePanel = ref<ActivePanel>(ActivePanel.none)
const panels = shallowRef({
[ActivePanel.measurements]: {
id: ActivePanel.measurements,
@@ -122,6 +130,10 @@ const showResetButton = computed(() => {
const toggleActivePanel = (panel: ActivePanel) => {
activePanel.value = activePanel.value === panel ? ActivePanel.none : panel
if (activePanel.value !== ActivePanel.none && isMobile.value) {
emit('forceClosePanels')
}
if (panel === ActivePanel.sectionBox) {
toggleSectionBox()
}
@@ -161,6 +173,10 @@ registerShortcuts({
ToggleSectionBox: () => toggleSectionBox()
})
const forceClosePanels = () => {
activePanel.value = ActivePanel.none
}
onKeyStroke('Escape', () => {
const isActiveMeasurement = getActiveMeasurement()
@@ -175,4 +191,8 @@ onKeyStroke('Escape', () => {
activePanel.value = ActivePanel.none
}
})
defineExpose({
forceClosePanels
})
</script>
@@ -156,6 +156,10 @@ type ActivePanel =
| 'filters'
| 'devMode'
const emit = defineEmits<{
forceClosePanels: []
}>()
const width = ref(264)
const scrollableControlsContainer = ref(null as Nullable<HTMLDivElement>)
const height = ref(scrollableControlsContainer.value?.clientHeight)
@@ -246,7 +250,17 @@ registerShortcuts({
})
const toggleActivePanel = (panel: ActivePanel) => {
const wasNone = activePanel.value === 'none'
activePanel.value = activePanel.value === panel ? 'none' : panel
// If a panel is being opened (not closed) on mobile, emit event to parent
if (wasNone && activePanel.value !== 'none' && isMobile.value) {
emit('forceClosePanels')
}
}
const forceClosePanel = () => {
activePanel.value = 'none'
}
const openDocs = () => {
@@ -261,4 +275,9 @@ onMounted(() => {
watch(isSmallerOrEqualSm, (newVal) => {
activePanel.value = newVal ? 'none' : 'models'
})
defineExpose({
forceClosePanel,
forceClosePanels: forceClosePanel
})
</script>
@@ -100,6 +100,10 @@ import { TailwindBreakpoints } from '~~/lib/common/helpers/tailwind'
import type { ConcreteComponent } from 'vue'
import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
const emit = defineEmits<{
forceClosePanels: []
}>()
enum ActionTypes {
OpenInNewTab = 'open-in-new-tab'
}
@@ -119,6 +123,7 @@ const { hideObjects, showObjects, isolateObjects, unIsolateObjects } =
const { isSmallerOrEqualSm } = useIsSmallerOrEqualThanBreakpoint()
const breakpoints = useBreakpoints(TailwindBreakpoints)
const isGreaterThanSm = breakpoints.greater('sm')
const isMobile = breakpoints.smaller('sm')
const menuId = useId()
const mp = useMixpanel()
const { getTooltipProps } = useSmartTooltipDelay()
@@ -248,6 +253,10 @@ const onClose = () => {
trackAndClearSelection()
}
const forceClose = () => {
sidebarOpen.value = false
}
onKeyStroke('Escape', () => {
// Cleareance of any vis/iso state coming from here should happen in clearSelection()
// Note: we're not using the trackAndClearSelection method beacuse
@@ -267,6 +276,10 @@ watch(
// Dont open sidebar if a comment is open
if (newLength !== 0 && !focusedThreadId.value) {
sidebarOpen.value = true
// Emit event when sidebar opens on mobile
if (isMobile.value) {
emit('forceClosePanels')
}
} else if (newLength === 0) {
sidebarOpen.value = false
}
@@ -295,4 +308,8 @@ watch(
}
}
)
defineExpose({
forceClose
})
</script>