83d8035dc2
* root + server * frontend * frontend-2 * dui3 * dui3 * tailwind theme * ui-components * preview service * viewer * viewer-sandbox * fileimport-service * webhook service * objectloader * shared * ui-components-nuxt * WIP full config * WIP full linter * eslint projectwide util * minor fix * removing redundant ci * clean up test errors * fixed prettier formatting * CI improvements * TSC lint fix * 'buildBatch' needs to be async since some batch types (like Text) require it. Removed a disabled liniting rule from ObjLoader * removed unnecessary void --------- Co-authored-by: AlexandruPopovici <alexandrupopoviciioan@gmail.com>
161 lines
4.8 KiB
Vue
161 lines
4.8 KiB
Vue
<template>
|
|
<div
|
|
ref="parentEl"
|
|
class="fixed z-30 left-0 top-0 w-screen h-[100dvh] pointer-events-none overflow-hidden"
|
|
>
|
|
<!--
|
|
Tour Slideshow
|
|
-->
|
|
<TourComment
|
|
v-for="(item, index) in slideshowItems.slice(0, tourItems.length)"
|
|
:key="index"
|
|
:item="item"
|
|
:index="index"
|
|
class="absolute"
|
|
:class="isSmallerOrEqualSm ? 'bottom-0 left-0 w-screen' : ''"
|
|
:style="isSmallerOrEqualSm ? undefined : item.style"
|
|
:show-controls="item.showControls"
|
|
@skip="finishSlideshow()"
|
|
>
|
|
<Component :is="tourItems[index]" />
|
|
</TourComment>
|
|
<!-- In case the bubble is closed by the user, we need to display something -->
|
|
<Transition
|
|
enter-from-class="opacity-0"
|
|
leave-to-class="opacity-0"
|
|
enter-active-class="transition duration-300"
|
|
leave-active-class="transition duration-300"
|
|
>
|
|
<div
|
|
v-show="!hasOpenComments"
|
|
class="fixed bottom-0 left-0 w-full h-28 flex align-center p-10 items-center justify-center space-x-2 pointer-events-auto"
|
|
>
|
|
<FormButton size="xs" color="invert" rounded @click="finishSlideshow()">
|
|
Skip
|
|
</FormButton>
|
|
<FormButton
|
|
size="xl"
|
|
:icon-right="ArrowRightIcon"
|
|
rounded
|
|
class="shadow-md"
|
|
@click="resumeSlideshow()"
|
|
>
|
|
Resume Tour
|
|
</FormButton>
|
|
</div>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
<script setup lang="ts">
|
|
// Disclaimer, not the cleanest code.
|
|
import type { Nullable } from '@speckle/shared'
|
|
import type { Vector3 } from 'three'
|
|
import { items as slideshowItemsRaw } from '~~/lib/tour/slideshowItems'
|
|
import { ArrowRightIcon } from '@heroicons/vue/24/solid'
|
|
import { useViewerAnchoredPoints } from '~~/lib/viewer/composables/anchorPoints'
|
|
|
|
// Slideshow component imports
|
|
import FirstTip from '~~/components/tour/content/FirstTip.vue'
|
|
import BasicViewerNavigation from '~~/components/tour/content/BasicViewerNavigation.vue'
|
|
import OverlayModel from '~~/components/tour/content/OverlayModel.vue'
|
|
import { useCameraUtilities } from '~~/lib/viewer/composables/ui'
|
|
import { useMixpanel } from '~~/lib/core/composables/mp'
|
|
import { useViewerTour } from '~/lib/viewer/composables/tour'
|
|
|
|
const emit = defineEmits(['next'])
|
|
|
|
const tourStage = useViewerTour()
|
|
const { zoom, setView } = useCameraUtilities()
|
|
|
|
// Drives the amount of slideshow items
|
|
const tourItems = [FirstTip, BasicViewerNavigation, OverlayModel /* , LastTip */]
|
|
|
|
// Ensuring we don't have more 3d points than actual tips by slicing the array
|
|
// TODO: should check the other way around, but since this part is so handcrafted
|
|
// doesn't make much sense.
|
|
const slideshowItems = ref(slideshowItemsRaw.slice(0, tourItems.length))
|
|
provide('slideshowItems', slideshowItems)
|
|
|
|
const lastOpenIndex = ref(0)
|
|
const mp = useMixpanel()
|
|
|
|
// const isSmallerOrEqualSm = computed(() => breakpoints.smallerOrEqual('sm').value)
|
|
const { isSmallerOrEqualSm } = useIsSmallerOrEqualThanBreakpoint()
|
|
|
|
const next = (currentIndex: number) => {
|
|
if (currentIndex + 1 >= slideshowItems.value.length) {
|
|
finishSlideshow()
|
|
return
|
|
}
|
|
slideshowItems.value[currentIndex].expanded = false
|
|
slideshowItems.value[currentIndex].viewed = true
|
|
slideshowItems.value[currentIndex + 1].expanded = true
|
|
lastOpenIndex.value = currentIndex + 1
|
|
mp.track('Onboarding Action', {
|
|
type: 'action',
|
|
name: 'slideshow',
|
|
action: 'next',
|
|
step: currentIndex
|
|
})
|
|
}
|
|
|
|
const prev = (currentIndex: number) => {
|
|
if (currentIndex - 1 < 0) return
|
|
slideshowItems.value[currentIndex].expanded = false
|
|
slideshowItems.value[currentIndex - 1].expanded = true
|
|
lastOpenIndex.value = currentIndex - 1
|
|
mp.track('Onboarding Action', {
|
|
type: 'action',
|
|
name: 'slideshow',
|
|
action: 'previous',
|
|
step: currentIndex
|
|
})
|
|
}
|
|
|
|
const toggle = (index: number) => {
|
|
if (!slideshowItems.value[index]) return
|
|
slideshowItems.value[index].expanded = !slideshowItems.value[index].expanded
|
|
if (slideshowItems.value[index].expanded) lastOpenIndex.value = index
|
|
|
|
mp.track('Onboarding Action', {
|
|
type: 'action',
|
|
name: 'slideshow',
|
|
action: 'toggle',
|
|
step: index
|
|
})
|
|
}
|
|
|
|
provide('slideshowActions', { next, prev, toggle })
|
|
|
|
const hasOpenComments = computed(() => {
|
|
return slideshowItems.value.some((item) => item.expanded === true)
|
|
})
|
|
|
|
const parentEl = ref(null as Nullable<HTMLElement>)
|
|
useViewerAnchoredPoints({
|
|
parentEl,
|
|
points: slideshowItems,
|
|
pointLocationGetter: (c) => c.location as Vector3,
|
|
updatePositionCallback: (c, res) => {
|
|
c.style = {
|
|
...c.style,
|
|
...res.style,
|
|
display: 'inline-block',
|
|
transition: 'all 0.1s ease'
|
|
}
|
|
}
|
|
})
|
|
|
|
const finishSlideshow = () => {
|
|
zoom()
|
|
setView('left')
|
|
tourStage.showNavbar.value = true
|
|
tourStage.showControls.value = true
|
|
emit('next')
|
|
}
|
|
|
|
const resumeSlideshow = () => {
|
|
slideshowItems.value[lastOpenIndex.value].expanded = true
|
|
}
|
|
</script>
|