Files
2025-08-18 11:08:01 +01:00

276 lines
10 KiB
Vue

<template>
<div>
<div v-if="(store.hostAppName && app.$isRunningOnConnector) || app.$isDev">
<div v-if="!config.isDevMode" class="px-1">
<CommonUpdateAlert />
</div>
<!-- IMPORTANT CHECK!! otherwise host app communication corrputed for many different reasons -->
<div v-if="accounts.length != 0">
<div
v-if="hasNoModelCards"
class="fixed h-screen w-screen flex items-center justify-center pr-2 pointer-events-none"
>
<LayoutPanel fancy-glow class="transition pointer-events-auto w-[90%]">
<div class="flex">
<h1
class="text-heading-lg w-full bg-gradient-to-r from-blue-500 via-blue-400 to-blue-600 inline-block py-1 text-transparent bg-clip-text"
>
Hello!
</h1>
<button
v-if="accountStore.activeAccount"
v-tippy="'Open web app'"
class="transition mr-1 opacity-70 hover:opacity-100"
@click.stop="
$openUrl(accountStore.activeAccount.accountInfo.serverInfo.url)
"
>
<ArrowTopRightOnSquareIcon class="w-4" />
</button>
</div>
<!-- Returning null from host app is blocked by CI for now, hence host app send here empty documentInfo, we check it's id whether null or not. -->
<div v-if="!!store.documentInfo?.id">
<div class="text-foreground-2 text-body-sm">
There are no Speckle models being published or loaded in this file yet.
</div>
<div
class="flex space-x-2 max-[275px]:flex-col max-[275px]:space-y-2 max-[275px]:space-x-0 mt-4"
>
<div v-if="app.$sendBinding" class="grow">
<FormButton
v-tippy="'Publish objects from this file to a new model'"
:icon-left="ArrowUpTrayIcon"
color="outline"
full-width
@click="handleSendClick"
>
Publish
</FormButton>
</div>
<div v-if="app.$receiveBinding" class="grow">
<FormButton
v-tippy="'Load an existing model in this file'"
:icon-left="ArrowDownTrayIcon"
color="outline"
full-width
@click="handleReceiveClick"
>
Load
</FormButton>
</div>
</div>
</div>
<div v-else>
<div v-if="store.documentInfo?.message" class="text-foreground-2">
{{ store.documentInfo?.message }}
</div>
<div v-else class="text-foreground-2">
Welcome to Speckle! Please open a file to use this plugin.
</div>
</div>
<!-- TEMPORARY MESSAGE TO USER! will be deleted -->
<div
v-if="store.isDistributedBySpeckle"
class="mt-2 bg-highlight-1 rounded-md p-2"
>
<h1
class="text-heading-sm w-full bg-gradient-to-r from-blue-500 via-blue-400 to-blue-600 inline-block py-1 text-transparent bg-clip-text"
>
Speckle for
<span class="capitalize">{{ store.hostAppName }}</span>
</h1>
<div class="text-foreground-2 text-body-xs">
Get started in no time with our key workflows and tutorials for
<span class="capitalize">{{ store.hostAppName }}:</span>
<FormButton
size="sm"
color="outline"
class="my-2"
full-width
@click="
app.$openUrl(
`https://docs.speckle.systems/connectors/${store.hostAppName}?utm=dui`
)
"
>
Getting started
</FormButton>
</div>
<!--
<FormButton
text
size="sm"
color="subtle"
class=""
full-width
@click="
app.$openUrl(
'https://speckle.community/t/next-gen-connectors-supported-workflows-and-faq/16162'
)
"
>
<span class="text-foreground-2 text-body-3xs truncate line-clamp-1">
New connectors announcement
</span>
</FormButton> -->
</div>
<!--Apply Revit Categories section (only if mapper binding exists)-->
<div
v-if="app.$revitMapperBinding"
class="mt-2 bg-highlight-1 rounded-md p-2"
>
<h1
class="text-heading-sm w-full bg-gradient-to-r from-blue-500 via-blue-400 to-blue-600 inline-block py-1 text-transparent bg-clip-text"
>
Assign Revit Categories
</h1>
<div class="text-foreground-2 text-body-xs">
Set Rhino geometry categories for accurate DirectShape loading in Revit.
<FormButton
size="sm"
color="outline"
class="my-2"
full-width
@click="$router.push('/revit-mapper')"
>
Assign categories
</FormButton>
</div>
</div>
</LayoutPanel>
</div>
<div v-if="accounts.length !== 0 && !hasNoModelCards" class="space-y-2 pb-24">
<div v-for="project in store.projectModelGroups" :key="project.projectId">
<CommonProjectModelGroup :project="project" />
</div>
</div>
<!-- Triggered by "Show Details" button on Toast Notification -->
<ErrorDialog
v-model:open="store.showErrorDialog"
chromium65-compatibility
@close="store.showErrorDialog = false"
/>
</div>
<!-- No accounts present: display a signin button. This currently launches manager. -->
<!-- NOTE: The flow is horrible, we should migrate as many connectors as possible to their own account adding logic -->
<div v-else>
<div
class="fixed h-screen w-screen flex items-center justify-center pr-2 pointer-events-none"
>
<HomeNoAccountsPanel />
</div>
</div>
</div>
<div
v-else-if="!app.$isRunningOnConnector"
class="flex-1 flex flex-col items-center justify-center py-12 gap-y-6"
>
<section
class="w-full max-w-md flex flex-col gap-y-8 items-center mx-auto py-12 px-6 border rounded-2xl border-outline-2"
>
<NuxtImg
:src="`/assets/images/pleading_spockle.svg`"
alt=""
class="w-20"
width="120"
/>
<h1 class="text-2xl md:text-3xl font-semibold text-foreground text-center">
Connector is not detected.
</h1>
<FormButton color="outline" @click="openSpeckleConnectors">
Get them here
</FormButton>
</section>
</div>
<div v-else>
<div class="fixed h-screen w-screen flex items-center pointer-events-none">
<LayoutPanel fancy-glow class="transition pointer-events-auto w-full">
<h1
class="h4 font-bold bg-gradient-to-r from-blue-500 via-blue-400 to-blue-600 inline-block py-1 text-transparent bg-clip-text"
>
Reload needed!
</h1>
<div class="text-foreground-2 mt-2 mb-4">
Host application lost its communication with user interface for some reason.
</div>
<FormButton :icon-left="ArrowPathIcon" full-width @click="reload()">
Reload
</FormButton>
</LayoutPanel>
</div>
</div>
<SendWizard v-model:open="showSendDialog" @close="showSendDialog = false" />
<ReceiveWizard
v-model:open="showReceiveDialog"
@close="showReceiveDialog = false"
/>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
ArrowPathIcon,
ArrowTopRightOnSquareIcon
} from '@heroicons/vue/24/solid'
import { useAccountStore } from '~~/store/accounts'
import { useHostAppStore } from '~~/store/hostApp'
import { useConfigStore } from '~~/store/config'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
const app = useNuxtApp()
const config = useConfigStore()
// IMPORTANT: the account store needs to be awaited here, and in any other top level page to prevent
// race conditions on initialisation (model cards get loaded, but accounts are not there yet)
// TODO: guard against this later, incase we will have more top level entry pages
const accountStore = useAccountStore()
await accountStore.refreshAccounts()
const { accounts } = storeToRefs(accountStore)
const store = useHostAppStore()
const { trackEvent } = useMixpanel()
const showSendDialog = ref(false)
const showReceiveDialog = ref(false)
app.$baseBinding?.on('documentChanged', () => {
showSendDialog.value = false
showReceiveDialog.value = false
})
const handleSendClick = () => {
showSendDialog.value = !showSendDialog.value
trackEvent('DUI3 Action', { name: 'Publish Wizard', step: 'start' })
}
const handleReceiveClick = () => {
showReceiveDialog.value = !showReceiveDialog.value
trackEvent('DUI3 Action', { name: 'Load Wizard', step: 'start' })
}
const hasNoModelCards = computed(
() => store.projectModelGroups.length === 0 || hasNoValidProjects.value
)
const hasNoValidProjects = computed(() => {
const serverUrls = accounts.value
.filter((acc) => acc.isValid)
.map((acc) => acc.accountInfo.serverInfo.url)
return (
store.projectModelGroups.filter((p) => serverUrls.includes(p.serverUrl)).length ===
0
)
})
const reload = () => {
window.location.reload()
}
const openSpeckleConnectors = () => {
window.open('https://app.speckle.systems/connectors', '_blank')
}
</script>