c7e0929eca
* feat: initial can create version implementation on model card * feat: disable model card CTAs for send * feat: initial model ingestion tests * fix: apply ingestion send to all CTAs * feat: sketchup bridge * feat: centeralize the start ingestion logic in host app store * fix: sketchup is handling via model ingestion * chore: cosmetics * feat(ingestion): add failWithError and failWithCancel GraphQL mutations * feat(ingestion): add failIngestion and cancelIngestion methods to useModelIngestion composable * feat(ingestion): handle ingestion failure and cancellation in hostAppStore * fix: reviewers comments * fix: don't know where the f that came from * refactor(ingestion): remove unused statusData and fix lint errors * feat(wizard): add canCreateVersion permission check to publish wizard * TODOs * feat(permissions): add 1s polling for canCreateVersion to reflect workspace limit changes * fix(tooltip): undefined doesnt refresh v-tippy * fix(wizard): too much ctrl z lol * refactor(permissions): check canCreateVersion on action instead of polling * feat(hostApp): adds fallback for model ingestion on older servers * fix: ingestion available check and rock'n roll * feat: workspace plan updated subscription boilerplate * fix: bump the timeout to 2h * feat: handle version limits in publish flows via subscription * feat: align Archicad and Vectorworks with new ingestion flow * chore: onMounted at end of file * fix: logic and ui adjustments * fix: refactoring and permissions * refactor: ingestionStatus renamed to activeIngestions * fix: error handling and notifications * fix: global error handling * chore: general alignment and clean up * fix(vectorworks): now uses capital V * chore: revert codegen --------- Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com> Co-authored-by: Björn Steinhagen <steinhagen.bjoern@gmail.com>
247 lines
7.5 KiB
Vue
247 lines
7.5 KiB
Vue
<template>
|
|
<CommonDialog
|
|
v-model:open="showSendDialog"
|
|
fullscreen="none"
|
|
:title="title"
|
|
:show-back-button="step !== 1"
|
|
@back="step--"
|
|
@fully-closed="
|
|
() => {
|
|
step = 1
|
|
settingsWereChanged = false
|
|
}
|
|
"
|
|
>
|
|
<div v-if="step === 1">
|
|
<WizardProjectSelector
|
|
is-sender
|
|
disable-no-write-access-projects
|
|
:url-parse-error="urlParseError"
|
|
@next="selectProject"
|
|
@search-text-update="updateSearchText"
|
|
/>
|
|
</div>
|
|
<div v-if="step === 2 && selectedProject && selectedAccountId">
|
|
<WizardModelSelector
|
|
:project="selectedProject"
|
|
:workspace-id="selectedProject.workspace?.id"
|
|
:workspace-slug="selectedProject.workspace?.slug"
|
|
:account-id="selectedAccountId"
|
|
is-sender
|
|
@next="selectModel"
|
|
/>
|
|
</div>
|
|
<div v-if="step === 3">
|
|
<SendFiltersAndSettings
|
|
v-model="filter"
|
|
@update:filter="(f) => (filter = f)"
|
|
@update:settings="
|
|
(s) => {
|
|
settings = s
|
|
settingsWereChanged = true
|
|
}
|
|
"
|
|
/>
|
|
<div v-tippy="!canPublish ? publishLimitMessage : ''" class="mt-2">
|
|
<FormButton full-width :disabled="!canPublish" @click="addModel">
|
|
Publish
|
|
</FormButton>
|
|
</div>
|
|
</div>
|
|
<div v-if="urlParseError" class="p-2 text-danger">
|
|
{{ urlParseError }}
|
|
</div>
|
|
</CommonDialog>
|
|
</template>
|
|
<script setup lang="ts">
|
|
import { storeToRefs } from 'pinia'
|
|
import { useSubscription } from '@vue/apollo-composable'
|
|
import type {
|
|
ModelListModelItemFragment,
|
|
ProjectListProjectItemFragment
|
|
} from '~/lib/common/generated/gql/graphql'
|
|
import type { ISendFilter } from '~/lib/models/card/send'
|
|
import { SenderModelCard } from '~/lib/models/card/send'
|
|
import { useHostAppStore } from '~/store/hostApp'
|
|
import { useAccountStore } from '~/store/accounts'
|
|
import { useMixpanel } from '~/lib/core/composables/mixpanel'
|
|
import { useSettingsTracking } from '~/lib/core/composables/trackSettings'
|
|
import type { CardSetting } from '~/lib/models/card/setting'
|
|
import { useAddByUrl } from '~/lib/core/composables/addByUrl'
|
|
import { useCheckGraphql } from '~/lib/core/composables/useCheckGraphql'
|
|
import { workspacePlanUsageUpdatedSubscription } from '~/lib/workspaces/graphql/subscriptions'
|
|
|
|
const { trackEvent } = useMixpanel()
|
|
const { trackSettingsChange } = useSettingsTracking()
|
|
|
|
const showSendDialog = defineModel<boolean>('open', { default: false })
|
|
|
|
const emit = defineEmits(['close'])
|
|
|
|
const step = ref(1)
|
|
const accountStore = useAccountStore()
|
|
const { activeAccount } = storeToRefs(accountStore)
|
|
|
|
const selectedAccountId = ref<string>(activeAccount.value?.accountInfo.id as string)
|
|
const selectedProject = ref<ProjectListProjectItemFragment>()
|
|
const selectedModel = ref<ModelListModelItemFragment>()
|
|
const filter = ref<ISendFilter | undefined>(undefined)
|
|
const settings = ref<CardSetting[] | undefined>(undefined)
|
|
const settingsWereChanged = ref(false)
|
|
|
|
const { tryParseUrl, urlParsedData, urlParseError } = useAddByUrl()
|
|
const { canCreateModelIngestion, canCreateVersion } = useCheckGraphql()
|
|
|
|
const canPublish = ref(true)
|
|
const publishLimitMessage = ref<string | undefined>(undefined)
|
|
|
|
const updateSearchText = (text: string | undefined) => {
|
|
urlParseError.value = undefined
|
|
if (!text) return
|
|
tryParseUrl(text, 'sender')
|
|
}
|
|
|
|
watch(urlParsedData, (newVal) => {
|
|
if (!newVal) return
|
|
selectProject(newVal.account?.accountInfo.id, newVal.project)
|
|
selectModel(newVal.model)
|
|
})
|
|
|
|
watch(showSendDialog, (newVal) => {
|
|
if (newVal) {
|
|
urlParseError.value = undefined
|
|
}
|
|
})
|
|
|
|
const checkPermissions = async () => {
|
|
if (!selectedProject.value || !selectedModel.value) return
|
|
|
|
const res = await canCreateModelIngestion(
|
|
selectedProject.value.id,
|
|
selectedModel.value.id,
|
|
selectedAccountId.value
|
|
)
|
|
if (res.queryAvailable) {
|
|
canPublish.value = res.authorized
|
|
publishLimitMessage.value = res.message || undefined
|
|
} else {
|
|
// check legacy canCreateVersion in else block
|
|
const legacyRes = await canCreateVersion(
|
|
selectedProject.value.id,
|
|
selectedModel.value.id,
|
|
selectedAccountId.value
|
|
)
|
|
canPublish.value = legacyRes.authorized
|
|
publishLimitMessage.value = legacyRes.message || undefined
|
|
}
|
|
}
|
|
|
|
watch(step, async (newVal, oldVal) => {
|
|
if (newVal > oldVal) {
|
|
if (newVal === 3) {
|
|
await checkPermissions()
|
|
}
|
|
return // exit fast on forward
|
|
}
|
|
if (newVal === 1) {
|
|
selectedProject.value = undefined
|
|
selectedModel.value = undefined
|
|
}
|
|
if (newVal === 2) selectedModel.value = undefined
|
|
})
|
|
|
|
const workspaceId = computed(() => selectedProject.value?.workspace?.id)
|
|
|
|
const { onResult: onUsageUpdate } = useSubscription(
|
|
workspacePlanUsageUpdatedSubscription,
|
|
() => ({
|
|
input: {
|
|
workspaceId: workspaceId.value || ''
|
|
}
|
|
}),
|
|
() => ({
|
|
enabled: !!workspaceId.value && step.value === 3,
|
|
clientId: selectedAccountId.value
|
|
})
|
|
)
|
|
|
|
onUsageUpdate(() => {
|
|
void checkPermissions()
|
|
})
|
|
|
|
const selectProject = (accountId: string, project: ProjectListProjectItemFragment) => {
|
|
step.value++
|
|
selectedAccountId.value = accountId
|
|
selectedProject.value = project
|
|
void trackEvent('DUI3 Action', { name: 'Publish Wizard', step: 'project selected' })
|
|
}
|
|
|
|
const title = computed(() => {
|
|
if (step.value === 1) return 'Select project'
|
|
if (step.value === 2) return 'Select model'
|
|
if (step.value === 3) return 'Select objects'
|
|
return ''
|
|
})
|
|
|
|
const selectModel = (model: ModelListModelItemFragment) => {
|
|
step.value++
|
|
selectedModel.value = model
|
|
void trackEvent('DUI3 Action', { name: 'Publish Wizard', step: 'model selected' })
|
|
}
|
|
|
|
const hostAppStore = useHostAppStore()
|
|
|
|
// accountId, serverUrl, projectId, modelId, sendFilter, settings
|
|
const addModel = async () => {
|
|
void trackEvent('DUI3 Action', {
|
|
name: 'Publish Wizard',
|
|
step: 'objects selected',
|
|
filter: filter.value?.typeDiscriminator
|
|
})
|
|
|
|
const existingModel = hostAppStore.models.find(
|
|
(m) =>
|
|
m.modelId === selectedModel.value?.id &&
|
|
m.typeDiscriminator.includes('SenderModelCard')
|
|
) as SenderModelCard
|
|
|
|
// track settings only if user changed them
|
|
// compare against existing model card settings
|
|
if (settingsWereChanged.value && settings.value) {
|
|
trackSettingsChange(
|
|
'Publish Settings Changed',
|
|
settings.value,
|
|
existingModel?.settings || hostAppStore.sendSettings || [],
|
|
selectedAccountId.value,
|
|
true
|
|
)
|
|
}
|
|
if (existingModel) {
|
|
emit('close')
|
|
// Patch the existing model card with new send filter and non-expired state!
|
|
await hostAppStore.patchModel(existingModel.modelCardId, {
|
|
sendFilter: filter.value as ISendFilter,
|
|
expired: false
|
|
})
|
|
void hostAppStore.sendModel(existingModel.modelCardId, 'Wizard')
|
|
return
|
|
}
|
|
|
|
const model = new SenderModelCard()
|
|
model.accountId = selectedAccountId.value
|
|
model.serverUrl = activeAccount.value?.accountInfo.serverInfo.url as string
|
|
model.projectId = selectedProject.value?.id as string
|
|
model.modelId = selectedModel.value?.id as string
|
|
model.workspaceId = selectedProject.value?.workspace?.id as string
|
|
model.workspaceSlug = selectedProject?.value?.workspace?.slug as string
|
|
model.sendFilter = filter.value as ISendFilter
|
|
model.sendFilter.idMap = {} // do not let it null from the beginning otherwise we will end up with null state on Revit...
|
|
model.settings = settings.value
|
|
model.expired = false
|
|
|
|
emit('close')
|
|
await hostAppStore.addModel(model)
|
|
void hostAppStore.sendModel(model.modelCardId, 'Wizard')
|
|
}
|
|
</script>
|