Compare commits

...

80 Commits

Author SHA1 Message Date
Oğuzhan Koral 75ff0b1efe Merge branch 'main' into oguzhan/disable-intercom-for-externals 2025-08-18 22:11:39 +01:00
oguzhankoral 49f9b2cf48 chore: disable intercom for external developers 2025-08-18 22:04:26 +01:00
Claire Kuang c8f85c3874 fix(mapper): switches mapper terminology to category assignment (#52)
* switches mapper terminology to category assignment

* linting

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
2025-08-18 14:14:39 +01:00
oguzhankoral e939ecf8d8 linting 2025-08-18 14:10:35 +01:00
Claire Kuang 29f7e708f7 switches mapper terminology to category assignment 2025-08-18 14:06:05 +01:00
Björn Steinhagen 85405d10dd chore: separate header for mapper (#51) 2025-08-18 11:08:01 +01:00
Björn Steinhagen d8fdc2c3c5 fix(mapper): preserve mapping mode state on navigation (#49)
* feat: poc

- needs cleaning
- just me, hacking

* refactor: cleaning

* chore: update available categories

* fix: remember previous mode

* fix: clear search string after mapping

* feat: add Mixpanel tracking to revit mapper interactions (#50)

* feat: add Mixpanel tracking to revit mapper interactions

* fix: pr comments

* fix: just mode

* chore(interop-lite): rename event name prop

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>

* revit mapper store

* WIP

* Fix form select base placeholder on select

* refactor: convention, not composable

* fix: deselecting objects through mapped mode

* fix: eslinting ?

* chore: remove console log

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
2025-08-15 18:05:18 +03:00
Björn Steinhagen 034d8645c6 feat(ui): show existing category mappings in revit mapper dropdown (#48)
* feat: poc

- needs cleaning
- just me, hacking

* refactor: cleaning

* chore: update available categories

* feat: add Mixpanel tracking to revit mapper interactions (#50)

* feat: add Mixpanel tracking to revit mapper interactions

* fix: pr comments

* fix: just mode

* chore(interop-lite): rename event name prop

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>

* revit mapper store

* WIP

* Fix form select base placeholder on select

* refactor: convention, not composable

* fix: deselecting objects through mapped mode

* fix: eslinting ?

* chore: remove console log

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
2025-08-15 17:53:07 +03:00
Björn Steinhagen d797a65fab fix: pre-selected objects (#47) 2025-08-14 15:28:45 +03:00
Björn Steinhagen 028c9d2ac1 feat(dui): layer mapping for revit integration in interop lite (#44)
* feat: update mapper binding interface for layer support and renamed methods

* fix: add missing layer mock methods to `IRevitMapperBinding`

* feat: adds mode toggle

* feat: layer dropdown

* feat: hierarchical layer object highlighting and simple mappings mgmt

* fix: multi instead of base

* fix: refresh layer list on doc switch

* fix: formatting

* feat: added `Select All` button and updated event handling

* fix: event handling

* refactor: components to make mapper more maintainable

* chore: rename button to Assign Revit Categories

* refactor: hardcoded list now in dui

* fix: pr comments

* fix: redundant div

* refactor: remove redundant Props interfaces in mapper components

* refactor: group conditional buttons in mapper

* fix: auto import not working?

* fix: jokes i was being dumb

* chore(revit-mapper): css

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
2025-08-14 15:01:29 +03:00
Oğuzhan Koral b2695e77f5 fix(settings): warn user and force push them to refresh (#43) 2025-08-07 11:30:18 +03:00
Björn Steinhagen 669afe81cf feat(rhino): add revit mapper UI for category assignment (#41)
* feat: basic structure

* feat: categories

* feat: selection filter

* chore: mock categories

* feat: second iteration

* docs: comments

* feat: create mapper binding interface

* feat: register bindings

* feat: add Revit Integration button

conditionally based on the presence or absence of binding

* fix: tooltip

* fix: missing method and interface for `getAvailableCategories`

* fix: remove hardcoded categories

* chore: categories from connector

* chore: remaining methods

* chore: remove unused method

* fix: removing duplicate interfaces

* chore: cleanups

* fix: add DocumentModelStore dependency for event handling

* fix: linting

* fix: dropdown

* fix: again, linting

* chore: don't need the double label

* fix: missing label

* chore: small tweaks

* chore: name

* chore(revit-mapper): css

* chore(revit-mapper): correct routing

* fix(revit-mapper): revit integration buttons

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
2025-08-06 14:55:44 +03:00
Oğuzhan Koral 48bb180899 feat: remove button for deleted models (#40) 2025-08-01 20:06:58 +03:00
Oğuzhan Koral 3b4aa93858 Feat: mocked bindings and logging to seq (#39)
* mocked bindings and logging to seq

* test deploy

* test deploy

* test deploy

* connectorless state

* remove logs

* remove more logs

* add flags to globalThus

* log with /api/events/raw

* log error link on prod over local account

* handle test query to distinguish self hosters

* throw again

* log again...

* sa and ra

* error policy non none

* attach server url to logs

* Add host app version

* rename name to slug

* remove useless re throw

* fix confusion on versions
2025-07-23 15:51:09 +01:00
Oğuzhan Koral 4ebf702ab2 Feat: receive settings (#35)
* Receive settings for POC for now

* Patch the model after settings change
2025-06-27 12:38:47 +03:00
Oğuzhan Koral 6e6bd423a0 multi selectable card setting (#38) 2025-06-25 19:30:30 +03:00
Oğuzhan Koral 57ef9685b6 Pass URL origin to auth flow (#37) 2025-06-24 18:19:18 +03:00
Oğuzhan Koral 2ff5849739 Handle connectors that not deployed by Speckle (#36) 2025-06-19 20:07:36 +03:00
Oğuzhan Koral e55c0ca7dd Change the messaging for personal projects (#34) 2025-06-17 12:57:35 +03:00
Dimitrie Stefanescu 8ef79f7a7c Merge pull request #33 from specklesystems/oguzhan/hide-receive-button-on-navbar
Fix: hide receive button according to binding
2025-06-17 10:35:19 +01:00
oguzhankoral 79951f7cf7 Hide receive button according to binding 2025-06-17 12:22:23 +03:00
Dimitrie Stefanescu cf70ddc79b fix: don't throw on zero affected elements on automate results (#32) 2025-06-09 15:58:01 +03:00
Dimitrie Stefanescu 4e16813c75 Merge pull request #31 from specklesystems/dim/correctly-respect-server-roles
feat: correctly respects server roles when adding by url
2025-06-05 14:28:04 +01:00
Dimitrie Stefanescu ee4e7576ad feat: correctly respects server roles 2025-06-05 14:22:52 +01:00
Oğuzhan Koral 3ba11c983b Feat: open workspace and server in web buttons (#30)
* Open workspace and server buttons

* Do not have navigator for personal projects
2025-06-04 19:53:52 +03:00
Dimitrie Stefanescu d92dcf5342 Merge pull request #29 from specklesystems/dimitrie/cnx-1968-update-dui-and-appspsecklesystems-doc-links
feat: points docs to docs.speckle.systems
2025-06-03 13:42:05 +01:00
Dimitrie Stefanescu 55afedab68 feat: points docs to docs.speckle.systems 2025-06-03 12:58:24 +01:00
Dimitrie Stefanescu a8d948ec71 Merge pull request #25 from specklesystems/intercom
Adds intercom
2025-06-03 10:30:38 +01:00
Dimitrie Stefanescu 65a9d3e485 Merge branch 'main' into intercom 2025-06-03 09:30:43 +01:00
Oğuzhan Koral be631746b9 Fix explore plans (#28) 2025-06-02 16:25:00 +03:00
Dimitrie Stefanescu 20c43f2108 feat: adds last guards 2025-05-30 17:59:53 +01:00
Dimitrie Stefanescu af0de85ef7 chore: comment 2025-05-30 17:38:02 +01:00
Dimitrie Stefanescu 7c54845a05 removes runtime config 2025-05-30 17:37:14 +01:00
Oğuzhan Koral cbec244443 Fix: exclude incomplete workspaces (#26)
* Exclude incomplete workspaces

* get rid of from computed value
2025-05-30 19:36:52 +03:00
Dimitrie Stefanescu a2a9ab1f4b another try 2025-05-30 17:36:35 +01:00
Dimitrie Stefanescu 2f87c34272 reverts bad change 2025-05-30 17:34:30 +01:00
Dimitrie Stefanescu 7fde35e639 feat: maybe fix 2025-05-30 17:27:54 +01:00
Dimitrie Stefanescu 97765d84ca fix: maybe prod fix 2025-05-30 16:56:21 +01:00
Dimitrie Stefanescu 9d15be73ad Merge branch 'intercom' of https://github.com/specklesystems/speckle-connectors-dui into intercom 2025-05-30 16:52:00 +01:00
Dimitrie Stefanescu 45f763a2ce feat: makes intercom plugin client side only 2025-05-30 16:48:46 +01:00
Oğuzhan Koral 3c6bda7af9 Merge branch 'main' into intercom 2025-05-30 17:52:00 +03:00
Dimitrie Stefanescu 007794dae2 chore: just a comment 2025-05-30 15:24:41 +01:00
Dimitrie Stefanescu f8912338cb chore: unfies watch logic on active account 2025-05-30 15:11:42 +01:00
Dimitrie Stefanescu f932fa46a3 feat: stylign + account handling changes for intercom, when no accounts are present 2025-05-30 15:07:32 +01:00
Oğuzhan Koral 292d2bf0bb Feat: Handle new automate schema (#24)
* Handle new automate schema

* Get rid of from old schema for automate
2025-05-30 17:04:22 +03:00
Dimitrie Stefanescu 43340d9b52 feat: maybe fix for revit 2022 2025-05-30 14:44:06 +01:00
Dimitrie Stefanescu 4898a9e2e9 chore: console.log etc cleanup 2025-05-30 14:25:52 +01:00
Dimitrie Stefanescu 19b982e2e3 feat: adds intercom. wip, revit 2022 seems to not like it 2025-05-30 14:14:48 +01:00
Dimitrie Stefanescu e3cf896c14 Merge pull request #23 from specklesystems/dim/url-fixes
feat: removes buttons if there's a faulty url parsed
2025-05-27 17:42:35 +01:00
Dimitrie Stefanescu 34d855212f Merge branch 'main' into dim/url-fixes 2025-05-27 17:30:31 +01:00
Dimitrie Stefanescu 8d159547d4 feat: removes buttons if there's a faulty url parsed 2025-05-27 17:28:37 +01:00
Dimitrie Stefanescu 0c3ee8b38f Merge pull request #22 from specklesystems/dimitrie/cnx-1781-in-the-revit-connector-add-the-option-to-select-all-the
feat: adds select/deselect all button in revit categories filter
2025-05-26 14:26:26 +01:00
Dimitrie Stefanescu c51f282644 feat: adds select/deselect all button in revit categories filter 2025-05-26 13:48:31 +01:00
Dimitrie Stefanescu 1faea0aec2 Merge pull request #21 from specklesystems/dimitrie/cnx-598-version-message
Allows to set a version message post send
2025-05-26 11:55:45 +01:00
Dimitrie Stefanescu 5e97acf4c0 chore: ogu should be happy now 2025-05-26 11:45:03 +01:00
Dimitrie Stefanescu 9c7413d630 chore: linting errs 2025-05-26 10:43:36 +01:00
Dimitrie Stefanescu 89acd7ca80 chore: comment on the hack 2025-05-26 10:40:30 +01:00
Dimitrie Stefanescu 2e51a2fb3c feat: adds mp 2025-05-26 10:37:07 +01:00
Dimitrie Stefanescu 644754262c feat: wraps up set version message 2025-05-26 10:36:54 +01:00
Dimitrie Stefanescu b28129c30e feat: sets message. todo: fix hack when needing to query for latest version 2025-05-22 21:39:00 +01:00
Dimitrie Stefanescu 305ad36cac feat: wip 2025-05-22 20:09:33 +01:00
Dimitrie Stefanescu 554d0ee478 Merge pull request #20 from specklesystems/dim/doc-changed-event-removal
feat: removes doc changed event
2025-05-22 10:09:29 +01:00
Dimitrie Stefanescu 33fd9c65e3 feat: removes doc changed event 2025-05-22 10:01:52 +01:00
Dimitrie Stefanescu ebaee49fe8 Merge pull request #17 from specklesystems/dimitrie/cnx-1859-bring-back-search-and-create-over-project-and-model
feat: reorganises project creation
2025-05-22 09:21:04 +01:00
Dimitrie Stefanescu b877c1d321 feat: ogu is now happy 2025-05-21 19:05:08 +01:00
Dimitrie Stefanescu be4dc87b3e Merge branch 'main' into dimitrie/cnx-1859-bring-back-search-and-create-over-project-and-model 2025-05-21 19:02:20 +01:00
Dimitrie Stefanescu 94ddc486aa chore: cleanup 2025-05-21 18:56:49 +01:00
Dimitrie Stefanescu ac5984d184 feat: prefills model name, disables create button if empty name, makes notificaiton buttons primary 2025-05-21 18:56:10 +01:00
Dimitrie Stefanescu 8dae170592 feat: brings back model creation from search & cleanup 2025-05-21 18:38:10 +01:00
Dimitrie Stefanescu d6f371c7d2 chore: removes console.logs 2025-05-21 18:13:04 +01:00
Dimitrie Stefanescu cbf9e8d578 fix: handles correctly servers with no workspaces 2025-05-21 18:04:26 +01:00
Dimitrie Stefanescu 6f10519e6f chore: cleanup 2025-05-21 17:37:35 +01:00
Dimitrie Stefanescu 590b8b8872 feat: upgrade actions 2025-05-21 17:34:51 +01:00
Dimitrie Stefanescu 2514b6c606 chore: removes unused comp 2025-05-21 17:22:52 +01:00
Dimitrie Stefanescu af37112a5f feat: reorganises project creation 2025-05-21 17:20:24 +01:00
Oğuzhan Koral d224b33bc8 Remove toast for update (#11) 2025-05-21 00:16:21 +03:00
Adam Hathcock 377cfc2f65 Feat(preview): get preview image over rest api (#8)
* pass token to the model card to allow it to be used when loading a preview

* fix lint conditons

* composable for preview url and reuse in version and model cards

* remove unused import

* handle no version scenario

* remove console log

---------

Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
2025-05-20 20:53:52 +03:00
Dimitrie Stefanescu fdd13170f5 Merge pull request #9 from specklesystems/dim/codegen-without-local-server
feat: enables running the gql codegen without having a local server running
2025-05-20 18:20:10 +01:00
Dimitrie Stefanescu e70310654b Merge pull request #10 from specklesystems/dimitrie/cnx-1656-default-model-name-input-doesnt-work
fix: in the model create dialog, always defaults to either a model name
2025-05-20 18:19:50 +01:00
Dimitrie Stefanescu 8bb14d3389 fix: in the model create dialog, always defaults to either a model name 2025-05-20 18:01:01 +01:00
71 changed files with 3814 additions and 1167 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
HOST=0.0.0.0
HOST=127.0.0.1
PORT=8082
NUXT_PUBLIC_MIXPANEL_TOKEN_ID=acd87c5a50b56df91a795e999812a3a4
+13 -3
View File
@@ -13,15 +13,20 @@
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useConfigStore } from '~/store/config'
import { useAccountStore } from '~/store/accounts'
import { useHostAppStore } from '~/store/hostApp'
import { storeToRefs } from 'pinia'
import { logToSeq } from '~/lib/logger/composables/useLogger'
const uiConfigStore = useConfigStore()
const { isDarkTheme } = storeToRefs(uiConfigStore)
const hostAppStore = useHostAppStore()
const { connectorVersion, hostAppName, hostAppVersion } = storeToRefs(hostAppStore)
useHead({
// Title suffix
titleTemplate: (titleChunk) =>
titleChunk ? `${titleChunk as string} - Speckle DUIv3` : 'Speckle DUIv3',
title: computed(
() =>
`CNX: (hostApp: ${hostAppName.value}:v${hostAppVersion.value}),(version: ${connectorVersion.value})`
),
htmlAttrs: {
lang: 'en',
class: computed(() => (isDarkTheme.value ? `dark` : ``))
@@ -50,5 +55,10 @@ onMounted(() => {
uniqueEmails.add(email)
}
})
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { $intercom } = useNuxtApp() // needed her for initialisation
logToSeq('Information', 'DUI3 initialized')
})
</script>
+1 -1
View File
@@ -95,7 +95,7 @@ const showAccountsDialog = defineModel<boolean>('open', {
const isDesktopServiceAvailable = ref(false) // this should be false default because there is a delay if /ping is not successful.
app.$baseBinding.on('documentChanged', () => {
app.$baseBinding?.on('documentChanged', () => {
showAccountsDialog.value = false
})
+3 -1
View File
@@ -86,7 +86,9 @@ const startAccountAddFlow = () => {
showHelp.value = true
}, 10_000)
const url = customServerUrl.value
? `http://localhost:29364/auth/add-account?serverUrl=${customServerUrl.value}`
? `http://localhost:29364/auth/add-account?serverUrl=${
new URL(customServerUrl.value).origin
}`
: `http://localhost:29364/auth/add-account`
app.$openUrl(url)
@@ -9,7 +9,13 @@
<Component :is="iconAndColor.icon" :class="`w-4 h-4 ${iconAndColor.color}`" />
</div>
<div :class="`text-xs ${iconAndColor.color}`">
{{ result.category }}: {{ result.objectIds.length }} affected elements
{{ result.category }}:
{{
'objectIds' in props.result
? props.result.objectIds.length
: props.result.objectAppIds.length
}}
affected elements
</div>
</div>
<div v-if="result.message" class="text-xs text-foreground-2 pl-5">
@@ -19,16 +25,13 @@
</div>
</template>
<script setup lang="ts">
import { useQuery } from '@vue/apollo-composable'
import {
XMarkIcon,
InformationCircleIcon,
ExclamationTriangleIcon
} from '@heroicons/vue/24/outline'
import type { Automate } from '@speckle/shared'
import { objectQuery } from '~/lib/graphql/mutationsAndQueries'
import type { IModelCard } from '~/lib/models/card'
import { useAccountStore } from '~/store/accounts'
type ObjectResult = Automate.AutomateTypes.ResultsSchema['values']['objectResults'][0]
@@ -37,41 +40,16 @@ const props = defineProps<{
result: ObjectResult
functionId?: string
}>()
const accStore = useAccountStore()
const app = useNuxtApp()
const projectAccount = computed(() =>
accStore.accountWithFallback(props.modelCard.accountId, props.modelCard.serverUrl)
)
const clientId = projectAccount.value.accountInfo.id
const applicationIds = ref<string[]>([])
type Data = {
applicationId?: string
}
// Loop over each objectId to run the query and collect application IDs
props.result.objectIds.forEach((objectId) => {
const { result: objectResult } = useQuery(
objectQuery,
() => ({
projectId: props.modelCard.projectId,
objectId
}),
() => ({ clientId })
)
watch(objectResult, (newValue) => {
const data = newValue?.project.object?.data as Data | undefined
const applicationId = data?.applicationId
if (applicationId && !applicationIds.value.includes(applicationId)) {
applicationIds.value.push(applicationId)
}
})
const applicationIds = computed(() => {
// Old schema ignore
if ('objectIds' in props.result) return []
return Object.values(props.result.objectAppIds).filter((id) => id !== null)
})
const handleClick = async () => {
if (applicationIds.value.length === 0) return
await app.$baseBinding.highlightObjects(applicationIds.value)
}
+29 -20
View File
@@ -17,9 +17,21 @@
</div>
<div class="flex items-center group">
<FormButton
v-if="notification.cta"
v-if="notification.secondaryCta"
v-tippy="notification.secondaryCta.tooltipText"
size="sm"
:color="notificationButtonColor(notification.level)"
color="outline"
full-width
class="mr-1"
@click.stop="notification.secondaryCta.action"
>
{{ notification.secondaryCta.name }}
</FormButton>
<FormButton
v-if="notification.cta"
v-tippy="notification.cta.tooltipText"
size="sm"
color="primary"
full-width
@click.stop="notification.cta?.action"
>
@@ -47,10 +59,7 @@
<script setup lang="ts">
import { useTimeoutFn } from '@vueuse/core'
import type {
ModelCardNotification,
ModelCardNotificationLevel
} from '~/lib/models/card/notification'
import type { ModelCardNotification } from '~/lib/models/card/notification'
import { XMarkIcon } from '@heroicons/vue/24/outline'
const props = defineProps<{
notification: ModelCardNotification
@@ -62,20 +71,20 @@ if (props.notification.timeout) {
useTimeoutFn(() => emit('dismiss'), props.notification.timeout)
}
const notificationButtonColor = (notificationLevel: ModelCardNotificationLevel) => {
switch (notificationLevel) {
case 'info':
return 'outline'
case 'danger':
return 'danger'
case 'success':
return 'primary'
case 'warning':
return 'danger'
default:
return 'outline'
}
}
// const notificationButtonColor = (notificationLevel: ModelCardNotificationLevel) => {
// switch (notificationLevel) {
// case 'info':
// return 'outline'
// case 'danger':
// return 'danger'
// case 'success':
// return 'primary'
// case 'warning':
// return 'danger'
// default:
// return 'outline'
// }
// }
const textClassColor = computed(() => {
switch (props.notification.level) {
+1 -1
View File
@@ -7,7 +7,7 @@
color="neutral"
size="xs"
hide-icon
class="mb-2 mt-1"
class="mt-1"
>
<template #description>
<div class="flex items-center">
+6 -2
View File
@@ -1,7 +1,6 @@
<template>
<!-- ONLY FOR TEST FOR NOW-->
<form class="flex flex-col space-y-4 form-json-form">
<span>Settings</span>
<FormJsonForm :schema="jsonSchema" @change="onParamsFormChange"></FormJsonForm>
</form>
</template>
@@ -21,6 +20,11 @@ const jsonSchema = {
type: 'string',
title: 'Favorite Color',
enum: ['red', 'green', 'blue']
},
multiSelect: {
type: 'array',
title: 'Multi Favorite Chars',
enum: ['a', 'b', 'c', 'd']
}
}
}
@@ -28,6 +32,6 @@ const jsonSchema = {
const paramsFormState = ref<JsonFormsChangeEvent>()
const onParamsFormChange = (e: JsonFormsChangeEvent) => {
paramsFormState.value = e
console.log(JSON.stringify(e))
console.log(e)
}
</script>
+23 -1
View File
@@ -12,8 +12,11 @@
full-width
color="foundation"
/>
<FormButton color="outline" size="sm" @click="selectAllCategories">
{{ allSelected ? 'Deselect all' : 'Select all' }}
</FormButton>
</div>
<div class="flex space-y-1 flex-col">
<div class="flex space-y-1 flex-col max-h-48 simple-scrollbar overflow-auto">
<div
v-for="cat in selectedCategoriesObjects.sort((a, b) =>
a.name.localeCompare(b.name)
@@ -93,6 +96,25 @@ const searchResults = computed(() => {
const selectedCategories = ref<string[]>(props.filter.selectedCategories || [])
const selectAllCategories = () => {
if (allSelected.value) {
selectedCategories.value = []
return
}
availableCategories.value.forEach((cat) => {
const index = selectedCategories.value.indexOf(cat.id)
if (index !== -1) {
return
} else {
selectedCategories.value.push(cat.id)
}
})
}
const allSelected = computed(() => {
return availableCategories.value.length === selectedCategories.value.length
})
const selectOrUnselectCategory = (id: string) => {
const index = selectedCategories.value.indexOf(id)
if (index !== -1) {
@@ -0,0 +1,111 @@
<template>
<div>
<div class="text-foreground-2 text-body-2xs mb-1 pl-1">
{{ control.label }}
</div>
<FormSelectMulti
:model-value="modelValue"
:name="fieldName"
:rules="multiValidator"
:label="control.label"
:items="control.options"
clearable
:search="true"
:search-placeholder="'Search'"
:filter-predicate="searchFilterPredicate"
:help="control.description"
:allow-unset="false"
by="value"
button-style="tinted"
:validate-on-value-update="validateOnValueUpdate"
mount-menu-on-body
@update:model-value="handleChange"
>
<template #nothing-selected>
{{
appliedOptions['placeholder']
? appliedOptions['placeholder']
: 'Select values'
}}
</template>
<template #something-selected="{ value }">
<div ref="elementToWatchForChanges" class="flex items-center space-x-0.5">
<div ref="itemContainer" class="flex flex-wrap overflow-hidden space-x-0.5">
<div v-for="(item, i) in value" :key="item.value" class="text-foreground">
{{ item.label + (i < value.length - 1 ? ', ' : '') }}
</div>
</div>
<div v-if="hiddenSelectedItemCount > 0" class="text-foreground-2 normal">
+{{ hiddenSelectedItemCount }}
</div>
</div>
</template>
<template #option="{ item }">
<div class="flex items-center text-foreground-2 text-body-2xs">
<span class="truncate">{{ item.label }}</span>
</div>
</template>
</FormSelectMulti>
</div>
</template>
<script setup lang="ts">
import type { ControlElement } from '@jsonforms/core'
import { rendererProps, useJsonFormsEnumControl } from '@jsonforms/vue'
import type { Nullable } from '@speckle/shared'
import { useFormSelectChildInternals } from '@speckle/ui-components'
import type { GenericValidateFunction } from 'vee-validate'
import { useJsonRendererBaseSetup } from '~/lib/form/composables/jsonRenderers'
type OptionType = { value: string; label: string }
type ValueType = OptionType | OptionType[] | undefined
const emit = defineEmits<(e: 'update:modelValue', v: ValueType) => void>()
const props = defineProps({
...rendererProps<ControlElement>(),
// TODO: Doesn't appear that jsonforms properly supports multiple selection
multiple: {
type: Boolean,
default: true
},
controlOverrides: {
type: Object as PropType<Nullable<ReturnType<typeof useJsonFormsEnumControl>>>,
default: null
}
})
const searchFilterPredicate = (item: OptionType, search: string) =>
item.label.toLocaleLowerCase().includes(search.toLocaleLowerCase())
const elementToWatchForChanges = ref(null as Nullable<HTMLElement>)
const itemContainer = ref(null as Nullable<HTMLElement>)
const { hiddenSelectedItemCount, isArrayValue } =
useFormSelectChildInternals<OptionType>({
props: toRefs(props),
emit,
dynamicVisibility: { elementToWatchForChanges, itemContainer }
})
/* eslint-disable @typescript-eslint/no-explicit-any */
const multiValidator: GenericValidateFunction<any> = () => true // ignoring validation for multi enum since it is custom and jsonforms does not support it properly
const { handleChange, control, appliedOptions, fieldName, validateOnValueUpdate } =
useJsonRendererBaseSetup(props.controlOverrides || useJsonFormsEnumControl(props), {
onChangeValueConverter: (newVal: ValueType) => {
if (props.multiple && isArrayValue(newVal)) {
return newVal.map((v) => v.value)
} else if (newVal && !props.multiple && !isArrayValue(newVal)) {
return newVal.value
} else {
return undefined
}
}
})
const modelValue = computed(() => {
const val = control.value.data as OptionType[]
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return control.value.options.filter((o) => val?.includes(o.value))
})
</script>
@@ -7,8 +7,8 @@
:label="control.label"
:placeholder="appliedOptions['placeholder']"
:help="control.description"
color="foundation"
show-label
size="lg"
:validate-on-value-update="validateOnValueUpdate"
@update:model-value="handleChange"
/>
+18 -3
View File
@@ -25,6 +25,7 @@
</div>
<div class="relative group flex items-center">
<FormButton
v-if="app.$receiveBinding"
v-tippy="'Load a model from Speckle into this file'"
color="outline"
size="sm"
@@ -52,10 +53,11 @@
{{ hostAppStore.connectorVersion }}
</div>
<HeaderButton
v-if="hostAppStore.isDistributedBySpeckle"
v-tippy="'Documentation and help'"
@click="
app.$openUrl(
`https://www.speckle.systems/connectors/${hostAppStore.hostAppName}?utm=dui`
`https://docs.speckle.systems/connectors/${hostAppStore.hostAppName}?utm=dui`
)
"
>
@@ -63,7 +65,7 @@
class="w-4 text-foreground-disabled group-hover:text-foreground-2"
/>
</HeaderButton>
<HeaderButton v-tippy="'Send us feedback'" @click="showFeedbackDialog = true">
<HeaderButton v-tippy="'Send us feedback'" @click="openFeedbackDialog()">
<ChatBubbleLeftIcon
class="w-4 text-foreground-disabled group-hover:text-foreground-2"
/>
@@ -95,8 +97,21 @@ const showFeedbackDialog = ref<boolean>(false)
const showSendDialog = ref<boolean>(false)
const showReceiveDialog = ref<boolean>(false)
app.$baseBinding.on('documentChanged', () => {
app.$baseBinding?.on('documentChanged', () => {
showSendDialog.value = false
showReceiveDialog.value = false
})
const { $intercom } = useNuxtApp()
const openFeedbackDialog = () => {
if (
hostAppStore.hostAppName?.toLowerCase() === 'revit' &&
hostAppStore.hostAppVersion?.includes('2022')
) {
showFeedbackDialog.value = true
} else {
$intercom.show()
}
}
</script>
+14
View File
@@ -37,6 +37,18 @@
</div>
</MenuItem>
<div class="border-t border-outline-3 mt-1">
<MenuItem v-if="app.$revitMapperBinding" v-slot="{ active }">
<button
type="button"
:class="[
active ? 'bg-highlight-1' : '',
'my-1 text-body-2xs flex px-2 py-1 text-foreground cursor-pointer transition mx-1 rounded'
]"
@click="$router.push('/revit-mapper')"
>
Assign Revit Categories
</button>
</MenuItem>
<MenuItem
v-slot="{ active }"
@click="
@@ -109,6 +121,8 @@ import { XMarkIcon, Bars3Icon } from '@heroicons/vue/20/solid'
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
import { useConfigStore } from '~/store/config'
const app = useNuxtApp()
const uiConfigStore = useConfigStore()
const { isDarkTheme, hasConfigBindings, isDevMode } = storeToRefs(uiConfigStore)
const { toggleTheme } = uiConfigStore
+82
View File
@@ -0,0 +1,82 @@
<template>
<div class="px-2">
<p class="h5">Layer Selection</p>
<div class="space-y-2 my-2">
<!-- Multi-select layer dropdown -->
<FormSelectMulti
:key="selectedLayers.length === 0 ? 'empty' : 'hasSelection'"
:model-value="selectedLayers"
name="layerSelection"
label="Select layers"
class="w-full"
fixed-height
size="sm"
:items="layerOptions"
:allow-unset="false"
by="id"
clearable
:search="true"
:search-placeholder="''"
:filter-predicate="layerSearchFilterPredicate"
mount-menu-on-body
@update:model-value="(value) => $emit('update:selectedLayers', value as LayerOption[])"
>
<template #something-selected="{ value }">
<span class="text-primary text-xs">
{{ `${value.length} layer${value.length !== 1 ? 's' : ''} selected` }}
</span>
</template>
<template #option="{ item }">
<span class="text-xs">{{ item.name }}</span>
</template>
</FormSelectMulti>
<!-- Layer selection summary -->
<div
v-if="selectedLayers.length === 0"
class="space-y-2 p-2 bg-highlight-1 rounded-md text-body-xs"
>
<div class="text-foreground-2">
No layers selected, choose layers from the dropdown above!
</div>
</div>
<div v-else class="space-y-2 p-2 bg-highlight-1 rounded-md text-body-xs">
<div>
Selected {{ selectedLayers.length }} layer{{
selectedLayers.length !== 1 ? 's' : ''
}}:
{{ selectedLayers.map((l) => l.name).join(', ') }}
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
interface LayerOption {
id: string
name: string
[key: string]: unknown
}
defineProps<{
selectedLayers: LayerOption[]
layerOptions: LayerOption[]
}>()
defineEmits<{
'update:selectedLayers': [layers: LayerOption[]]
}>()
// Search predicate for layer dropdown
const layerSearchFilterPredicate = (
item: LayerOption | string | number | Record<string, unknown>,
query: string
): boolean => {
if (typeof item === 'object' && item !== null && 'name' in item) {
const layerItem = item as LayerOption
return layerItem.name.toLowerCase().includes(query.toLowerCase())
}
return false
}
</script>
+48
View File
@@ -0,0 +1,48 @@
<template>
<div class="py-1 px-2 bg-foundation border rounded-lg">
<div class="flex justify-between items-center">
<div class="text-xs font-medium grow">{{ categoryLabel }}</div>
<div class="flex space-x-1">
<div class="flex justify-center items-center text-xs text-foreground-2 mr-1">
{{ countText }}
</div>
<FormButton
v-if="tooltipText"
v-tippy="tooltipText"
size="sm"
color="outline"
:icon-left="CursorArrowRaysIcon"
hide-text
@click="$emit('select')"
/>
<FormButton
v-else
size="sm"
color="outline"
:icon-left="CursorArrowRaysIcon"
hide-text
@click="$emit('select')"
/>
<FormButton class="!px-1.5" size="sm" color="outline" @click="$emit('clear')">
<TrashIcon class="w-3 h-3" />
</FormButton>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { CursorArrowRaysIcon, TrashIcon } from '@heroicons/vue/24/outline'
defineProps<{
categoryLabel: string
countText: string
tooltipText?: string
}>()
defineEmits<{
select: []
clear: []
}>()
</script>
+15
View File
@@ -0,0 +1,15 @@
<template>
<div class="space-y-2 p-2 bg-highlight-1 rounded-md text-body-xs">
<div v-if="!hasSelection">
No objects selected, go ahead and select some from your model!
</div>
<div v-else>{{ selectionSummary }}</div>
</div>
</template>
<script setup lang="ts">
defineProps<{
hasSelection: boolean
selectionSummary: string
}>()
</script>
+1 -1
View File
@@ -73,7 +73,7 @@ const hasSettings = computed(() => {
})
const app = useNuxtApp()
app.$baseBinding.on('documentChanged', () => {
app.$baseBinding?.on('documentChanged', () => {
openModelCardActionsDialog.value = false
})
+55 -3
View File
@@ -19,7 +19,7 @@
"
hide-text
class=""
:disabled="!canEdit"
:disabled="!canEdit || isSettingsMissing"
@click.stop="$emit('manual-publish-or-load')"
></FormButton>
</div>
@@ -57,6 +57,16 @@
</button>
</template>
</AutomateResultDialog>
<!-- To test missing settings -->
<!-- <FormButton
v-if="!isSettingsMissing"
v-tippy="'Refresh settings are needed'"
color="subtle"
:icon-left="TrashIcon"
hide-text
size="sm"
@click="deleteSettings"
/> -->
<FormButton
v-if="store.hostAppName !== 'navisworks' && store.hostAppName !== 'etabs'"
v-tippy="'Highlight'"
@@ -92,7 +102,24 @@
Fetching model data...
<CommonLoadingBar loading />
</div>
<div v-else class="px-1 py-1">Error loading data.</div>
<div
v-else
class="flex flex-row items-center px-2 pt-2 text-body-2xs text-foreground-2 truncate text-red-500"
>
<span class="ml-1.5">Error on loading model data.</span>
<div class="flex items-center justify-end grow">
<FormButton
v-tippy="'Remove model card'"
color="subtle"
:icon-left="TrashIcon"
hide-text
size="sm"
class="text-red-500"
@click.stop="removeModel"
/>
</div>
</div>
<!-- Slot to allow senders or receivers to hoist their own buttons/ui -->
<!-- class="px-2 h-0 group-hover:h-auto transition-all overflow-hidden" -->
@@ -207,7 +234,7 @@ import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useIntervalFn, useTimeoutFn } from '@vueuse/core'
import type { ProjectCommentsUpdatedMessage } from '~/lib/common/generated/gql/graphql'
import { useFunctionRunsStatusSummary } from '~/lib/automate/runStatus'
import { CursorArrowRaysIcon, XCircleIcon } from '@heroicons/vue/24/outline'
import { CursorArrowRaysIcon, XCircleIcon, TrashIcon } from '@heroicons/vue/24/outline'
import type { AvatarUserWithId } from '@speckle/ui-components'
const app = useNuxtApp()
@@ -321,6 +348,31 @@ const highlightModel = () => {
trackEvent('DUI3 Action', { name: 'Highlight Model' }, props.modelCard.accountId)
}
const isSettingsMissing = computed(() =>
isSender.value ? isSendSettingsMissing.value : isReceiveSettingsMissing.value
)
const isSendSettingsMissing = computed(
() =>
isSender.value &&
store.sendSettings &&
store.sendSettings.length > 0 &&
!props.modelCard.settings
)
const isReceiveSettingsMissing = computed(
() =>
!isSender.value &&
store.receiveSettings &&
store.receiveSettings.length > 0 &&
!props.modelCard.settings
)
// To test missing settings
// const deleteSettings = async () => {
// await store.patchModel(props.modelCard.modelCardId, { settings: undefined })
// }
const viewModel = () => {
// previously with DUI2, it was Stream View but actually it is "Version View" now. Also having conflict with old/new terminology.
trackEvent('DUI3 Action', { name: 'Version View' }, props.modelCard.accountId)
-186
View File
@@ -1,186 +0,0 @@
<template>
<div class="p-0">
<slot name="activator" :toggle="toggleDialog"></slot>
<CommonDialog
v-model:open="showModelCreateDialog"
:title="canCreateModelInWorkspace ? `Create new model` : errorMessage?.title"
fullscreen="none"
>
<form v-if="canCreateModelInWorkspace" @submit="onSubmitCreateNewModel">
<div class="text-body-2xs mb-2 ml-1">Model name</div>
<FormTextInput
v-model="newModelName"
class="text-xs"
autocomplete="off"
name="name"
label="Model name"
color="foundation"
:show-clear="!!newModelName"
:placeholder="hostAppStore.documentInfo?.name"
:rules="[
ValidationHelpers.isRequired,
ValidationHelpers.isStringOfLength({ minLength: 3 })
]"
full-width
/>
<div class="mt-4 flex justify-end items-center space-x-2 w-full">
<FormButton size="sm" text @click="showModelCreateDialog = false">
Cancel
</FormButton>
<FormButton size="sm" submit :disabled="isCreatingModel">Create</FormButton>
</div>
</form>
<div v-else class="m-2">
{{ errorMessage?.description }}
<div class="flex mt-2 space-x-2 justify-end">
<FormButton size="sm" color="outline" @click="showModelCreateDialog = false">
Close
</FormButton>
<FormButton
v-if="errorMessage?.cta"
size="sm"
submit
@click="errorMessage?.cta?.action(), (showModelCreateDialog = false)"
>
{{ errorMessage?.cta?.name }}
</FormButton>
</div>
</div>
</CommonDialog>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useMutation, provideApolloClient, useQuery } from '@vue/apollo-composable'
import type { ModelListModelItemFragment } from '~/lib/common/generated/gql/graphql'
import { useForm } from 'vee-validate'
import { ValidationHelpers } from '@speckle/ui-components'
import type { DUIAccount } from '~/store/accounts'
import { useAccountStore } from '~/store/accounts'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useHostAppStore } from '~/store/hostApp'
import {
canCreateModelInProjectQuery,
createModelMutation
} from '~/lib/graphql/mutationsAndQueries'
type WorkspacePermissionMessage = {
title: string
description: string
cta?: {
name: string
action: () => void
}
}
const { $openUrl } = useNuxtApp()
const showModelCreateDialog = ref(false)
const isCreatingModel = ref(false)
const props = defineProps<{
projectId: string
workspaceId?: string
workspaceSlug?: string
}>()
const emit = defineEmits<{
(e: 'model:created', model: ModelListModelItemFragment): void
}>()
const { trackEvent } = useMixpanel()
const accountStore = useAccountStore()
const hostAppStore = useHostAppStore()
const { activeAccount } = storeToRefs(accountStore)
const accountId = computed(() => activeAccount.value.accountInfo.id)
const newModelName = ref<string>()
const errorMessage = ref<WorkspacePermissionMessage>()
const toggleDialog = () => {
showModelCreateDialog.value = !showModelCreateDialog.value
}
const account = computed(() => {
return accountStore.accounts.find(
(acc) => acc.accountInfo.id === accountId.value
) as DUIAccount
})
const canCreateModelInWorkspace = ref<boolean>()
const { result: canCreateModelInWorkspaceResult } = useQuery(
canCreateModelInProjectQuery,
() => ({ projectId: props.projectId }),
() => ({
clientId: accountId.value,
debounce: 500,
fetchPolicy: 'network-only'
})
)
watch(canCreateModelInWorkspaceResult, (val) => {
if (val?.project.permissions.canCreateModel.code !== 'OK') {
switch (val?.project.permissions.canCreateModel.code) {
case 'WorkspaceLimitsReached':
errorMessage.value = {
title: 'Plan limit reached',
description:
'The model limit for this workspace has been reached. Upgrade the workspace plan to create or move more models.',
cta: {
name: 'Explore Plans',
action: () =>
$openUrl(
`${account.value.accountInfo.serverInfo.url}/settings/workspaces/${props.workspaceSlug}/billing`
)
}
}
break
// TODO: we should add more cases later according to `code`
default:
errorMessage.value = {
title: 'Workspace warning',
description: val?.project.permissions.canCreateModel.message ?? 'error'
}
break
}
canCreateModelInWorkspace.value = false
} else {
canCreateModelInWorkspace.value = true
}
})
const createNewModel = async (name: string) => {
isCreatingModel.value = true
void trackEvent('DUI3 Action', { name: 'Model Create' }, account.value.accountInfo.id)
const { mutate } = provideApolloClient(account.value.client)(() =>
useMutation(createModelMutation)
)
const res = await mutate({ input: { projectId: props.projectId, name } })
if (res?.data?.modelMutations.create) {
emit('model:created', res?.data?.modelMutations.create)
// refetch() // Sorts the list with newly created model otherwise it will put the model at the bottom.
// emit('next', res?.data?.modelMutations.create)
} else {
let errorMessage = 'Undefined error'
if (res?.errors && res?.errors.length !== 0) {
errorMessage = res?.errors[0].message
}
hostAppStore.setNotification({
type: 1,
title: 'Failed to create model',
description: errorMessage
})
}
isCreatingModel.value = false
}
const { handleSubmit } = useForm<{ name: string }>()
const onSubmitCreateNewModel = handleSubmit(() => {
void createNewModel(newModelName.value as string)
})
</script>
+41 -11
View File
@@ -19,7 +19,7 @@
color="subtle"
class="block text-foreground-2 hover:text-foreground overflow-hidden max-w-full !justify-start"
full-width
:disabled="!!modelCard.progress || !canEdit"
:disabled="!!modelCard.progress || !canEdit || isReceiveSettingsMissing"
@click.stop="openVersionsDialog = true"
>
<span>
@@ -52,10 +52,16 @@
:model-id="modelCard.modelId"
:workspace-slug="modelCard.workspaceSlug"
:selected-version-id="modelCard.selectedVersionId"
:settings="modelCard.settings"
@next="handleVersionSelection"
@update:settings="handleUpdateSettings"
/>
</CommonDialog>
<template #states>
<CommonModelNotification
v-if="isReceiveSettingsMissing"
:notification="receiveSettingsMissingNotification"
/>
<CommonModelNotification
v-if="expiredNotification"
:notification="expiredNotification"
@@ -95,6 +101,7 @@ import type { VersionListItemFragment } from '~/lib/common/generated/gql/graphql
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useInterval, watchOnce } from '@vueuse/core'
import { useAccountStore } from '~~/store/accounts'
import type { CardSetting } from '~/lib/models/card/setting'
const { trackEvent } = useMixpanel()
const app = useNuxtApp()
@@ -114,14 +121,44 @@ const projectAccount = computed(() =>
accountStore.accountWithFallback(props.project.accountId, props.project.serverUrl)
)
app.$baseBinding.on('documentChanged', () => {
app.$baseBinding?.on('documentChanged', () => {
openVersionsDialog.value = false
})
const isReceiveSettingsMissing = computed(
() =>
store.receiveSettings &&
store.receiveSettings.length > 0 &&
!props.modelCard.settings
)
const receiveSettingsMissingNotification = computed(() => {
const notification = {} as ModelCardNotification
notification.dismissible = false
notification.level = 'danger'
notification.text = 'Load settings are corrupted for some reason.'
notification.cta = {
name: 'Refresh',
action: async () => {
await store.patchModel(props.modelCard.modelCardId, {
settings: store.receiveSettings
})
}
}
return notification
})
const isExpired = computed(() => {
return props.modelCard.latestVersionId !== props.modelCard.selectedVersionId
})
const handleUpdateSettings = async (settings: CardSetting[]) => {
await store.patchModel(props.modelCard.modelCardId, {
settings
})
}
// Cancels any in progress receive AND load selected version
const handleVersionSelection = async (
selectedVersion: VersionListItemFragment,
@@ -178,7 +215,7 @@ const expiredNotification = computed(() => {
if (props.modelCard.latestVersionId === props.modelCard.selectedVersionId) return
const notification = {} as ModelCardNotification
notification.dismissible = true
notification.level = 'info'
notification.level = 'success'
notification.text = 'Newer version available!'
notification.cta = {
name: 'Update',
@@ -203,13 +240,6 @@ const receiveResultNotificationText = computed(() => {
return 'Model loaded!'
})
const receiveResultNotificationLevel = computed(() => {
if (failRate.value > 80) {
return 'warning'
}
return 'info'
})
const receiveResultNotification = computed(() => {
if (
!props.modelCard.bakedObjectIds ||
@@ -219,7 +249,7 @@ const receiveResultNotification = computed(() => {
const notification = {} as ModelCardNotification
notification.dismissible = true
notification.level = receiveResultNotificationLevel.value
notification.level = 'success'
notification.text = receiveResultNotificationText.value
notification.report = props.modelCard.report
notification.cta = {
+145 -3
View File
@@ -13,7 +13,7 @@
size="sm"
color="subtle"
class="block text-foreground-2 hover:text-foreground overflow-hidden max-w-full !justify-start"
:disabled="!!modelCard.progress || !props.canEdit"
:disabled="!!modelCard.progress || !props.canEdit || isSendSettingsMissing"
full-width
@click.stop="openFilterDialog = true"
>
@@ -41,7 +41,49 @@
</div>
</CommonDialog>
<CommonDialog
v-model:open="showSetMessageDialog"
title="Version message"
fullscreen="none"
>
<form @submit="setVersionMessage(versionMessage as string)">
<div class="text-body-2xs mb-2 ml-1">
Describe your latest changes to help keep track of design intent.
</div>
<FormTextArea
v-model="versionMessage"
class="text-xs"
placeholder="Moved elements to prevent clash"
autocomplete="off"
name="name"
label="Version message"
color="foundation"
:show-clear="!!versionMessage"
:rules="[ValidationHelpers.isStringOfLength({ minLength: 3 })]"
full-width
/>
<CommonLoadingBar v-if="isUpdatingVersionMessage" loading />
<div class="mt-4 flex justify-end items-center space-x-2 w-full">
<FormButton size="sm" text @click="showSetMessageDialog = false">
Cancel
</FormButton>
<FormButton
size="sm"
submit
:disabled="
isUpdatingVersionMessage || !versionMessage || versionMessage.length < 3
"
>
Save
</FormButton>
</div>
</form>
</CommonDialog>
<template #states>
<CommonModelNotification
v-if="isSendSettingsMissing"
:notification="sendSettingsMissingNotification"
/>
<CommonModelNotification
v-if="expiredNotification"
:notification="expiredNotification"
@@ -74,6 +116,11 @@ import type { ISendFilter, ISenderModelCard } from '~/lib/models/card/send'
import type { ProjectModelGroup } from '~/store/hostApp'
import { useHostAppStore } from '~/store/hostApp'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { ToastNotificationType, ValidationHelpers } from '@speckle/ui-components'
import { provideApolloClient, useMutation } from '@vue/apollo-composable'
import { useAccountStore, type DUIAccount } from '~/store/accounts'
import { setVersionMessageMutation } from '~/lib/graphql/mutationsAndQueries'
const hostAppStore = useHostAppStore()
const { trackEvent } = useMixpanel()
const app = useNuxtApp()
@@ -87,7 +134,7 @@ const props = defineProps<{
const store = useHostAppStore()
const openFilterDialog = ref(false)
app.$baseBinding.on('documentChanged', () => {
app.$baseBinding?.on('documentChanged', () => {
openFilterDialog.value = false
})
@@ -97,6 +144,7 @@ const sendOrCancel = () => {
}
if (props.modelCard.progress) store.sendModelCancel(props.modelCard.modelCardId)
else store.sendModel(props.modelCard.modelCardId, 'ModelCardButton')
hasSetVersionMessage.value = false
}
let newFilter: ISendFilter
@@ -120,11 +168,86 @@ const saveFilter = async () => {
openFilterDialog.value = false
}
const showSetMessageDialog = ref(false)
const isUpdatingVersionMessage = ref(false)
const hasSetVersionMessage = ref(false)
const versionMessage = ref<string>()
const accountStore = useAccountStore()
const account = accountStore.accounts.find(
(acc) => acc.accountInfo.id === props.project.accountId
) as DUIAccount
const setVersionMessage = async (message: string) => {
if (!props.modelCard.latestCreatedVersionId) {
return
}
void trackEvent('DUI3 Action', {
name: 'Set version message'
})
isUpdatingVersionMessage.value = true
const { mutate } = provideApolloClient(account.client)(() =>
useMutation(setVersionMessageMutation)
)
const res = await mutate({
input: {
projectId: props.project.projectId,
versionId: props.modelCard.latestCreatedVersionId,
message
}
})
if (res?.data?.versionMutations.update.id) {
// seemed to noisy, and autoclose does not work for some reason.
// nicer ux to just close the dialog
// hostAppStore.setNotification({
// type: ToastNotificationType.Info,
// title: 'Version message saved',
// autoClose: true
// })
hasSetVersionMessage.value = true
} else {
hostAppStore.setNotification({
type: ToastNotificationType.Danger,
title: 'Request failed',
description: 'Failed to update version message.',
autoClose: true
})
}
showSetMessageDialog.value = false
isUpdatingVersionMessage.value = false
}
const saveFilterAndSend = async () => {
await saveFilter()
store.sendModel(props.modelCard.modelCardId, 'Filter')
hasSetVersionMessage.value = false
}
const isSendSettingsMissing = computed(
() => store.sendSettings && store.sendSettings.length > 0 && !props.modelCard.settings
)
const sendSettingsMissingNotification = computed(() => {
const notification = {} as ModelCardNotification
notification.dismissible = false
notification.level = 'danger'
notification.text = 'Publish settings are corrupted for some reason.'
notification.cta = {
name: 'Refresh',
action: async () => {
await store.patchModel(props.modelCard.modelCardId, {
settings: store.sendSettings
})
}
}
return notification
})
const expiredNotification = computed(() => {
if (!props.modelCard.expired) return
@@ -139,6 +262,7 @@ const expiredNotification = computed(() => {
notification.cta = {
name: ctaType,
action: async () => {
hasSetVersionMessage.value = false
if (props.modelCard.progress) {
await store.sendModelCancel(props.modelCard.modelCardId)
}
@@ -188,8 +312,26 @@ const latestVersionNotification = computed(() => {
notification.level = sendResultNotificationLevel.value
notification.text = sendResultNotificationText.value
notification.report = props.modelCard.report
// NOTE: this prevents us displaying the set message button for non-updated
// connectors that send over the root object id over instead of the commit id
if (
props.modelCard.latestCreatedVersionId.length === 10 &&
!hasSetVersionMessage.value
) {
notification.secondaryCta = {
name: 'Set message',
tooltipText: 'Describe your changes',
action: () => {
showSetMessageDialog.value = true
versionMessage.value = ''
}
}
}
notification.cta = {
name: 'View version',
name: 'View',
tooltipText: 'Check your model in the browser!',
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
action: () => cardBase.value?.viewModel()
}
@@ -27,28 +27,29 @@ import type { CardSetting, CardSettingValue } from '~/lib/models/card/setting'
import type { JsonFormsChangeEvent } from '@jsonforms/vue'
import { cloneDeep, omit } from 'lodash-es'
import type { JsonSchema } from '@jsonforms/core'
import { useHostAppStore } from '~/store/hostApp'
// import { useHostAppStore } from '~/store/hostApp'
const props = defineProps<{
settings?: CardSetting[]
defaultSettings: CardSetting[]
expandable: boolean
}>()
const emit = defineEmits<{ (e: 'update:settings', value: CardSetting[]): void }>()
const store = useHostAppStore()
// const store = useHostAppStore()
const defaultSendSettings = computed(() => store.sendSettings)
const sendSettings = ref<CardSetting[] | undefined>(
cloneDeep(props.settings ?? defaultSendSettings.value) // need to prevent mutation!
// const defaultSendSettings = computed(() => store.sendSettings)
const settings = ref<CardSetting[] | undefined>(
cloneDeep(props.settings ?? props.defaultSettings) // need to prevent mutation!
)
const showSettings = ref(!props.expandable)
const settingsJsonForms = computed(() => {
if (sendSettings.value === undefined) return {}
if (settings.value === undefined) return {}
const obj: JsonSchema = { type: 'object', properties: {} }
sendSettings.value.forEach((setting: CardSetting) => {
settings.value.forEach((setting: CardSetting) => {
const mappedSetting = omit({ ...setting, $id: setting.id }, ['id'])
if (obj && obj.properties) {
obj.properties[setting.id] = mappedSetting
@@ -60,8 +61,8 @@ const settingsJsonForms = computed(() => {
type DataType = Record<string, unknown>
const data = computed(() => {
const settingValues = {} as DataType
if (sendSettings.value) {
sendSettings.value.forEach((setting) => {
if (settings.value) {
settings.value.forEach((setting) => {
settingValues[setting.id as string] = setting.value
})
}
@@ -69,14 +70,14 @@ const data = computed(() => {
})
const onParamsFormChange = (e: JsonFormsChangeEvent) => {
if (sendSettings.value === undefined) return
sendSettings.value?.forEach((setting) => {
if (settings.value === undefined) return
settings.value?.forEach((setting) => {
if (setting) {
if (setting.value !== (e.data as DataType)[setting.id]) {
setting.value = (e.data as DataType)[setting.id] as CardSettingValue
}
}
})
emit('update:settings', sendSettings.value)
emit('update:settings', settings.value)
}
</script>
-220
View File
@@ -1,220 +0,0 @@
<template>
<div class="p-0">
<slot name="activator" :toggle="toggleDialog"></slot>
<CommonDialog
v-model:open="showProjectCreateDialog"
:title="`Create new project`"
fullscreen="none"
>
<form @submit="onSubmitCreateNewProject">
<div class="text-body-2xs mb-2 ml-1">Project name</div>
<FormTextInput
v-model="newProjectName"
class="text-xs"
placeholder="A Beautiful Home, A Small Bridge..."
autocomplete="off"
name="name"
label="Project name"
color="foundation"
:show-clear="!!newProjectName"
:rules="[
ValidationHelpers.isRequired,
ValidationHelpers.isStringOfLength({ minLength: 3 })
]"
full-width
/>
<div class="mt-4 flex justify-end items-center space-x-2 w-full">
<FormButton size="sm" text @click="showProjectCreateDialog = false">
Cancel
</FormButton>
<FormButton
size="sm"
submit
:disabled="isCreatingProject || !canCreateProject"
>
Create
</FormButton>
</div>
</form>
</CommonDialog>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useMutation, provideApolloClient, useQuery } from '@vue/apollo-composable'
import type { ProjectListProjectItemFragment } from '~/lib/common/generated/gql/graphql'
import {
canCreatePersonalProjectQuery,
canCreateProjectInWorkspaceQuery,
createProjectInWorkspaceMutation,
createProjectMutation
} from '~/lib/graphql/mutationsAndQueries'
import type { DUIAccount } from '~/store/accounts'
import { useAccountStore } from '~/store/accounts'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useHostAppStore } from '~/store/hostApp'
import { useForm } from 'vee-validate'
import { ValidationHelpers } from '@speckle/ui-components'
const showProjectCreateDialog = ref(false)
const isCreatingProject = ref(false)
const props = defineProps<{ workspaceId?: string }>()
const emit = defineEmits<{
(e: 'project:created', result: ProjectListProjectItemFragment): void
}>()
const { trackEvent } = useMixpanel()
const accountStore = useAccountStore()
const hostAppStore = useHostAppStore()
const { activeAccount } = storeToRefs(accountStore)
const accountId = computed(() => activeAccount.value.accountInfo.id)
const newProjectName = ref<string>()
const errorMessageForWorkspace = ref<string>()
const errorMessageForPersonalProject = ref<string>()
const toggleDialog = () => {
showProjectCreateDialog.value = !showProjectCreateDialog.value
}
const account = computed(() => {
return accountStore.accounts.find(
(acc) => acc.accountInfo.id === accountId.value
) as DUIAccount
})
const canCreateProject = computed(() =>
props.workspaceId === 'personalProject'
? canCreatePersonalProject.value
: canCreateProjectInWorkspace.value
)
const { result: canCreatePersonalProjectResult } = useQuery(
canCreatePersonalProjectQuery,
() => ({}),
() => ({
clientId: accountId.value,
debounce: 500,
fetchPolicy: 'network-only'
})
)
watch(canCreatePersonalProjectResult, (val) => {
if (val?.activeUser?.permissions.canCreatePersonalProject.code !== 'OK') {
errorMessageForPersonalProject.value =
val?.activeUser?.permissions.canCreatePersonalProject.message
}
})
const canCreatePersonalProject = computed(() => {
try {
return (
canCreatePersonalProjectResult.value?.activeUser?.permissions
.canCreatePersonalProject.code === 'OK'
)
} catch {
return true
}
})
const { result: canCreateProjectInWorkspaceResult } = useQuery(
canCreateProjectInWorkspaceQuery,
() => ({ workspaceId: props.workspaceId ?? 'null' }), // TODO: i do not know the potential cause here
() => ({
clientId: accountId.value,
debounce: 500,
fetchPolicy: 'network-only'
})
)
watch(canCreateProjectInWorkspaceResult, (val) => {
if (val?.workspace.permissions.canCreateProject.code !== 'OK') {
errorMessageForWorkspace.value = val?.workspace.permissions.canCreateProject.message
}
})
const canCreateProjectInWorkspace = computed(() => {
try {
return (
canCreateProjectInWorkspaceResult.value?.workspace.permissions.canCreateProject
.code === 'OK'
)
} catch {
return true
}
})
const { handleSubmit } = useForm<{ name: string }>()
const onSubmitCreateNewProject = handleSubmit(() => {
// TODO: Chat with Fabians
// This works, but if we use handleSubmit(args) > args.name -> it is undefined in Production on netlify, but works fine on local dev
void createNewProject(newProjectName.value as string)
})
const createNewProject = async (name: string) => {
isCreatingProject.value = true
if (props.workspaceId !== 'personalProject' && props.workspaceId !== undefined) {
createNewProjectInWorkspace(name)
isCreatingProject.value = false
return
}
void trackEvent(
'DUI3 Action',
{ name: 'Project Create', workspace: false },
account.value.accountInfo.id
)
const { mutate } = provideApolloClient(account.value.client)(() =>
useMutation(createProjectMutation)
)
const res = await mutate({ input: { name } })
if (res?.data?.projectMutations.create) {
emit('project:created', res?.data?.projectMutations.create)
} else {
let errorMessage = 'Undefined error'
if (res?.errors && res?.errors.length !== 0) {
errorMessage = res?.errors[0].message
}
hostAppStore.setNotification({
type: 1,
title: 'Failed to create project',
description: errorMessage
})
}
isCreatingProject.value = false
}
const createNewProjectInWorkspace = async (name: string) => {
void trackEvent(
'DUI3 Action',
{ name: 'Project Create', workspace: true },
account.value.accountInfo.id
)
const { mutate } = provideApolloClient(account.value.client)(() =>
useMutation(createProjectInWorkspaceMutation)
)
const res = await mutate({
input: { name, workspaceId: props.workspaceId as string }
})
if (res?.data?.workspaceMutations.projects.create) {
emit('project:created', res?.data?.workspaceMutations.projects.create)
} else {
let errorMessage = 'Undefined error'
if (res?.errors && res?.errors.length !== 0) {
errorMessage = res?.errors[0].message
}
hostAppStore.setNotification({
type: 1,
title: 'Failed to create project',
description: errorMessage
})
}
}
</script>
@@ -1,141 +0,0 @@
<template>
<div class="p-0">
<slot name="activator" :toggle="toggleDialog"></slot>
<CommonDialog
v-model:open="showProjectCreateDialog"
:title="`Create new project`"
fullscreen="none"
>
<form @submit="onSubmitCreateNewProject">
<div class="text-body-2xs mb-2 ml-1">Project name</div>
<FormTextInput
v-model="newProjectName"
class="text-xs"
placeholder="A Beautiful Home, A Small Bridge..."
autocomplete="off"
name="name"
label="Project name"
color="foundation"
:show-clear="!!newProjectName"
:rules="[
ValidationHelpers.isRequired,
ValidationHelpers.isStringOfLength({ minLength: 3 })
]"
full-width
/>
<div class="mt-4 flex justify-end items-center space-x-2 w-full">
<FormButton size="sm" text @click="showProjectCreateDialog = false">
Cancel
</FormButton>
<FormButton
size="sm"
submit
:disabled="isCreatingProject || !canCreatePersonalProject"
>
Create
</FormButton>
</div>
</form>
</CommonDialog>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useMutation, provideApolloClient, useQuery } from '@vue/apollo-composable'
import type { ProjectListProjectItemFragment } from '~/lib/common/generated/gql/graphql'
import {
canCreatePersonalProjectQuery,
createProjectMutation
} from '~/lib/graphql/mutationsAndQueries'
import type { DUIAccount } from '~/store/accounts'
import { useAccountStore } from '~/store/accounts'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useHostAppStore } from '~/store/hostApp'
import { useForm } from 'vee-validate'
import { ValidationHelpers } from '@speckle/ui-components'
const showProjectCreateDialog = ref(false)
const isCreatingProject = ref(false)
const emit = defineEmits<{
(e: 'project:created', result: ProjectListProjectItemFragment): void
}>()
const { trackEvent } = useMixpanel()
const accountStore = useAccountStore()
const hostAppStore = useHostAppStore()
const { activeAccount } = storeToRefs(accountStore)
const accountId = computed(() => activeAccount.value.accountInfo.id)
const newProjectName = ref<string>()
const errorMessage = ref<string>()
const toggleDialog = () => {
showProjectCreateDialog.value = !showProjectCreateDialog.value
}
const account = computed(() => {
return accountStore.accounts.find(
(acc) => acc.accountInfo.id === accountId.value
) as DUIAccount
})
const canCreatePersonalProject = ref<boolean>(false)
const { result: canCreatePersonalProjectResult } = useQuery(
canCreatePersonalProjectQuery,
() => ({}),
() => ({
clientId: accountId.value,
debounce: 500,
fetchPolicy: 'network-only'
})
)
watch(canCreatePersonalProjectResult, (val) => {
if (val?.activeUser?.permissions.canCreatePersonalProject.code !== 'OK') {
errorMessage.value = val?.activeUser?.permissions.canCreatePersonalProject.message
canCreatePersonalProject.value = false
} else {
canCreatePersonalProject.value = true
}
})
const { handleSubmit } = useForm<{ name: string }>()
const onSubmitCreateNewProject = handleSubmit(() => {
// TODO: Chat with Fabians
// This works, but if we use handleSubmit(args) > args.name -> it is undefined in Production on netlify, but works fine on local dev
void createNewProject(newProjectName.value as string)
})
const createNewProject = async (name: string) => {
isCreatingProject.value = true
void trackEvent(
'DUI3 Action',
{ name: 'Project Create', workspace: false },
account.value.accountInfo.id
)
const { mutate } = provideApolloClient(account.value.client)(() =>
useMutation(createProjectMutation)
)
const res = await mutate({ input: { name } })
if (res?.data?.projectMutations.create) {
emit('project:created', res?.data?.projectMutations.create)
} else {
let errorMessage = 'Undefined error'
if (res?.errors && res?.errors.length !== 0) {
errorMessage = res?.errors[0].message
}
hostAppStore.setNotification({
type: 1,
title: 'Failed to create project',
description: errorMessage
})
}
isCreatingProject.value = false
}
</script>
@@ -1,198 +0,0 @@
<template>
<div class="p-0">
<slot name="activator" :toggle="toggleDialog"></slot>
<CommonDialog
v-model:open="showProjectCreateDialog"
:title="canCreateProjectInWorkspace ? `Create new project` : errorMessage?.title"
fullscreen="none"
>
<form v-if="canCreateProjectInWorkspace" @submit="onSubmitCreateNewProject">
<div class="text-body-2xs mb-2 ml-1">Project name</div>
<FormTextInput
v-model="newProjectName"
class="text-xs"
placeholder="A Beautiful Home, A Small Bridge..."
autocomplete="off"
name="name"
label="Project name"
color="foundation"
:show-clear="!!newProjectName"
:rules="[
ValidationHelpers.isRequired,
ValidationHelpers.isStringOfLength({ minLength: 3 })
]"
full-width
/>
<div class="mt-4 flex justify-end items-center space-x-2 w-full">
<FormButton
size="sm"
color="outline"
@click="showProjectCreateDialog = false"
>
Cancel
</FormButton>
<FormButton size="sm" submit :disabled="isCreatingProject">Create</FormButton>
</div>
</form>
<div v-else class="m-2">
{{ errorMessage?.description }}
<div class="flex mt-2 space-x-2 justify-end">
<FormButton
size="sm"
color="outline"
@click="showProjectCreateDialog = false"
>
Close
</FormButton>
<FormButton
v-if="errorMessage?.cta"
size="sm"
submit
@click="errorMessage?.cta?.action(), (showProjectCreateDialog = false)"
>
{{ errorMessage?.cta?.name }}
</FormButton>
</div>
</div>
</CommonDialog>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useMutation, provideApolloClient, useQuery } from '@vue/apollo-composable'
import type {
ProjectListProjectItemFragment,
WorkspaceListWorkspaceItemFragment
} from '~/lib/common/generated/gql/graphql'
import {
canCreateProjectInWorkspaceQuery,
createProjectInWorkspaceMutation
} from '~/lib/graphql/mutationsAndQueries'
import type { DUIAccount } from '~/store/accounts'
import { useAccountStore } from '~/store/accounts'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useHostAppStore } from '~/store/hostApp'
import { useForm } from 'vee-validate'
import { ValidationHelpers } from '@speckle/ui-components'
type WorkspacePermissionMessage = {
title: string
description: string
cta?: {
name: string
action: () => void
}
}
const { $openUrl } = useNuxtApp()
const showProjectCreateDialog = ref(false)
const isCreatingProject = ref(false)
const props = defineProps<{ workspace?: WorkspaceListWorkspaceItemFragment }>()
const emit = defineEmits<{
(e: 'project:created', result: ProjectListProjectItemFragment): void
}>()
const { trackEvent } = useMixpanel()
const accountStore = useAccountStore()
const hostAppStore = useHostAppStore()
const { activeAccount } = storeToRefs(accountStore)
const accountId = computed(() => activeAccount.value.accountInfo.id)
const newProjectName = ref<string>()
const errorMessage = ref<WorkspacePermissionMessage>()
const toggleDialog = () => {
showProjectCreateDialog.value = !showProjectCreateDialog.value
}
const account = computed(() => {
return accountStore.accounts.find(
(acc) => acc.accountInfo.id === accountId.value
) as DUIAccount
})
const canCreateProjectInWorkspace = ref<boolean>()
const { result: canCreateProjectInWorkspaceResult } = useQuery(
canCreateProjectInWorkspaceQuery,
() => ({ workspaceId: props.workspace?.id ?? 'null' }), // TODO: i do not know the potential cause here
() => ({
clientId: accountId.value,
debounce: 500,
fetchPolicy: 'network-only'
})
)
watch(canCreateProjectInWorkspaceResult, (val) => {
if (val?.workspace.permissions.canCreateProject.code !== 'OK') {
switch (val?.workspace.permissions.canCreateProject.code) {
case 'WorkspaceLimitsReached':
errorMessage.value = {
title: 'Plan limit reached',
description:
'The project limit for this workspace has been reached. Upgrade the workspace plan to create or move more projects.',
cta: {
name: 'Explore Plans',
action: () =>
$openUrl(
`${account.value.accountInfo.serverInfo.url}/settings/workspaces/${props.workspace?.slug}/billing`
)
}
}
break
// TODO: we should add more cases later according to `code`
default:
errorMessage.value = {
title: 'Workspace warning',
description: val?.workspace.permissions.canCreateProject.message ?? 'error'
}
break
}
canCreateProjectInWorkspace.value = false
} else {
canCreateProjectInWorkspace.value = true
}
})
const { handleSubmit } = useForm<{ name: string }>()
const onSubmitCreateNewProject = handleSubmit(() => {
// TODO: Chat with Fabians
// This works, but if we use handleSubmit(args) > args.name -> it is undefined in Production on netlify, but works fine on local dev
void createNewProjectInWorkspace(newProjectName.value as string)
})
const createNewProjectInWorkspace = async (name: string) => {
isCreatingProject.value = true
void trackEvent(
'DUI3 Action',
{ name: 'Project Create', workspace: true },
account.value.accountInfo.id
)
const { mutate } = provideApolloClient(account.value.client)(() =>
useMutation(createProjectInWorkspaceMutation)
)
const res = await mutate({
input: { name, workspaceId: props.workspace?.id as string }
})
if (res?.data?.workspaceMutations.projects.create) {
emit('project:created', res?.data?.workspaceMutations.projects.create)
} else {
let errorMessage = 'Undefined error'
if (res?.errors && res?.errors.length !== 0) {
errorMessage = res?.errors[0].message
}
hostAppStore.setNotification({
type: 1,
title: 'Failed to create project',
description: errorMessage
})
}
isCreatingProject.value = false
}
</script>
+10 -1
View File
@@ -12,6 +12,7 @@
<WizardProjectSelector
:is-sender="false"
:show-new-project="false"
:url-parse-error="urlParseError"
@next="selectProject"
@search-text-update="updateSearchText"
/>
@@ -36,10 +37,11 @@
:workspace-slug="selectedWorkspace?.slug"
:from-wizard="true"
@next="selectVersionAndAddModel"
@update:settings="handleUpdateSettings"
/>
</div>
</div>
<div v-if="urlParseError" class="p-2 text-xs text-danger">{{ urlParseError }}</div>
<div v-if="urlParseError" class="p-2 text-danger">{{ urlParseError }}</div>
</CommonDialog>
</template>
<script setup lang="ts">
@@ -56,6 +58,7 @@ import { ReceiverModelCard } from '~/lib/models/card/receiver'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useAddByUrl } from '~/lib/core/composables/addByUrl'
import { getSlugFromHostAppNameAndVersion } from '~/lib/common/helpers/hostAppSlug'
import type { CardSetting } from '~/lib/models/card/setting'
const { trackEvent } = useMixpanel()
@@ -82,6 +85,7 @@ const selectedAccountId = ref<string>(activeAccount.value?.accountInfo.id as str
const selectedWorkspace = ref<WorkspaceListWorkspaceItemFragment>()
const selectedProject = ref<ProjectListProjectItemFragment>()
const selectedModel = ref<ModelListModelItemFragment>()
const receieveSettings = ref<CardSetting[] | undefined>(undefined)
const { tryParseUrl, urlParsedData, urlParseError } = useAddByUrl()
const updateSearchText = (text: string | undefined) => {
@@ -130,6 +134,10 @@ const title = computed(() => {
return ''
})
const handleUpdateSettings = (settings: CardSetting[]) => {
receieveSettings.value = settings
}
// accountId, serverUrl, ModelListModelItemFragment, VersionListItemFragment
const selectVersionAndAddModel = async (
version: VersionListItemFragment,
@@ -172,6 +180,7 @@ const selectVersionAndAddModel = async (
)
const modelCard = new ReceiverModelCard()
modelCard.settings = receieveSettings.value
modelCard.accountId = selectedAccountId.value
modelCard.serverUrl = activeAccount.value.accountInfo.serverInfo.url
+3 -2
View File
@@ -1,11 +1,12 @@
<template>
<div class="space-y-4">
<FilterListSelect @update:filter="updateFilter" />
<SendSettings
<ModelSettings
v-if="hasSendSettings"
expandable
:default-settings="(store.sendSettings as unknown as CardSetting[])"
@update:settings="updateSettings"
></SendSettings>
></ModelSettings>
</div>
</template>
<script setup lang="ts">
+3 -2
View File
@@ -6,11 +6,12 @@
:title="`Settings`"
fullscreen="none"
>
<SendSettings
<ModelSettings
:expandable="false"
:default-settings="(store.sendSettings as unknown as CardSetting[])"
:settings="props.settings"
@update:settings="updateSettings"
></SendSettings>
></ModelSettings>
<div class="mt-4 flex justify-end items-center space-x-2">
<FormButton size="sm" color="outline" @click="showSettingsDialog = false">
Cancel
+2 -1
View File
@@ -11,6 +11,7 @@
<WizardProjectSelector
is-sender
disable-no-write-access-projects
:url-parse-error="urlParseError"
@next="selectProject"
@search-text-update="updateSearchText"
/>
@@ -37,7 +38,7 @@
<FormButton full-width @click="addModel">Publish</FormButton>
</div>
</div>
<div v-if="urlParseError" class="p-2 text-xs text-danger">
<div v-if="urlParseError" class="p-2 text-danger">
{{ urlParseError }}
</div>
</CommonDialog>
+96 -30
View File
@@ -13,22 +13,51 @@
full-width
color="foundation"
/>
<ModelCreateDialog
:project-id="project.id"
:workspace-id="workspaceId"
:workspace-slug="workspaceSlug"
@model:created="(result: ModelListModelItemFragment) => handleModelCreated(result)"
<div
v-tippy="
canCreateModelResult?.project.permissions.canCreateModel.authorized
? 'Create new model'
: canCreateModelResult?.project.permissions.canCreateModel.message
"
>
<template #activator="{ toggle }">
<button
v-tippy="'New model'"
class="p-1.5 bg-foundation hover:bg-primary-muted rounded text-foreground border"
@click="toggle()"
<FormButton
color="outline"
:disabled="
!canCreateModelResult?.project.permissions.canCreateModel.authorized
"
:class="`p-1.5 bg-foundation hover:bg-primary-muted rounded text-foreground border`"
@click="showNewModelDialog = true"
>
<PlusIcon class="w-4 -mx-2" />
</FormButton>
</div>
</div>
<div
v-if="
canCreateModelResult &&
!canCreateModelResult.project.permissions.canCreateModel.authorized
"
>
<CommonAlert title="Cannot create new models" color="info" hide-icon>
<template #description>
{{ canCreateModelResult.project.permissions.canCreateModel.message }}
<FormButton
v-if="workspaceSlug"
full-width
color="primary"
size="sm"
class="mt-2"
@click="
$openUrl(
`${account.accountInfo.serverInfo.url}/settings/workspaces/${workspaceSlug}/billing`
)
"
>
<PlusIcon class="w-4" />
</button>
Explore Plans
</FormButton>
</template>
</ModelCreateDialog>
</CommonAlert>
</div>
<div class="relative grid grid-cols-1 gap-2">
<CommonLoadingBar v-if="loading" loading />
@@ -37,6 +66,7 @@
v-for="model in models"
:key="model.id"
:model="model"
:token="token"
@click="handleModelSelect(model)"
/>
@@ -77,6 +107,20 @@
</template>
</CommonDialog>
<FormButton
v-if="
models?.length === 0 &&
!!searchText &&
canCreateModelResult?.project.permissions.canCreateModel?.authorized
"
full-width
color="outline"
:disabled="isCreatingModel"
@click="createNewModel(searchText)"
>
Create "{{ searchText }}"
</FormButton>
<FormButton
v-else
color="outline"
full-width
:disabled="hasReachedEnd"
@@ -91,7 +135,7 @@
title="Create new model"
fullscreen="none"
>
<form @submit="onSubmit">
<form @submit="createNewModel(newModelName as string)">
<FormTextInput
v-model="newModelName"
:rules="rules"
@@ -107,7 +151,9 @@
<FormButton size="sm" text @click="showNewModelDialog = false">
Cancel
</FormButton>
<FormButton size="sm" submit :disabled="isCreatingModel">Create</FormButton>
<FormButton size="sm" submit :disabled="isCreatingModel || !newModelName">
Create
</FormButton>
</div>
</form>
</CommonDialog>
@@ -122,10 +168,10 @@ import type {
} from '~/lib/common/generated/gql/graphql'
import { useModelNameValidationRules } from '~/lib/validation'
import {
canCreateModelInProjectQuery,
createModelMutation,
projectModelsQuery
} from '~/lib/graphql/mutationsAndQueries'
import { useForm } from 'vee-validate'
import type { DUIAccount } from '~/store/accounts'
import { useAccountStore } from '~/store/accounts'
import { useHostAppStore } from '~/store/hostApp'
@@ -152,13 +198,20 @@ const props = withDefaults(
const accountStore = useAccountStore()
const account = computed(
() =>
accountStore.accounts.find(
(acc) => acc.accountInfo.id === props.accountId
) as DUIAccount
)
const showNewModelDialog = ref(false)
const showSelectionHasProblemsDialog = ref(false)
const searchText = ref<string>()
const newModelName = ref<string>()
const newModelName = ref<string>(hostAppStore.documentInfo?.name ?? 'unnamed model')
watch(searchText, () => (newModelName.value = searchText.value))
watch(searchText, () => (newModelName.value = searchText.value as string))
let selectedModel: ModelListModelItemFragment | undefined = undefined
const existingModelProblem = ref(false)
@@ -187,12 +240,6 @@ const confirmModelSelection = () => {
}
const rules = useModelNameValidationRules()
const { handleSubmit } = useForm<{ name: string }>()
const onSubmit = handleSubmit(() => {
// TODO: Chat with Fabians
// This works, but if we use handleSubmit(args) > args.name -> it is undefined in Production on netlify, but works fine on local dev
void createNewModel(newModelName.value as string)
})
const handleModelCreated = (result: ModelListModelItemFragment) => {
refetch() // Sorts the list with newly created project otherwise it will put the project at the bottom.
@@ -200,21 +247,30 @@ const handleModelCreated = (result: ModelListModelItemFragment) => {
}
const isCreatingModel = ref(false)
const createNewModel = async (name: string) => {
if (!canCreateModelResult.value?.project.permissions.canCreateModel.authorized) {
hostAppStore.setNotification({
type: 1,
title: 'Failed to create model',
description:
canCreateModelResult.value?.project.permissions.canCreateModel.message
})
return
}
isCreatingModel.value = true
const account = accountStore.accounts.find(
(acc) => acc.accountInfo.id === props.accountId
) as DUIAccount
void trackEvent('DUI3 Action', { name: 'Model Create' }, account.accountInfo.id)
void trackEvent('DUI3 Action', { name: 'Model Create' }, account.value.accountInfo.id)
const { mutate } = provideApolloClient(account.client)(() =>
const { mutate } = provideApolloClient(account.value.client)(() =>
useMutation(createModelMutation)
)
const res = await mutate({ input: { projectId: props.project.id, name } })
if (res?.data?.modelMutations.create) {
refetch() // Sorts the list with newly created model otherwise it will put the model at the bottom.
emit('next', res?.data?.modelMutations.create)
// emit('next', res?.data?.modelMutations.create)
handleModelCreated(res?.data?.modelMutations.create)
} else {
let errorMessage = 'Undefined error'
if (res?.errors && res?.errors.length !== 0) {
@@ -230,6 +286,15 @@ const createNewModel = async (name: string) => {
isCreatingModel.value = false
}
const { result: canCreateModelResult } = useQuery(
canCreateModelInProjectQuery,
() => ({ projectId: props.project.id }),
() => ({
clientId: props.accountId,
fetchPolicy: 'network-only'
})
)
const {
result: projectModelsResult,
loading,
@@ -247,6 +312,7 @@ const {
() => ({ clientId: props.accountId, debounce: 500, fetchPolicy: 'cache-and-network' })
)
const token = computed(() => account.value.accountInfo.token)
const models = computed(() => projectModelsResult.value?.project.models.items)
const totalCount = computed(() => projectModelsResult.value?.project.models.totalCount)
const hasReachedEnd = ref(false)
+14 -65
View File
@@ -12,78 +12,27 @@
Move your projects to a workspace
</h1>
<p class="mb-2">
<span class="text-sm"></span>
<span class="text-xs">
We are making workspaces the default way to work in Speckle.
</span>
</p>
<p class="mb-1">
<span class="text-sm"></span>
<span class="text-xs">
Introducing
<FormButton
text
link
external
size="sm"
class="font-semibold"
@click="$openUrl(`https://www.speckle.systems/pricing`)"
>
new pricing
</FormButton>
including limits to the free plan.
Personal projects are being phased out. Move your projects to a
workspace to create new projects and models, invite new project
collaborators, and view comments and versions older than 7 days. By
January 1st 2026, all projects will be archived if not moved into a
workspace.
</span>
</p>
<FormButton
text
color="primary"
size="sm"
:class="showMore ? `mb-1` : ``"
:icon-right="showMore ? ChevronUpIcon : ChevronDownIcon"
@click="showMore = !showMore"
class="mb-2"
@click="
$openUrl(
`${accountStore.activeAccount.accountInfo.serverInfo.url}/projects`
)
"
>
{{ showMore ? 'Show less' : 'Show timeline' }}
Move projects
</FormButton>
<div v-show="showMore">
<hr />
<h3 class="font-medium text-warning-darker my-1">By June 1st 2025</h3>
<p class="text-xs mb-1">Move your projects to a workspace to:</p>
<ul class="list-disc list-inside pl-2 mb-2">
<li>
<span class="text-xs font-medium">
Create new projects and models
</span>
<span class="text-xs">
(will be disabled for personal projects; existing projects and
models stay editable)
</span>
</li>
<li>
<span class="text-xs font-medium">
Invite new project collaborators
</span>
<span class="text-xs">
(new invites will be unavailable for personal projects)
</span>
</li>
<li>
<span class="text-xs font-medium">
Preserve version and comment history
</span>
<span class="text-xs">
(history is reduced to 7 days for personal projects)
</span>
</li>
</ul>
<h3 class="font-medium text-warning-darker">By Janury 1st 2026</h3>
<span class="text-xs mb-1">
All projects will be archived if not moved into a workspace. Don't
worry, we'll give you plenty of reminders before then.
</span>
</div>
</div>
</div>
</div>
@@ -92,8 +41,8 @@
</template>
<script setup lang="ts">
import { ChevronUpIcon, ChevronDownIcon } from '@heroicons/vue/20/solid'
import { useAccountStore } from '~/store/accounts'
const accountStore = useAccountStore()
const { $openUrl } = useNuxtApp()
const showMore = ref(false)
</script>
+265 -42
View File
@@ -1,10 +1,7 @@
<template>
<div class="space-y-2">
<div class="space-y-2 relative">
<div
v-if="workspacesEnabled && workspaces"
class="flex items-center space-x-2 bg-foundation -mx-3 -mt-2 px-3 py-2 shadow-sm border-b"
>
<div v-if="workspacesEnabled && workspaces" class="flex items-center space-x-2">
<div class="flex-grow min-w-0">
<!-- NO WORKSPACE YET -->
<div v-if="workspaces.length === 0">
@@ -39,12 +36,24 @@
<div class="min-w-0 truncate flex-grow text-left">
<span>{{ selectedWorkspace.name }}</span>
</div>
<button
v-if="selectedWorkspace.slug"
v-tippy="'Open workspace in browser'"
class="transition mr-1 opacity-70 hover:opacity-100"
@click.stop="
$openUrl(
`${accountStore.activeAccount.accountInfo.serverInfo.url}/workspaces/${selectedWorkspace.slug}`
)
"
>
<ArrowTopRightOnSquareIcon class="w-3.5" />
</button>
<ChevronDownIcon class="h-3 w-3 shrink-0" />
</button>
</template>
</WorkspaceMenu>
</div>
<div class="px-0.5 shrink-0">
<div class="shrink-0 pt-1 px-1">
<AccountsMenu
:current-selected-account-id="accountId"
@select="(e) => selectAccount(e)"
@@ -63,36 +72,58 @@
color="foundation"
/>
<div class="flex justify-between items-center space-x-2">
<ProjectCreateWorkspaceDialog
v-if="selectedWorkspace && selectedWorkspace.id !== 'personalProject'"
:workspace="selectedWorkspace"
@project:created="(result : ProjectListProjectItemFragment) => handleProjectCreated(result)"
<div
v-tippy="
canCreateProject
? 'Create new project'
: canCreateProjectPermissionCheck?.message
"
>
<template #activator="{ toggle }">
<button
v-tippy="'New project in workspace'"
class="p-1.5 bg-foundation hover:bg-primary-muted rounded text-foreground border"
@click="toggle()"
>
<PlusIcon class="w-4" />
</button>
</template>
</ProjectCreateWorkspaceDialog>
<!-- TODO: once we deprecate personal projects, else block is bye bye -->
<ProjectCreatePersonalDialog
v-else
@project:created="(result : ProjectListProjectItemFragment) => handleProjectCreated(result)"
<FormButton
color="outline"
:disabled="!canCreateProject"
:class="`p-1.5 bg-foundation hover:bg-primary-muted rounded text-foreground border`"
@click="showProjectCreateDialog = true"
>
<PlusIcon class="w-4 -mx-2" />
</FormButton>
</div>
<CommonDialog
v-model:open="showProjectCreateDialog"
:title="`Create new project`"
fullscreen="none"
>
<template #activator="{ toggle }">
<button
v-tippy="'New personal project'"
class="p-1.5 bg-foundation hover:bg-primary-muted rounded text-foreground border"
@click="toggle()"
>
<PlusIcon class="w-4" />
</button>
</template>
</ProjectCreatePersonalDialog>
<form @submit="createProject(newProjectName as string)">
<div class="text-body-2xs mb-2 ml-1">Project name</div>
<FormTextInput
v-model="newProjectName"
class="text-xs"
placeholder="A Beautiful Home, A Small Bridge..."
autocomplete="off"
name="name"
label="Project name"
color="foundation"
:show-clear="!!newProjectName"
:rules="[
ValidationHelpers.isRequired,
ValidationHelpers.isStringOfLength({ minLength: 3 })
]"
full-width
/>
<div class="mt-4 flex justify-end items-center space-x-2 w-full">
<FormButton size="sm" text @click="showProjectCreateDialog = false">
Cancel
</FormButton>
<FormButton
size="sm"
submit
:disabled="isCreatingProject || !newProjectName"
>
Create
</FormButton>
</div>
</form>
</CommonDialog>
<div v-if="!workspacesEnabled || !workspaces" class="mt-1">
<AccountsMenu
:current-selected-account-id="accountId"
@@ -101,19 +132,34 @@
</div>
</div>
</div>
<div v-if="isPersonalProjectsAsWorkspace">
<!-- <CommonAlert size="xs" :color="'warning'">
<div
v-if="
canCreateProjectPermissionCheck &&
!canCreateProjectPermissionCheck.authorized
"
>
<CommonAlert color="info" hide-icon>
<template #description>
You are listing legacy personal projects which will be deprecated end of
2025. We suggest you to move your personal projects into a workspace
before then.
{{ canCreateProjectPermissionCheck.message }}
<FormButton
v-if="showUpgradeButton"
full-width
class="mt-2"
color="primary"
size="sm"
@click="upgradeButtonAction()"
>
Upgrade now
</FormButton>
</template>
</CommonAlert> -->
<WizardPersonalProjectsWarning />
</CommonAlert>
</div>
<CommonLoadingBar v-if="loading" loading />
<WizardPersonalProjectsWarning v-if="isPersonalProjectsAsWorkspace" />
<CommonLoadingBar v-if="loading || isCreatingProject" loading />
</div>
<div class="grid grid-cols-1 gap-2 relative z-0">
<div v-if="!urlParseError" class="grid grid-cols-1 gap-2 relative z-0">
<WizardListProjectCard
v-for="project in projects"
:key="project.id"
@@ -121,7 +167,27 @@
:is-sender="isSender"
@click="handleProjectCardClick(project)"
/>
<p v-if="projects?.length === 0 && !!searchText" class="text-sm">
No projects found
</p>
<FormButton
v-if="
projects?.length === 0 &&
!!searchText &&
canCreateProjectPermissionCheck?.authorized
"
full-width
color="outline"
:disabled="isCreatingProject"
class="block truncate overflow-hidden"
@click="createProject(searchText)"
>
Create "{{
searchText.length > 10 ? searchText.substring(0, 10) + '...' : searchText
}}"
</FormButton>
<FormButton
v-else
full-width
:disabled="hasReachedEnd"
color="outline"
@@ -141,19 +207,25 @@ import type { DUIAccount } from '~/store/accounts'
import { useAccountStore } from '~/store/accounts'
import {
activeWorkspaceQuery,
canCreatePersonalProjectQuery,
createProjectInWorkspaceMutation,
createProjectMutation,
projectsListQuery,
serverInfoQuery,
setActiveWorkspaceMutation,
workspacesListQuery
} from '~/lib/graphql/mutationsAndQueries'
import { useMutation, provideApolloClient, useQuery } from '@vue/apollo-composable'
import { ValidationHelpers } from '@speckle/ui-components'
import type {
ProjectListProjectItemFragment,
WorkspaceListWorkspaceItemFragment
} from '~/lib/common/generated/gql/graphql'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
import { useConfigStore } from '~/store/config'
import { useHostAppStore } from '~/store/hostApp'
const hostAppStore = useHostAppStore()
const { trackEvent } = useMixpanel()
const { $openUrl } = useNuxtApp()
@@ -175,6 +247,7 @@ const props = withDefaults(
* For the send wizard - not allowing selecting projects we can't write to.
*/
disableNoWriteAccessProjects?: boolean
urlParseError?: string
}>(),
{
showNewProject: true,
@@ -388,6 +461,156 @@ watch(projectsResult, (newVal) => {
}
})
const { result: canCreatePersonalProjectResult } = useQuery(
canCreatePersonalProjectQuery,
{},
() => ({
clientId: accountId.value
})
)
const canCreateProject = computed(() => {
// If a workspace is selected, return that permission check
if (selectedWorkspace.value && selectedWorkspace.value.permissions) {
return selectedWorkspace.value.permissions.canCreateProject.authorized //as boolean
}
// Otherwise, check for personal projects
if (canCreatePersonalProjectResult) {
return canCreatePersonalProjectResult.value?.activeUser?.permissions
.canCreatePersonalProject.authorized
}
// To be always safe, default to false
return false
})
const canCreateProjectPermissionCheck = computed(() => {
if (selectedWorkspace.value && selectedWorkspace.value.permissions) {
return selectedWorkspace.value.permissions.canCreateProject
}
if (canCreatePersonalProjectResult) {
return canCreatePersonalProjectResult.value?.activeUser?.permissions
.canCreatePersonalProject
}
return null
})
const upgradeButtonAction = () => {
if (!canCreateProjectPermissionCheck.value) return
if (canCreateProjectPermissionCheck.value.code === 'WorkspaceNoEditorSeat') {
// open url to workspace/settings/users
$openUrl(
`${account.value.accountInfo.serverInfo.url}/settings/workspaces/${selectedWorkspace.value?.slug}/members`
)
return
}
if (canCreateProjectPermissionCheck.value.code === 'WorkspaceLimitsReached') {
// open url to workspace/billing
$openUrl(
`${account.value.accountInfo.serverInfo.url}/settings/workspaces/${selectedWorkspace.value?.slug}/billing`
)
return
}
}
const showUpgradeButton = computed(() => {
if (!canCreateProjectPermissionCheck.value) return false
if (
canCreateProjectPermissionCheck.value.code === 'WorkspaceNoEditorSeat' ||
canCreateProjectPermissionCheck.value.code === 'WorkspaceLimitsReached'
) {
return true
}
return false
})
const isCreatingProject = ref(false)
const showProjectCreateDialog = ref(false)
const createProject = (name: string) => {
if (
canCreateProjectPermissionCheck.value &&
!canCreateProjectPermissionCheck.value.authorized
) {
hostAppStore.setNotification({
type: 1,
title: 'Failed to create project',
description: canCreateProjectPermissionCheck.value.message as string
})
return
}
if (isPersonalProjectsAsWorkspace.value || !selectedWorkspace.value) {
return void createNewPersonalProject(name)
} else {
return void createNewWorkspaceProject(name)
}
}
const account = computed(() => {
return accountStore.accounts.find(
(acc) => acc.accountInfo.id === accountId.value
) as DUIAccount
})
const createNewWorkspaceProject = async (name: string) => {
isCreatingProject.value = true
void trackEvent(
'DUI3 Action',
{ name: 'Project Create', workspace: true },
accountId.value
)
const { mutate, onError } = provideApolloClient(account.value.client)(() =>
useMutation(createProjectInWorkspaceMutation)
)
onError((err) => {
hostAppStore.setNotification({
type: 1,
title: 'Failed to create project',
description: err.cause?.message ?? err.message ?? 'Unknown error'
})
})
const res = await mutate({
input: { name, workspaceId: selectedWorkspace.value?.id as string }
})
if (res?.data?.workspaceMutations.projects.create) {
handleProjectCreated(res?.data?.workspaceMutations.projects.create)
}
isCreatingProject.value = false
}
const createNewPersonalProject = async (name: string) => {
isCreatingProject.value = true
void trackEvent(
'DUI3 Action',
{ name: 'Project Create', workspace: false },
account.value.accountInfo.id
)
const { mutate, onError } = provideApolloClient(account.value.client)(() =>
useMutation(createProjectMutation)
)
onError((err) => {
hostAppStore.setNotification({
type: 1,
title: 'Failed to create project',
description: err.cause?.message ?? err.message ?? 'Unknown error'
})
})
const res = await mutate({ input: { name } })
if (res?.data?.projectMutations.create) {
return handleProjectCreated(res?.data?.projectMutations.create)
}
isCreatingProject.value = false
}
const loadMore = () => {
fetchMore({
variables: { cursor: projectsResult.value?.activeUser?.projects.cursor },
+25 -1
View File
@@ -13,6 +13,17 @@
Upgrade
</FormButton>
</div>
<div v-if="hasReceiveSettings">
<ModelSettings
class="mb-2"
expandable
:settings="settings"
:default-settings="(store.receiveSettings as unknown as CardSetting[])"
@update:settings="updateSettings"
></ModelSettings>
<hr />
</div>
<div v-if="latestVersion" class="grid grid-cols-2 gap-3 max-[275px]:grid-cols-1">
<WizardListVersionCard
v-for="(version, index) in versions"
@@ -44,27 +55,40 @@ import { useQuery } from '@vue/apollo-composable'
import { modelVersionsQuery } from '~/lib/graphql/mutationsAndQueries'
import type { VersionListItemFragment } from '~/lib/common/generated/gql/graphql'
import { useAccountStore } from '~/store/accounts'
import type { CardSetting } from '~/lib/models/card/setting'
import { useHostAppStore } from '~/store/hostApp'
defineEmits<{
const emit = defineEmits<{
(
e: 'next',
version: VersionListItemFragment,
latestVersion: VersionListItemFragment
): void
(e: 'update:settings', settings: CardSetting[]): void
}>()
const props = defineProps<{
accountId: string
projectId: string
modelId: string
settings?: CardSetting[]
selectedVersionId?: string
workspaceSlug?: string
fromWizard?: boolean
}>()
const store = useHostAppStore()
const accountStore = useAccountStore()
const serverUrl = computed(() => accountStore.activeAccount.accountInfo.serverInfo.url)
const hasReceiveSettings = computed(
() => store.receiveSettings && store.receiveSettings.length > 0
)
const updateSettings = (settings: CardSetting[]) => {
emit('update:settings', settings)
}
const {
result: modelVersionResults,
loading,
+21 -3
View File
@@ -4,9 +4,15 @@
>
<div class="flex items-center space-x-2 max-[275px]:space-x-0">
<div class="max-[275px]:hidden">
<div v-if="model.previewUrl" class="h-12 w-12">
<div
v-if="model.versions.totalCount === 0"
class="h-12 w-12 bg-blue-500/10 rounded flex items-center justify-center"
>
<CubeTransparentIcon class="w-5 h-5 text-foreground-2" />
</div>
<div v-else-if="previewUrl" class="h-12 w-12">
<img
:src="model.previewUrl"
:src="previewUrl"
alt="preview image for model"
class="h-12 w-12 object-cover"
/>
@@ -15,7 +21,7 @@
v-else
class="h-12 w-12 bg-blue-500/10 rounded flex items-center justify-center"
>
<CubeTransparentIcon class="w-5 h-5 text-foreground-2" />
<CommonLoadingIcon />
</div>
</div>
<div class="min-w-0 w-full">
@@ -52,9 +58,16 @@ import { ClockIcon } from '@heroicons/vue/24/outline'
import type { SourceAppName } from '@speckle/shared'
import { SourceApps } from '@speckle/shared'
import type { ModelListModelItemFragment } from '~/lib/common/generated/gql/graphql'
import { computedAsync } from '@vueuse/core'
import { usePreviewUrl } from '~/lib/core/composables/previewUrl'
const props = defineProps<{
model: ModelListModelItemFragment
/**
* Token to retrieve preview url
* @note by convention we pass around `accountId` but it doesn't make sense to get token for every model card. more efficient with this way.
*/
token: string
}>()
const folderPath = computed(() => {
@@ -68,6 +81,11 @@ const updatedAgo = computed(() => {
return dayjs(props.model.updatedAt).from(dayjs())
})
const previewUrl = computedAsync(async () => {
if (props.model.previewUrl === null) return
return await usePreviewUrl(props.token, props.model.previewUrl)
})
const sourceApp = computed(() => {
if (props.model.versions.items.length === 0) return
const version = props.model.versions.items[0]
+26 -1
View File
@@ -24,7 +24,15 @@
</div>
</div>
<div v-else class="flex items-center justify-center w-full h-24">
<img :src="version.previewUrl" alt="version preview" />
<div v-if="previewUrl">
<img :src="previewUrl" alt="preview image for version" />
</div>
<div
v-else
class="h-12 w-12 bg-blue-500/10 rounded flex items-center justify-center"
>
<CommonLoadingIcon />
</div>
</div>
<div class="p-1.5 border-t dark:border-gray-700">
<div class="flex space-x-2 items-center min-w-0">
@@ -100,9 +108,14 @@ import dayjs from 'dayjs'
import type { SourceAppName } from '@speckle/shared'
import { SourceApps } from '@speckle/shared'
import type { VersionListItemFragment } from '~/lib/common/generated/gql/graphql'
import { useAccountStore, type DUIAccount } from '~/store/accounts'
import { computedAsync } from '@vueuse/core'
import { usePreviewUrl } from '~/lib/core/composables/previewUrl'
// import { objectQuery } from '~/lib/graphql/mutationsAndQueries'
// import { useQuery } from '@vue/apollo-composable'
const accountStore = useAccountStore()
const props = defineProps<{
version: VersionListItemFragment
index: number
@@ -120,6 +133,18 @@ const createdAgo = computed(() => {
const isLimited = computed(() => props.version.referencedObject === null)
const token = computed(() => {
const account = accountStore.accounts.find(
(acc) => acc.accountInfo.id === props.accountId
) as DUIAccount
return account.accountInfo.token
})
const previewUrl = computedAsync(async () => {
if (props.version.previewUrl === null) return
return await usePreviewUrl(token.value, props.version.previewUrl)
})
// NOTE!!!: This logic somehow caused regression on versionList fetchMore, but we do not know exactly why yet.
// const { result: objectQueryResult } = useQuery(
// objectQuery,
+16
View File
@@ -21,11 +21,27 @@
<span>{{ workspace.name }}</span>
</div>
</div>
<button
v-if="workspace.slug"
v-tippy="'Open workspace in browser'"
class="hidden transition mr-1 opacity-70 group-hover:block"
@click.stop="
$openUrl(
`${accountStore.activeAccount.accountInfo.serverInfo.url}/workspaces/${workspace.slug}`
)
"
>
<ArrowTopRightOnSquareIcon class="w-3.5" />
</button>
</div>
</button>
</template>
<script setup lang="ts">
import type { WorkspaceListWorkspaceItemFragment } from '~/lib/common/generated/gql/graphql'
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/outline'
import { useAccountStore } from '~/store/accounts'
const accountStore = useAccountStore()
defineProps<{
workspace: WorkspaceListWorkspaceItemFragment
+2 -2
View File
@@ -14,7 +14,7 @@
@select="
$emit('workspace:selected', workspace), (showWorkspaceSelectorDialog = false)
"
></WorkspaceListItem>
/>
</CommonDialog>
</div>
</template>
@@ -34,7 +34,7 @@ defineEmits<{
}>()
const workspacesWithPersonalProjects = computed(() => [
...props.workspaces,
...props.workspaces.filter((w) => w.creationState?.completed !== false),
{
id: 'personalProject',
name: 'Personal Projects'
+15 -2
View File
@@ -8,7 +8,7 @@
v-if="hasNoModelCards"
class="px-3 text-body-3xs text-foreground-2 justify-center bg-red-200/1 py-2 flex items-center w-full space-x-2"
>
<span>Version {{ hostApp.connectorVersion }}</span>
<span>Version {{ hostApp.connectorVersion || 'dev' }}</span>
<FormButton
size="sm"
text
@@ -19,6 +19,17 @@
>
Toggle theme
</FormButton>
<FormButton
v-if="hostApp.hostAppName?.toLowerCase() === 'revit'"
size="sm"
text
color="subtle"
:icon-right="WrenchScrewdriverIcon"
hide-text
@click="app.$showDevTools()"
>
Open dev tools
</FormButton>
</div>
</div>
</template>
@@ -27,7 +38,9 @@
import { storeToRefs } from 'pinia'
import { useHostAppStore } from '~/store/hostApp'
import { useConfigStore } from '~/store/config'
import { MoonIcon, SunIcon } from '@heroicons/vue/24/outline'
import { MoonIcon, SunIcon, WrenchScrewdriverIcon } from '@heroicons/vue/24/outline'
const app = useNuxtApp()
const uiConfigStore = useConfigStore()
const { isDarkTheme } = storeToRefs(uiConfigStore)
@@ -31,3 +31,42 @@ export type Account = {
}
export interface IAccountBindingEvents extends IBindingSharedEvents {}
export class MockedAccountBinding implements IAccountBinding {
public async getAccounts() {
const config = useRuntimeConfig()
return (await [
{
id: 'whatever',
isDefault: true,
token: config.public.speckleToken,
serverInfo: {
name: 'test',
url: config.public.speckleUrl,
frontend2: true
},
userInfo: {
id: 'whatever',
avatar: 'whatever',
email: ''
}
}
]) as Account[]
}
public async removeAccount(accountId: string) {
return await console.log('no way dude', accountId)
}
public async showDevTools() {
await console.log('No way dude')
}
public async openUrl(url: string) {
await window.open(url)
}
public on() {
return
}
}
@@ -57,3 +57,65 @@ export type ToastAction = {
url: string
name: string
}
export class MockedBaseBinding implements IBasicConnectorBinding {
public async getSourceApplicationName() {
return await 'headless'
}
public async getSourceApplicationVersion() {
return await 'dev'
}
public async getConnectorVersion() {
return await 'dev'
}
public async getDocumentInfo() {
return (await {
id: 'whatever',
name: 'test',
location: 'whocares'
}) as DocumentInfo
}
public async getDocumentState() {
return (await { models: [] }) as DocumentModelStore
}
public async addModel(_model: IModelCard) {
await console.log('no way dude')
}
public async removeModel(_model: IModelCard) {
await console.log('no way dude')
}
public async removeModels(_models: IModelCard[]) {
await console.log('no way dude')
}
public async updateModel(_model: IModelCard) {
await console.log('no way dude')
}
public async highlightModel(_modelCardId: string) {
await console.log('no way dude')
}
public async highlightObjects(_objectIds: string[]) {
await console.log('no way dude')
}
public async showDevTools() {
await console.log('No way dude')
}
public async openUrl(url: string) {
await window.open(url)
}
public on() {
return
}
}
+45 -2
View File
@@ -1,4 +1,3 @@
import { BaseBridge } from '~/lib/bridge/base'
import type {
IBinding,
IBindingSharedEvents
@@ -38,4 +37,48 @@ export type WorkspacesConfig = {
}
// Useless, but will do for now :)
export class MockedConfigBinding extends BaseBridge {}
export class MockedConfigBinding implements IConfigBinding {
public async getIsDevMode() {
return await true
}
public async getConfig() {
return await { darkTheme: false }
}
public async updateConfig() {
return await console.log('')
}
public async setUserSelectedAccountId(accountId: string) {
return await console.log(accountId)
}
public async setUserSelectedWorkspaceId(workspaceId: string) {
return await console.log(workspaceId)
}
public async getAccountsConfig() {
return (await { userSelectedAccountId: 'whatever' }) as AccountsConfig
}
public async getWorkspacesConfig() {
return (await { userSelectedWorkspaceId: 'whatever' }) as WorkspacesConfig
}
public async getUserSelectedAccountId() {
return (await { userSelectedAccountId: 'whatever' }) as AccountsConfig
}
public async showDevTools() {
await console.log('No way dude')
}
public async openUrl(url: string) {
await window.open(url)
}
public on() {
return
}
}
@@ -24,3 +24,29 @@ export interface IReceiveBindingEvents
conversionResults: ConversionResult[]
}) => void
}
export class MockedReceiveBinding implements IReceiveBinding {
public async getReceiveSettings() {
return await []
}
public async receive(_modelCardId: string) {
return await console.log('no way dude')
}
public async cancelReceive(_modelCardId: string) {
return await console.log('no way dude')
}
public async showDevTools() {
await console.log('No way dude')
}
public async openUrl(url: string) {
await window.open(url)
}
public on() {
return
}
}
@@ -0,0 +1,158 @@
import type {
IBinding,
IBindingSharedEvents
} from '~/lib/bindings/definitions/IBinding'
export const IRevitMapperBindingKey = 'revitMapperBinding'
export interface IRevitMapperBinding extends IBinding<IMapperBindingEvents> {
// Gets list of defined layers in doc
getAvailableLayers: () => Promise<LayerOption[]>
// Object methods
assignObjectsToCategory: (objectIds: string[], categoryValue: string) => Promise<void>
clearObjectsCategoryAssignment: (objectIds: string[]) => Promise<void>
clearAllObjectsCategoryAssignments: () => Promise<void>
getCurrentObjectsMappings: () => Promise<CategoryMapping[]>
getCategoryMappingsForObjects: (objectIds: string[]) => Promise<string[]>
// Layer methods
assignLayerToCategory: (layerIds: string[], categoryValue: string) => Promise<void>
clearLayerCategoryAssignment: (layerIds: string[]) => Promise<void>
clearAllLayerCategoryAssignments: () => Promise<void>
getCurrentLayerMappings: () => Promise<LayerCategoryMapping[]>
getEffectiveObjectsForLayerMapping: (
layerIds: string[],
categoryValue: string
) => Promise<string[]>
getCategoryMappingsForLayers: (layerIds: string[]) => Promise<string[]>
}
export interface IMapperBindingEvents extends IBindingSharedEvents {
mappingsChanged: (mappings: CategoryMapping[]) => void
layersChanged: (layers: LayerOption[]) => void
}
export interface Category {
value: string
label: string
}
export interface CategoryMapping {
categoryValue: string
categoryLabel: string
objectIds: string[]
objectCount: number
}
export interface LayerCategoryMapping {
categoryValue: string
categoryLabel: string
layerIds: string[]
layerNames: string[]
layerCount: number
}
export interface LayerOption {
id: string
name: string
}
// Mock implementation for dev/testing
export class MockedMapperBinding implements IRevitMapperBinding {
private mockMappings: CategoryMapping[] = []
public assignObjectsToCategory(
objectIds: string[],
categoryValue: string
): Promise<void> {
console.log('Mock: Assigning objects to category', { objectIds, categoryValue })
return Promise.resolve()
}
public getAvailableLayers(): Promise<LayerOption[]> {
return Promise.resolve([
{ id: 'layer1', name: 'Ground Floor' },
{ id: 'layer2', name: 'Ground Floor/Walls' },
{ id: 'layer3', name: 'Ground Floor/Walls/Interior' },
{ id: 'layer4', name: 'Second Floor' }
])
}
public clearObjectsCategoryAssignment(objectIds: string[]): Promise<void> {
console.log('Mock: Clearing category assignment', { objectIds })
return Promise.resolve()
}
public clearAllObjectsCategoryAssignments(): Promise<void> {
console.log('Mock: Clearing all assignments')
this.mockMappings = []
return Promise.resolve()
}
public getCurrentObjectsMappings(): Promise<CategoryMapping[]> {
return Promise.resolve(this.mockMappings)
}
public assignLayerToCategory(
layerIds: string[],
categoryValue: string
): Promise<void> {
console.log('Mock: Assigning layers to category', { layerIds, categoryValue })
return Promise.resolve()
}
public clearLayerCategoryAssignment(layerIds: string[]): Promise<void> {
console.log('Mock: Clearing layer category assignment', { layerIds })
return Promise.resolve()
}
public clearAllLayerCategoryAssignments(): Promise<void> {
console.log('Mock: Clearing all layer assignments')
return Promise.resolve()
}
public getCurrentLayerMappings(): Promise<LayerCategoryMapping[]> {
return Promise.resolve([])
}
public getEffectiveObjectsForLayerMapping(
layerIds: string[],
categoryValue: string
): Promise<string[]> {
console.log('Mock: Getting effective objects for layer mapping', {
layerIds,
categoryValue
})
return Promise.resolve(['obj1', 'obj2', 'obj3'])
}
public getCategoryMappingsForObjects(objectIds: string[]): Promise<string[]> {
console.log('Mock: Getting category mappings for objects', { objectIds })
// Mock returning some categories for testing
return Promise.resolve(
objectIds.length > 1 ? ['OST_Walls', 'OST_Doors'] : ['OST_Walls']
)
}
public getCategoryMappingsForLayers(layerIds: string[]): Promise<string[]> {
console.log('Mock: Getting category mappings for layers', { layerIds })
return Promise.resolve(
layerIds.length > 1 ? ['OST_Floors', 'OST_Ceilings'] : ['OST_Floors']
)
}
public showDevTools(): Promise<void> {
console.log('Braaaaa, no way!')
return Promise.resolve()
}
public openUrl(url: string): Promise<void> {
window.open(url)
return Promise.resolve()
}
public on() {
// Mock event handler
}
}
@@ -17,3 +17,24 @@ export type SelectionInfo = {
summary?: string
selectedObjectIds: string[]
}
export class MockedSelectionBinding implements ISelectionBinding {
public async getSelection() {
return (await {
summary: '2 objects selected over mock binding',
selectedObjectIds: ['1', '2', '3']
}) as SelectionInfo
}
public async showDevTools() {
await console.log('No way dude')
}
public async openUrl(url: string) {
await window.open(url)
}
public on() {
return
}
}
+30
View File
@@ -38,3 +38,33 @@ export interface ISendBindingEvents
triggerCancel: (modelCardId: string) => void
triggerCreateVersion: (args: CreateVersionArgs) => void
}
export class MockedSendBinding implements ISendBinding {
public async getSendFilters() {
return await []
}
public async getSendSettings() {
return await []
}
public async send(_modelCardId: string) {
return await console.log('no way dude')
}
public async cancelSend(_modelCardId: string) {
return await console.log('no way dude')
}
public async showDevTools() {
await console.log('No way dude')
}
public async openUrl(url: string) {
await window.open(url)
}
public on() {
return
}
}
+17 -5
View File
@@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/require-await */
import { BaseBridge } from '~~/lib/bridge/base'
import type {
IBinding,
IBindingSharedEvents
@@ -38,9 +36,11 @@ export type ComplexType = {
count: number
}
export class MockedTestBinding extends BaseBridge {
export class MockedTestBinding implements ITestBinding {
public async sayHi(name: string, count: number, sayHelloNotHi: boolean) {
return `Hello from mocked bindings. Args: name = ${name}, count = ${count}, sayHelloNotHi = ${sayHelloNotHi.toString()}.`
return [
`Hello from mocked bindings. Args: name = ${name}, count = ${count}, sayHelloNotHi = ${sayHelloNotHi.toString()}.`
]
}
public async goAway() {
@@ -56,6 +56,18 @@ export class MockedTestBinding extends BaseBridge {
}
public async triggerEvent(eventName: string) {
return eventName
return console.log(eventName)
}
public async showDevTools() {
await console.log('No way dude')
}
public async openUrl(url: string) {
await window.open(url)
}
public on() {
return
}
}
+15 -3
View File
@@ -16,18 +16,20 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-
type Documents = {
"\n mutation SetActiveWorkspaceMutation($slug: String) {\n activeUserMutations {\n setActiveWorkspace(slug: $slug)\n }\n }\n": typeof types.SetActiveWorkspaceMutationDocument,
"\n mutation VersionMutations($input: CreateVersionInput!) {\n versionMutations {\n create(input: $input) {\n id\n }\n }\n }\n": typeof types.VersionMutationsDocument,
"\n mutation Update($input: UpdateVersionInput!) {\n versionMutations {\n update(input: $input) {\n id\n }\n }\n }\n": typeof types.UpdateDocument,
"\n mutation MarkReceivedVersion($input: MarkReceivedVersionInput!) {\n versionMutations {\n markReceived(input: $input)\n }\n }\n": typeof types.MarkReceivedVersionDocument,
"\n mutation CreateModel($input: CreateModelInput!) {\n modelMutations {\n create(input: $input) {\n ...ModelListModelItem\n }\n }\n }\n": typeof types.CreateModelDocument,
"\n mutation CreateProject($input: ProjectCreateInput) {\n projectMutations {\n create(input: $input) {\n ...ProjectListProjectItem\n }\n }\n }\n": typeof types.CreateProjectDocument,
"\n mutation CreateProjectInWorkspace($input: WorkspaceProjectCreateInput!) {\n workspaceMutations {\n projects {\n create(input: $input) {\n ...ProjectListProjectItem\n }\n }\n }\n }\n": typeof types.CreateProjectInWorkspaceDocument,
"\n mutation StreamAccessRequestCreate($input: String!) {\n streamAccessRequestCreate(streamId: $input) {\n id\n }\n }\n": typeof types.StreamAccessRequestCreateDocument,
"\n fragment WorkspaceListWorkspaceItem on Workspace {\n id\n slug\n name\n description\n createdAt\n updatedAt\n logo\n role\n readOnly\n }\n": typeof types.WorkspaceListWorkspaceItemFragmentDoc,
"\n fragment WorkspaceListWorkspaceItem on Workspace {\n id\n slug\n name\n description\n createdAt\n updatedAt\n logo\n role\n readOnly\n creationState {\n completed\n }\n permissions {\n canCreateProject {\n authorized\n code\n message\n }\n }\n }\n": typeof types.WorkspaceListWorkspaceItemFragmentDoc,
"\n fragment AutomateFunctionItem on AutomateFunction {\n name\n isFeatured\n id\n creator {\n name\n }\n releases {\n items {\n inputSchema\n }\n }\n }\n": typeof types.AutomateFunctionItemFragmentDoc,
"\n mutation CreateAutomation($projectId: ID!, $input: ProjectAutomationCreateInput!) {\n projectMutations {\n automationMutations(projectId: $projectId) {\n create(input: $input) {\n id\n name\n }\n }\n }\n }\n": typeof types.CreateAutomationDocument,
"\n fragment AutomateFunctionRunItem on AutomateFunctionRun {\n id\n status\n statusMessage\n results\n contextView\n function {\n id\n name\n logo\n }\n }\n": typeof types.AutomateFunctionRunItemFragmentDoc,
"\n fragment AutomationRunItem on AutomateRun {\n id\n status\n automation {\n id\n name\n }\n functionRuns {\n ...AutomateFunctionRunItem\n }\n }\n": typeof types.AutomationRunItemFragmentDoc,
"\n query AutomationStatus($projectId: String!, $modelId: String!) {\n project(id: $projectId) {\n model(id: $modelId) {\n automationsStatus {\n id\n status\n automationRuns {\n ...AutomationRunItem\n }\n }\n }\n }\n }\n": typeof types.AutomationStatusDocument,
"\n query WorkspaceListQuery(\n $limit: Int!\n $filter: UserWorkspacesFilter\n $cursor: String\n ) {\n activeUser {\n id\n workspaces(limit: $limit, filter: $filter, cursor: $cursor) {\n totalCount\n cursor\n items {\n ...WorkspaceListWorkspaceItem\n }\n }\n }\n }\n": typeof types.WorkspaceListQueryDocument,
"\n query ActiveUser {\n activeUser {\n role\n id\n name\n }\n }\n": typeof types.ActiveUserDocument,
"\n query CanCreatePersonalProject {\n activeUser {\n permissions {\n canCreatePersonalProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": typeof types.CanCreatePersonalProjectDocument,
"\n query CanCreateProjectInWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n permissions {\n canCreateProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": typeof types.CanCreateProjectInWorkspaceDocument,
"\n query CanCreateModelInProject($projectId: String!) {\n project(id: $projectId) {\n permissions {\n canCreateModel {\n authorized\n code\n message\n }\n }\n }\n }\n": typeof types.CanCreateModelInProjectDocument,
@@ -56,18 +58,20 @@ type Documents = {
const documents: Documents = {
"\n mutation SetActiveWorkspaceMutation($slug: String) {\n activeUserMutations {\n setActiveWorkspace(slug: $slug)\n }\n }\n": types.SetActiveWorkspaceMutationDocument,
"\n mutation VersionMutations($input: CreateVersionInput!) {\n versionMutations {\n create(input: $input) {\n id\n }\n }\n }\n": types.VersionMutationsDocument,
"\n mutation Update($input: UpdateVersionInput!) {\n versionMutations {\n update(input: $input) {\n id\n }\n }\n }\n": types.UpdateDocument,
"\n mutation MarkReceivedVersion($input: MarkReceivedVersionInput!) {\n versionMutations {\n markReceived(input: $input)\n }\n }\n": types.MarkReceivedVersionDocument,
"\n mutation CreateModel($input: CreateModelInput!) {\n modelMutations {\n create(input: $input) {\n ...ModelListModelItem\n }\n }\n }\n": types.CreateModelDocument,
"\n mutation CreateProject($input: ProjectCreateInput) {\n projectMutations {\n create(input: $input) {\n ...ProjectListProjectItem\n }\n }\n }\n": types.CreateProjectDocument,
"\n mutation CreateProjectInWorkspace($input: WorkspaceProjectCreateInput!) {\n workspaceMutations {\n projects {\n create(input: $input) {\n ...ProjectListProjectItem\n }\n }\n }\n }\n": types.CreateProjectInWorkspaceDocument,
"\n mutation StreamAccessRequestCreate($input: String!) {\n streamAccessRequestCreate(streamId: $input) {\n id\n }\n }\n": types.StreamAccessRequestCreateDocument,
"\n fragment WorkspaceListWorkspaceItem on Workspace {\n id\n slug\n name\n description\n createdAt\n updatedAt\n logo\n role\n readOnly\n }\n": types.WorkspaceListWorkspaceItemFragmentDoc,
"\n fragment WorkspaceListWorkspaceItem on Workspace {\n id\n slug\n name\n description\n createdAt\n updatedAt\n logo\n role\n readOnly\n creationState {\n completed\n }\n permissions {\n canCreateProject {\n authorized\n code\n message\n }\n }\n }\n": types.WorkspaceListWorkspaceItemFragmentDoc,
"\n fragment AutomateFunctionItem on AutomateFunction {\n name\n isFeatured\n id\n creator {\n name\n }\n releases {\n items {\n inputSchema\n }\n }\n }\n": types.AutomateFunctionItemFragmentDoc,
"\n mutation CreateAutomation($projectId: ID!, $input: ProjectAutomationCreateInput!) {\n projectMutations {\n automationMutations(projectId: $projectId) {\n create(input: $input) {\n id\n name\n }\n }\n }\n }\n": types.CreateAutomationDocument,
"\n fragment AutomateFunctionRunItem on AutomateFunctionRun {\n id\n status\n statusMessage\n results\n contextView\n function {\n id\n name\n logo\n }\n }\n": types.AutomateFunctionRunItemFragmentDoc,
"\n fragment AutomationRunItem on AutomateRun {\n id\n status\n automation {\n id\n name\n }\n functionRuns {\n ...AutomateFunctionRunItem\n }\n }\n": types.AutomationRunItemFragmentDoc,
"\n query AutomationStatus($projectId: String!, $modelId: String!) {\n project(id: $projectId) {\n model(id: $modelId) {\n automationsStatus {\n id\n status\n automationRuns {\n ...AutomationRunItem\n }\n }\n }\n }\n }\n": types.AutomationStatusDocument,
"\n query WorkspaceListQuery(\n $limit: Int!\n $filter: UserWorkspacesFilter\n $cursor: String\n ) {\n activeUser {\n id\n workspaces(limit: $limit, filter: $filter, cursor: $cursor) {\n totalCount\n cursor\n items {\n ...WorkspaceListWorkspaceItem\n }\n }\n }\n }\n": types.WorkspaceListQueryDocument,
"\n query ActiveUser {\n activeUser {\n role\n id\n name\n }\n }\n": types.ActiveUserDocument,
"\n query CanCreatePersonalProject {\n activeUser {\n permissions {\n canCreatePersonalProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": types.CanCreatePersonalProjectDocument,
"\n query CanCreateProjectInWorkspace($workspaceId: String!) {\n workspace(id: $workspaceId) {\n permissions {\n canCreateProject {\n authorized\n code\n message\n payload\n }\n }\n }\n }\n": types.CanCreateProjectInWorkspaceDocument,
"\n query CanCreateModelInProject($projectId: String!) {\n project(id: $projectId) {\n permissions {\n canCreateModel {\n authorized\n code\n message\n }\n }\n }\n }\n": types.CanCreateModelInProjectDocument,
@@ -116,6 +120,10 @@ export function graphql(source: "\n mutation SetActiveWorkspaceMutation($slug:
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation VersionMutations($input: CreateVersionInput!) {\n versionMutations {\n create(input: $input) {\n id\n }\n }\n }\n"): (typeof documents)["\n mutation VersionMutations($input: CreateVersionInput!) {\n versionMutations {\n create(input: $input) {\n id\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation Update($input: UpdateVersionInput!) {\n versionMutations {\n update(input: $input) {\n id\n }\n }\n }\n"): (typeof documents)["\n mutation Update($input: UpdateVersionInput!) {\n versionMutations {\n update(input: $input) {\n id\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -139,7 +147,7 @@ export function graphql(source: "\n mutation StreamAccessRequestCreate($input:
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment WorkspaceListWorkspaceItem on Workspace {\n id\n slug\n name\n description\n createdAt\n updatedAt\n logo\n role\n readOnly\n }\n"): (typeof documents)["\n fragment WorkspaceListWorkspaceItem on Workspace {\n id\n slug\n name\n description\n createdAt\n updatedAt\n logo\n role\n readOnly\n }\n"];
export function graphql(source: "\n fragment WorkspaceListWorkspaceItem on Workspace {\n id\n slug\n name\n description\n createdAt\n updatedAt\n logo\n role\n readOnly\n creationState {\n completed\n }\n permissions {\n canCreateProject {\n authorized\n code\n message\n }\n }\n }\n"): (typeof documents)["\n fragment WorkspaceListWorkspaceItem on Workspace {\n id\n slug\n name\n description\n createdAt\n updatedAt\n logo\n role\n readOnly\n creationState {\n completed\n }\n permissions {\n canCreateProject {\n authorized\n code\n message\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -164,6 +172,10 @@ export function graphql(source: "\n query AutomationStatus($projectId: String!,
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query WorkspaceListQuery(\n $limit: Int!\n $filter: UserWorkspacesFilter\n $cursor: String\n ) {\n activeUser {\n id\n workspaces(limit: $limit, filter: $filter, cursor: $cursor) {\n totalCount\n cursor\n items {\n ...WorkspaceListWorkspaceItem\n }\n }\n }\n }\n"): (typeof documents)["\n query WorkspaceListQuery(\n $limit: Int!\n $filter: UserWorkspacesFilter\n $cursor: String\n ) {\n activeUser {\n id\n workspaces(limit: $limit, filter: $filter, cursor: $cursor) {\n totalCount\n cursor\n items {\n ...WorkspaceListWorkspaceItem\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ActiveUser {\n activeUser {\n role\n id\n name\n }\n }\n"): (typeof documents)["\n query ActiveUser {\n activeUser {\n role\n id\n name\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
+209 -10
View File
@@ -270,12 +270,13 @@ export type AutomateFunction = {
isFeatured: Scalars['Boolean']['output'];
logo?: Maybe<Scalars['String']['output']>;
name: Scalars['String']['output'];
permissions: AutomateFunctionPermissionChecks;
releases: AutomateFunctionReleaseCollection;
repo: BasicGitRepositoryMetadata;
/** SourceAppNames values from @speckle/shared. Empty array means - all of them */
supportedSourceApps: Array<Scalars['String']['output']>;
tags: Array<Scalars['String']['output']>;
workspaceIds?: Maybe<Array<Scalars['String']['output']>>;
workspaceIds: Array<Scalars['String']['output']>;
};
@@ -292,6 +293,11 @@ export type AutomateFunctionCollection = {
totalCount: Scalars['Int']['output'];
};
export type AutomateFunctionPermissionChecks = {
__typename?: 'AutomateFunctionPermissionChecks';
canRegenerateToken: PermissionCheckResult;
};
export type AutomateFunctionRelease = {
__typename?: 'AutomateFunctionRelease';
commitId: Scalars['String']['output'];
@@ -373,6 +379,7 @@ export type AutomateMutations = {
__typename?: 'AutomateMutations';
createFunction: AutomateFunction;
createFunctionWithoutVersion: AutomateFunctionToken;
regenerateFunctionToken: Scalars['String']['output'];
updateFunction: AutomateFunction;
};
@@ -387,6 +394,11 @@ export type AutomateMutationsCreateFunctionWithoutVersionArgs = {
};
export type AutomateMutationsRegenerateFunctionTokenArgs = {
functionId: Scalars['String']['input'];
};
export type AutomateMutationsUpdateFunctionArgs = {
input: UpdateAutomateFunctionInput;
};
@@ -918,6 +930,12 @@ export type CreateCommentReplyInput = {
threadId: Scalars['String']['input'];
};
export type CreateEmbedTokenReturn = {
__typename?: 'CreateEmbedTokenReturn';
token: Scalars['String']['output'];
tokenMetadata: EmbedToken;
};
export type CreateModelInput = {
description?: InputMaybe<Scalars['String']['input']>;
name: Scalars['String']['input'];
@@ -994,6 +1012,32 @@ export type EmailVerificationRequestInput = {
id: Scalars['ID']['input'];
};
/** A token used to enable an embedded viewer for a private project */
export type EmbedToken = {
__typename?: 'EmbedToken';
createdAt: Scalars['DateTime']['output'];
lastUsed: Scalars['DateTime']['output'];
lifespan: Scalars['BigInt']['output'];
projectId: Scalars['String']['output'];
resourceIdString: Scalars['String']['output'];
tokenId: Scalars['String']['output'];
user?: Maybe<LimitedUser>;
};
export type EmbedTokenCollection = {
__typename?: 'EmbedTokenCollection';
cursor?: Maybe<Scalars['String']['output']>;
items: Array<EmbedToken>;
totalCount: Scalars['Int']['output'];
};
export type EmbedTokenCreateInput = {
lifespan?: InputMaybe<Scalars['BigInt']['input']>;
projectId: Scalars['String']['input'];
/** The model(s) and version(s) string used in the embed url */
resourceIdString: Scalars['String']['input'];
};
export type FileUpload = {
__typename?: 'FileUpload';
branchName: Scalars['String']['output'];
@@ -1012,17 +1056,53 @@ export type FileUpload = {
id: Scalars['String']['output'];
/** Model associated with the file upload, if it exists already */
model?: Maybe<Model>;
modelId?: Maybe<Scalars['String']['output']>;
/** Alias for branchName */
modelName: Scalars['String']['output'];
/** Alias for streamId */
projectId: Scalars['String']['output'];
streamId: Scalars['String']['output'];
/** Date when upload was last updated */
updatedAt: Scalars['DateTime']['output'];
uploadComplete: Scalars['Boolean']['output'];
uploadDate: Scalars['DateTime']['output'];
/** The user's id that uploaded this file. */
userId: Scalars['String']['output'];
};
export type FileUploadCollection = {
__typename?: 'FileUploadCollection';
cursor?: Maybe<Scalars['String']['output']>;
items: Array<FileUpload>;
totalCount: Scalars['Int']['output'];
};
export type FileUploadMutations = {
__typename?: 'FileUploadMutations';
/**
* Generate a pre-signed url to which a file can be uploaded.
* After uploading the file, call mutation startFileImport to register the completed upload.
*/
generateUploadUrl: GenerateFileUploadUrlOutput;
/**
* Before calling this mutation, call generateUploadUrl to get the
* pre-signed url and blobId. Then upload the file to that url.
* Once the upload to the pre-signed url is completed, this mutation should be
* called to register the completed upload and create the blob metadata.
*/
startFileImport: FileUpload;
};
export type FileUploadMutationsGenerateUploadUrlArgs = {
input: GenerateFileUploadUrlInput;
};
export type FileUploadMutationsStartFileImportArgs = {
input: StartFileImportInput;
};
export type GendoAiRender = {
__typename?: 'GendoAIRender';
camera?: Maybe<Scalars['JSONObject']['output']>;
@@ -1058,6 +1138,24 @@ export type GendoAiRenderInput = {
versionId: Scalars['ID']['input'];
};
export type GenerateFileUploadUrlInput = {
fileName: Scalars['String']['input'];
projectId: Scalars['String']['input'];
};
export type GenerateFileUploadUrlOutput = {
__typename?: 'GenerateFileUploadUrlOutput';
fileId: Scalars['String']['output'];
url: Scalars['String']['output'];
};
export type GetModelUploadsInput = {
/** The cursor for pagination. */
cursor?: InputMaybe<Scalars['String']['input']>;
/** The maximum number of uploads to return. */
limit?: InputMaybe<Scalars['Int']['input']>;
};
export type InvitableCollaboratorsFilter = {
search?: InputMaybe<Scalars['String']['input']>;
};
@@ -1191,8 +1289,12 @@ export type LimitedUserWorkspaceRoleArgs = {
/** Workspace metadata visible to non-workspace members. */
export type LimitedWorkspace = {
__typename?: 'LimitedWorkspace';
/** Workspace admins ordered by join date */
adminTeam: Array<LimitedWorkspaceCollaborator>;
/** Workspace description */
description?: Maybe<Scalars['String']['output']>;
/** If true, the users with a matching domain may join the workspace directly */
discoverabilityAutoJoinEnabled: Scalars['Boolean']['output'];
/** Workspace id */
id: Scalars['ID']['output'];
/** Optional base64 encoded workspace logo image */
@@ -1272,6 +1374,8 @@ export type Model = {
permissions: ModelPermissionChecks;
previewUrl?: Maybe<Scalars['String']['output']>;
updatedAt: Scalars['DateTime']['output'];
/** Get all file uploads ever done in this model */
uploads: FileUploadCollection;
version: Version;
versions: VersionCollection;
};
@@ -1288,6 +1392,11 @@ export type ModelPendingImportedVersionsArgs = {
};
export type ModelUploadsArgs = {
input?: InputMaybe<GetModelUploadsInput>;
};
export type ModelVersionArgs = {
id: Scalars['String']['input'];
};
@@ -1449,6 +1558,7 @@ export type Mutation = {
* @deprecated Part of the old API surface and will be removed in the future. Use VersionMutations.moveToModel instead.
*/
commitsMove: Scalars['Boolean']['output'];
fileUploadMutations: FileUploadMutations;
/**
* Delete a pending invite
* Note: The required scope to invoke this is not given out to app or personal access tokens
@@ -1983,9 +2093,7 @@ export type PendingWorkspaceCollaborator = {
updatedAt: Scalars['DateTime']['output'];
/** Set only if user is registered */
user?: Maybe<LimitedUser>;
workspaceId: Scalars['String']['output'];
workspaceName: Scalars['String']['output'];
workspaceSlug: Scalars['String']['output'];
workspace: LimitedWorkspace;
};
export type PendingWorkspaceCollaboratorsFilter = {
@@ -2024,6 +2132,8 @@ export type Project = {
description?: Maybe<Scalars['String']['output']>;
/** Public project-level configuration for embedded viewer */
embedOptions: ProjectEmbedOptions;
embedTokens: EmbedTokenCollection;
hasAccessToFeature: Scalars['Boolean']['output'];
id: Scalars['ID']['output'];
invitableCollaborators: WorkspaceCollaboratorCollection;
/** Collaborators who have been invited, but not yet accepted. */
@@ -2105,6 +2215,17 @@ export type ProjectCommentThreadsArgs = {
};
export type ProjectEmbedTokensArgs = {
cursor?: InputMaybe<Scalars['String']['input']>;
limit?: InputMaybe<Scalars['Int']['input']>;
};
export type ProjectHasAccessToFeatureArgs = {
featureName: WorkspaceFeatureName;
};
export type ProjectInvitableCollaboratorsArgs = {
cursor?: InputMaybe<Scalars['String']['input']>;
filter?: InputMaybe<InvitableCollaboratorsFilter>;
@@ -2495,6 +2616,7 @@ export type ProjectMutations = {
batchDelete: Scalars['Boolean']['output'];
/** Create new project */
create: Project;
createEmbedToken: CreateEmbedTokenReturn;
/**
* Create onboarding/tutorial project. If one is already created for the active user, that
* one will be returned instead.
@@ -2506,6 +2628,8 @@ export type ProjectMutations = {
invites: ProjectInviteMutations;
/** Leave a project. Only possible if you're not the last remaining owner. */
leave: Scalars['Boolean']['output'];
revokeEmbedToken: Scalars['Boolean']['output'];
revokeEmbedTokens: Scalars['Boolean']['output'];
/** Updates an existing project */
update: Project;
/** Update role for a collaborator */
@@ -2528,6 +2652,11 @@ export type ProjectMutationsCreateArgs = {
};
export type ProjectMutationsCreateEmbedTokenArgs = {
token: EmbedTokenCreateInput;
};
export type ProjectMutationsDeleteArgs = {
id: Scalars['String']['input'];
};
@@ -2538,6 +2667,17 @@ export type ProjectMutationsLeaveArgs = {
};
export type ProjectMutationsRevokeEmbedTokenArgs = {
projectId: Scalars['String']['input'];
token: Scalars['String']['input'];
};
export type ProjectMutationsRevokeEmbedTokensArgs = {
projectId: Scalars['String']['input'];
};
export type ProjectMutationsUpdateArgs = {
update: ProjectUpdateInput;
};
@@ -2578,6 +2718,7 @@ export type ProjectPermissionChecks = {
canBroadcastActivity: PermissionCheckResult;
canCreateAutomation: PermissionCheckResult;
canCreateComment: PermissionCheckResult;
canCreateEmbedTokens: PermissionCheckResult;
canCreateModel: PermissionCheckResult;
canDelete: PermissionCheckResult;
canInvite: PermissionCheckResult;
@@ -2586,9 +2727,11 @@ export type ProjectPermissionChecks = {
canMoveToWorkspace: PermissionCheckResult;
canPublish: PermissionCheckResult;
canRead: PermissionCheckResult;
canReadEmbedTokens: PermissionCheckResult;
canReadSettings: PermissionCheckResult;
canReadWebhooks: PermissionCheckResult;
canRequestRender: PermissionCheckResult;
canRevokeEmbedTokens: PermissionCheckResult;
canUpdate: PermissionCheckResult;
canUpdateAllowPublicComments: PermissionCheckResult;
};
@@ -3016,6 +3159,7 @@ export type Role = {
export type RootPermissionChecks = {
__typename?: 'RootPermissionChecks';
canCreatePersonalProject: PermissionCheckResult;
canCreateWorkspace: PermissionCheckResult;
};
/** Available scopes. */
@@ -3252,6 +3396,17 @@ export enum SortDirection {
Desc = 'DESC'
}
export type StartFileImportInput = {
/**
* The etag is returned by the blob storage provider in the response body after a successful upload.
* It is used to verify the integrity of the uploaded file.
*/
etag: Scalars['String']['input'];
fileId: Scalars['String']['input'];
modelId: Scalars['String']['input'];
projectId: Scalars['String']['input'];
};
export type Stream = {
__typename?: 'Stream';
/**
@@ -4102,12 +4257,14 @@ export type UserMeta = {
__typename?: 'UserMeta';
legacyProjectsExplainerCollapsed: Scalars['Boolean']['output'];
newWorkspaceExplainerDismissed: Scalars['Boolean']['output'];
speckleConBannerDismissed: Scalars['Boolean']['output'];
};
export type UserMetaMutations = {
__typename?: 'UserMetaMutations';
setLegacyProjectsExplainerCollapsed: Scalars['Boolean']['output'];
setNewWorkspaceExplainerDismissed: Scalars['Boolean']['output'];
setSpeckleConBannerDismissed: Scalars['Boolean']['output'];
};
@@ -4120,6 +4277,11 @@ export type UserMetaMutationsSetNewWorkspaceExplainerDismissedArgs = {
value: Scalars['Boolean']['input'];
};
export type UserMetaMutationsSetSpeckleConBannerDismissedArgs = {
value: Scalars['Boolean']['input'];
};
export type UserProjectCollection = {
__typename?: 'UserProjectCollection';
cursor?: Maybe<Scalars['String']['output']>;
@@ -4445,7 +4607,11 @@ export type Workspace = {
* region.
*/
defaultRegion?: Maybe<ServerRegionItem>;
/** The default seat assigned to users that join a workspace. Used during workspace discovery or on invites without seat types. */
defaultSeatType: WorkspaceSeatType;
description?: Maybe<Scalars['String']['output']>;
/** If true, allow users to automatically join discoverable workspaces (instead of requesting to join) */
discoverabilityAutoJoinEnabled: Scalars['Boolean']['output'];
/** Enable/Disable discovery of the workspace */
discoverabilityEnabled: Scalars['Boolean']['output'];
/** Enable/Disable restriction to invite users to workspace as Guests only */
@@ -4458,6 +4624,8 @@ export type Workspace = {
id: Scalars['ID']['output'];
/** Only available to workspace owners/members */
invitedTeam?: Maybe<Array<PendingWorkspaceCollaborator>>;
/** Exclusive workspaces do not allow their workspace members to create or join other workspaces as members. */
isExclusive: Scalars['Boolean']['output'];
/** Logo image as base64-encoded string */
logo?: Maybe<Scalars['String']['output']>;
name: Scalars['String']['output'];
@@ -4545,6 +4713,7 @@ export type WorkspaceBillingMutationsUpgradePlanArgs = {
/** Overridden by `WorkspaceCollaboratorGraphQLReturn` */
export type WorkspaceCollaborator = {
__typename?: 'WorkspaceCollaborator';
email?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
/** Date that the user joined the workspace. */
joinDate: Scalars['DateTime']['output'];
@@ -4612,6 +4781,8 @@ export type WorkspaceEmbedOptions = {
export enum WorkspaceFeatureName {
DomainBasedSecurityPolicies = 'domainBasedSecurityPolicies',
ExclusiveMembership = 'exclusiveMembership',
HideSpeckleBranding = 'hideSpeckleBranding',
OidcSso = 'oidcSso',
WorkspaceDataRegionSpecificity = 'workspaceDataRegionSpecificity'
}
@@ -4621,6 +4792,8 @@ export type WorkspaceInviteCreateInput = {
email?: InputMaybe<Scalars['String']['input']>;
/** Defaults to the member role, if not specified */
role?: InputMaybe<WorkspaceRole>;
/** The workspace seat type to assign to the user upon accepting the invite. */
seatType?: InputMaybe<WorkspaceSeatType>;
/** Defaults to User, if not specified */
serverRole?: InputMaybe<ServerRole>;
/** Either this or email must be filled */
@@ -4687,6 +4860,7 @@ export type WorkspaceInviteUseInput = {
export type WorkspaceJoinRequest = {
__typename?: 'WorkspaceJoinRequest';
createdAt: Scalars['DateTime']['output'];
email?: Maybe<Scalars['String']['output']>;
id: Scalars['String']['output'];
status: WorkspaceJoinRequestStatus;
user: LimitedUser;
@@ -4839,7 +5013,9 @@ export type WorkspacePermissionChecks = {
canCreateProject: PermissionCheckResult;
canEditEmbedOptions: PermissionCheckResult;
canInvite: PermissionCheckResult;
canMakeWorkspaceExclusive: PermissionCheckResult;
canMoveProjectToWorkspace: PermissionCheckResult;
canReadMemberEmail: PermissionCheckResult;
};
@@ -4877,6 +5053,7 @@ export type WorkspacePlanUsage = {
export enum WorkspacePlans {
Academia = 'academia',
Enterprise = 'enterprise',
Free = 'free',
Pro = 'pro',
ProUnlimited = 'proUnlimited',
@@ -4899,6 +5076,11 @@ export type WorkspaceProjectInviteCreateInput = {
email?: InputMaybe<Scalars['String']['input']>;
/** Defaults to the contributor role, if not specified */
role?: InputMaybe<Scalars['String']['input']>;
/**
* The workspace seat type to assign to the user upon accepting the invite
* (if user is a workspace member already, the seat type will be updated)
*/
seatType?: InputMaybe<WorkspaceSeatType>;
/** Can only be specified if guest mode is on or if the user is an admin */
serverRole?: InputMaybe<Scalars['String']['input']>;
/** Either this or email must be filled */
@@ -5076,10 +5258,13 @@ export type WorkspaceUpdateEmbedOptionsInput = {
};
export type WorkspaceUpdateInput = {
defaultSeatType?: InputMaybe<WorkspaceSeatType>;
description?: InputMaybe<Scalars['String']['input']>;
discoverabilityAutoJoinEnabled?: InputMaybe<Scalars['Boolean']['input']>;
discoverabilityEnabled?: InputMaybe<Scalars['Boolean']['input']>;
domainBasedMembershipProtectionEnabled?: InputMaybe<Scalars['Boolean']['input']>;
id: Scalars['String']['input'];
isExclusive?: InputMaybe<Scalars['Boolean']['input']>;
/** Logo image as base64-encoded string */
logo?: InputMaybe<Scalars['String']['input']>;
name?: InputMaybe<Scalars['String']['input']>;
@@ -5114,6 +5299,13 @@ export type VersionMutationsMutationVariables = Exact<{
export type VersionMutationsMutation = { __typename?: 'Mutation', versionMutations: { __typename?: 'VersionMutations', create: { __typename?: 'Version', id: string } } };
export type UpdateMutationVariables = Exact<{
input: UpdateVersionInput;
}>;
export type UpdateMutation = { __typename?: 'Mutation', versionMutations: { __typename?: 'VersionMutations', update: { __typename?: 'Version', id: string } } };
export type MarkReceivedVersionMutationVariables = Exact<{
input: MarkReceivedVersionInput;
}>;
@@ -5149,7 +5341,7 @@ export type StreamAccessRequestCreateMutationVariables = Exact<{
export type StreamAccessRequestCreateMutation = { __typename?: 'Mutation', streamAccessRequestCreate: { __typename?: 'StreamAccessRequest', id: string } };
export type WorkspaceListWorkspaceItemFragment = { __typename?: 'Workspace', id: string, slug: string, name: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, role?: string | null, readOnly: boolean };
export type WorkspaceListWorkspaceItemFragment = { __typename?: 'Workspace', id: string, slug: string, name: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, role?: string | null, readOnly: boolean, creationState?: { __typename?: 'WorkspaceCreationState', completed: boolean } | null, permissions: { __typename?: 'WorkspacePermissionChecks', canCreateProject: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string } } };
export type AutomateFunctionItemFragment = { __typename?: 'AutomateFunction', name: string, isFeatured: boolean, id: string, creator?: { __typename?: 'LimitedUser', name: string } | null, releases: { __typename?: 'AutomateFunctionReleaseCollection', items: Array<{ __typename?: 'AutomateFunctionRelease', inputSchema?: {} | null }> } };
@@ -5180,7 +5372,12 @@ export type WorkspaceListQueryQueryVariables = Exact<{
}>;
export type WorkspaceListQueryQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, workspaces: { __typename?: 'WorkspaceCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Workspace', id: string, slug: string, name: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, role?: string | null, readOnly: boolean }> } } | null };
export type WorkspaceListQueryQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, workspaces: { __typename?: 'WorkspaceCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Workspace', id: string, slug: string, name: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, role?: string | null, readOnly: boolean, creationState?: { __typename?: 'WorkspaceCreationState', completed: boolean } | null, permissions: { __typename?: 'WorkspacePermissionChecks', canCreateProject: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string } } }> } } | null };
export type ActiveUserQueryVariables = Exact<{ [key: string]: never; }>;
export type ActiveUserQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', role?: string | null, id: string, name: string } | null };
export type CanCreatePersonalProjectQueryVariables = Exact<{ [key: string]: never; }>;
@@ -5204,7 +5401,7 @@ export type CanCreateModelInProjectQuery = { __typename?: 'Query', project: { __
export type ActiveWorkspaceQueryVariables = Exact<{ [key: string]: never; }>;
export type ActiveWorkspaceQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', activeWorkspace?: { __typename?: 'Workspace', id: string, slug: string, name: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, role?: string | null, readOnly: boolean } | null } | null };
export type ActiveWorkspaceQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', activeWorkspace?: { __typename?: 'Workspace', id: string, slug: string, name: string, description?: string | null, createdAt: string, updatedAt: string, logo?: string | null, role?: string | null, readOnly: boolean, creationState?: { __typename?: 'WorkspaceCreationState', completed: boolean } | null, permissions: { __typename?: 'WorkspacePermissionChecks', canCreateProject: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string } } } | null } | null };
export type ProjectListProjectItemFragment = { __typename?: 'Project', id: string, name: string, role?: string | null, updatedAt: string, workspaceId?: string | null, workspace?: { __typename?: 'Workspace', id: string, name: string, slug: string, role?: string | null } | null, models: { __typename?: 'ModelCollection', totalCount: number }, permissions: { __typename?: 'ProjectPermissionChecks', canLoad: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string }, canPublish: { __typename?: 'PermissionCheckResult', authorized: boolean, code: string, message: string } } };
@@ -5341,7 +5538,7 @@ export type ProjectCommentsUpdatedSubscriptionVariables = Exact<{
export type ProjectCommentsUpdatedSubscription = { __typename?: 'Subscription', projectCommentsUpdated: { __typename?: 'ProjectCommentsUpdatedMessage', type: ProjectCommentsUpdatedMessageType, comment?: { __typename?: 'Comment', id: string, hasParent: boolean, author: { __typename?: 'LimitedUser', avatar?: string | null, id: string, name: string }, parent?: { __typename?: 'Comment', id: string } | null } | null } };
export const WorkspaceListWorkspaceItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceListWorkspaceItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode<WorkspaceListWorkspaceItemFragment, unknown>;
export const WorkspaceListWorkspaceItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceListWorkspaceItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"creationState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"completed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canCreateProject"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]} as unknown as DocumentNode<WorkspaceListWorkspaceItemFragment, unknown>;
export const AutomateFunctionItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"creator"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"releases"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"inputSchema"}}]}}]}}]}}]} as unknown as DocumentNode<AutomateFunctionItemFragment, unknown>;
export const AutomateFunctionRunItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionRunItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}}]}}]}}]} as unknown as DocumentNode<AutomateFunctionRunItemFragment, unknown>;
export const AutomationRunItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationRunItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionRunItem"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionRunItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}}]}}]}}]} as unknown as DocumentNode<AutomationRunItemFragment, unknown>;
@@ -5350,6 +5547,7 @@ export const VersionListItemFragmentDoc = {"kind":"Document","definitions":[{"ki
export const ModelListModelItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ModelListModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"VersionListItem"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"VersionListItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","name":{"kind":"Name","value":"authorUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}}]}}]} as unknown as DocumentNode<ModelListModelItemFragment, unknown>;
export const SetActiveWorkspaceMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SetActiveWorkspaceMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"slug"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUserMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setActiveWorkspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"slug"}}}]}]}}]}}]} as unknown as DocumentNode<SetActiveWorkspaceMutationMutation, SetActiveWorkspaceMutationMutationVariables>;
export const VersionMutationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"VersionMutations"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateVersionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode<VersionMutationsMutation, VersionMutationsMutationVariables>;
export const UpdateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Update"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateVersionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"update"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode<UpdateMutation, UpdateMutationVariables>;
export const MarkReceivedVersionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"MarkReceivedVersion"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MarkReceivedVersionInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"versionMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"markReceived"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]}}]} as unknown as DocumentNode<MarkReceivedVersionMutation, MarkReceivedVersionMutationVariables>;
export const CreateModelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateModel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateModelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ModelListModelItem"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"VersionListItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","name":{"kind":"Name","value":"authorUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ModelListModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"VersionListItem"}}]}}]}}]}}]} as unknown as DocumentNode<CreateModelMutation, CreateModelMutationVariables>;
export const CreateProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCreateInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectListProjectItem"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectListProjectItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"Field","name":{"kind":"Name","value":"models"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canLoad"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canPublish"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]} as unknown as DocumentNode<CreateProjectMutation, CreateProjectMutationVariables>;
@@ -5357,11 +5555,12 @@ export const CreateProjectInWorkspaceDocument = {"kind":"Document","definitions"
export const StreamAccessRequestCreateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StreamAccessRequestCreate"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"streamAccessRequestCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"streamId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode<StreamAccessRequestCreateMutation, StreamAccessRequestCreateMutationVariables>;
export const CreateAutomationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateAutomation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectAutomationCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"automationMutations"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"projectId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CreateAutomationMutation, CreateAutomationMutationVariables>;
export const AutomationStatusDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"AutomationStatus"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"model"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationRunItem"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateFunctionRunItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationRunItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateFunctionRunItem"}}]}}]}}]} as unknown as DocumentNode<AutomationStatusQuery, AutomationStatusQueryVariables>;
export const WorkspaceListQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceListQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UserWorkspacesFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceListWorkspaceItem"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceListWorkspaceItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode<WorkspaceListQueryQuery, WorkspaceListQueryQueryVariables>;
export const WorkspaceListQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceListQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UserWorkspacesFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspaces"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceListWorkspaceItem"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceListWorkspaceItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"creationState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"completed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canCreateProject"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]} as unknown as DocumentNode<WorkspaceListQueryQuery, WorkspaceListQueryQueryVariables>;
export const ActiveUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode<ActiveUserQuery, ActiveUserQueryVariables>;
export const CanCreatePersonalProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CanCreatePersonalProject"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canCreatePersonalProject"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CanCreatePersonalProjectQuery, CanCreatePersonalProjectQueryVariables>;
export const CanCreateProjectInWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CanCreateProjectInWorkspace"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspace"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canCreateProject"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"payload"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CanCreateProjectInWorkspaceQuery, CanCreateProjectInWorkspaceQueryVariables>;
export const CanCreateModelInProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"CanCreateModelInProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canCreateModel"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]}}]} as unknown as DocumentNode<CanCreateModelInProjectQuery, CanCreateModelInProjectQueryVariables>;
export const ActiveWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveWorkspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeWorkspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceListWorkspaceItem"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceListWorkspaceItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}}]}}]} as unknown as DocumentNode<ActiveWorkspaceQuery, ActiveWorkspaceQueryVariables>;
export const ActiveWorkspaceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ActiveWorkspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeWorkspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceListWorkspaceItem"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceListWorkspaceItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"readOnly"}},{"kind":"Field","name":{"kind":"Name","value":"creationState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"completed"}}]}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canCreateProject"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]} as unknown as DocumentNode<ActiveWorkspaceQuery, ActiveWorkspaceQueryVariables>;
export const ProjectListQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectListQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"UserProjectsFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectListProjectItem"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectListProjectItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"Field","name":{"kind":"Name","value":"models"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"permissions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"canLoad"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}},{"kind":"Field","name":{"kind":"Name","value":"canPublish"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authorized"}},{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"message"}}]}}]}}]}}]} as unknown as DocumentNode<ProjectListQueryQuery, ProjectListQueryQueryVariables>;
export const ProjectModelsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ProjectModels"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectModelsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ModelListModelItem"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"VersionListItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","name":{"kind":"Name","value":"authorUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ModelListModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"VersionListItem"}}]}}]}}]}}]} as unknown as DocumentNode<ProjectModelsQuery, ProjectModelsQueryVariables>;
export const ModelVersionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ModelVersions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"limit"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ModelVersionsFilter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"projectId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"Variable","name":{"kind":"Name","value":"limit"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"VersionListItem"}}]}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"VersionListItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Version"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"message"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","name":{"kind":"Name","value":"authorUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}}]}}]} as unknown as DocumentNode<ModelVersionsQuery, ModelVersionsQueryVariables>;
+4 -2
View File
@@ -7,7 +7,8 @@ import type {
} from '~/lib/common/generated/gql/graphql'
import {
projectAddByUrlQueryWithoutVersion,
projectAddByUrlQueryWithVersion
projectAddByUrlQueryWithVersion,
userInfoAndServerRoleQuery
} from '~/lib/graphql/mutationsAndQueries'
import { omit } from 'lodash-es'
import { useDebounceFn } from '@vueuse/core'
@@ -60,6 +61,7 @@ export function useAddByUrl() {
const { projectId, modelId, versionId } = params
const apollo = (acc as DUIAccount).client
const userInfoRes = await apollo.query({ query: userInfoAndServerRoleQuery })
let project: ProjectListProjectItemFragment | undefined = undefined,
model: ModelListModelItemFragment | undefined = undefined,
@@ -113,7 +115,7 @@ export function useAddByUrl() {
? project.permissions.canPublish.authorized
: project.permissions.canLoad.authorized
if (!hasAccess) {
if (!hasAccess && userInfoRes.data.activeUser?.role !== 'server:admin') {
urlParseError.value = errorMessage
return
}
+17
View File
@@ -0,0 +1,17 @@
/**
* @param previewUrl url that server returns but does not return the corresponding image if the project is private
* @param token auth token to get proper image over url
*/
export async function usePreviewUrl(
token: string,
previewUrl?: string
): Promise<string | undefined> {
if (!previewUrl) return previewUrl
const res = await fetch(previewUrl, {
headers: { Authorization: `Bearer ${token}` }
})
if (!res.ok) return previewUrl //
const blob = await res.blob()
return URL.createObjectURL(blob)
}
+40 -38
View File
@@ -1,8 +1,17 @@
import type { ToastNotification } from '@speckle/ui-components'
import { ToastNotificationType } from '@speckle/ui-components'
import { useConfigStore } from '~/store/config'
import { useHostAppStore } from '~/store/hostApp'
export class UpdateError extends Error {
constructor(message: string) {
super(message)
this.name = 'FetchError'
// Required when extending Error in TypeScript
Object.setPrototypeOf(this, new.target.prototype)
}
}
type Versions = {
Versions: Version[]
}
@@ -18,31 +27,13 @@ export type Version = {
export function useUpdateConnector() {
const hostApp = useHostAppStore()
const config = useConfigStore()
const { $openUrl } = useNuxtApp()
const versions = ref<Version[]>([])
const latestAvailableVersion = ref<Version | null>(null)
const isUpToDate = computed(
() => hostApp.connectorVersion === latestAvailableVersion.value?.Number
)
async function checkUpdate() {
try {
await getVersions()
if (!isUpToDate.value && !config.isDevMode) {
const notification: ToastNotification = {
type: ToastNotificationType.Success,
title: `New connector update available`,
description: latestAvailableVersion.value?.Number.replace('+0', ''), // TODO: currently versions end with "+0" Alan will have a look
cta: {
title: `Update`,
onClick: () => downloadLatestVersion()
}
}
hostApp.setNotification(notification)
}
} catch (e) {
console.error(e)
const notification: ToastNotification = {
@@ -54,28 +45,39 @@ export function useUpdateConnector() {
}
async function getVersions() {
const response = await fetch(
`https://releases.speckle.dev/manager2/feeds/${hostApp.hostAppName?.toLowerCase()}-v3.json`,
{
method: 'GET'
try {
// End point to get list of versions that deployed by Speckle's pipeline
const response = await fetch(
`https://releases.speckle.dev/manager2/feeds/${hostApp.hostAppName?.toLowerCase()}-v3.json`,
{
method: 'GET'
}
)
if (!response.ok) {
// It is the only way to understand the connector is distributed by Speckle or not.
throw new UpdateError('Failed to fetch versions')
}
)
if (!response.ok) {
throw new Error('Failed to fetch versions')
const data = (await response.json()) as unknown as Versions
const sortedVersions = data.Versions.sort(function (a: Version, b: Version) {
return new Date(b.Date).getTime() - new Date(a.Date).getTime()
})
versions.value = sortedVersions
latestAvailableVersion.value = sortedVersions[0]
hostApp.setLatestAvailableVersion(sortedVersions[0])
} catch (err) {
if (err instanceof TypeError && err.message === 'Failed to fetch') {
// When user has network issue in between, actually it is not so likely because regardless user need network to be able to render netlify page
throw new Error('Network error')
} else if (err instanceof UpdateError) {
// We set the flag to use it in relavant places, hide some documentation related buttons etc..
hostApp.setIsDistributedBySpeckle(false)
} else {
// Rest of the possibilites that we trigger toast
throw new Error('Unknown error occurred')
}
}
const data = (await response.json()) as unknown as Versions
const sortedVersions = data.Versions.sort(function (a: Version, b: Version) {
return new Date(b.Date).getTime() - new Date(a.Date).getTime()
})
versions.value = sortedVersions
latestAvailableVersion.value = sortedVersions[0]
hostApp.setLatestAvailableVersion(sortedVersions[0])
}
function downloadLatestVersion() {
$openUrl(latestAvailableVersion.value?.Url as string)
}
return { checkUpdate }
+22 -2
View File
@@ -1,6 +1,7 @@
import type { JsonFormsRendererRegistryEntry } from '@jsonforms/core'
import {
and,
hasType,
isBooleanControl,
isDateControl,
isDateTimeControl,
@@ -11,12 +12,15 @@ import {
isOneOfEnumControl,
isStringControl,
isTimeControl,
rankWith
rankWith,
schemaMatches,
uiTypeIs
} from '@jsonforms/core'
import { vanillaRenderers } from '@jsonforms/vue-vanilla'
import BooleanControlRenderer from '~/components/form/json/BooleanControlRenderer.vue'
import DateControlRenderer from '~/components/form/json/DateControlRenderer.vue'
import DateTimeControlRenderer from '~/components/form/json/DateTimeControlRenderer.vue'
import MultiEnumControlRenderer from '~/components/form/json/MultiEnumControlRenderer.vue'
import EnumControlRenderer from '~/components/form/json/EnumControlRenderer.vue'
import EnumOneOfControlRenderer from '~/components/form/json/EnumOneOfControlRenderer.vue'
import IntegerControlRenderer from '~/components/form/json/IntegerControlRenderer.vue'
@@ -75,6 +79,21 @@ export const timeControlRenderer: JsonFormsRendererRegistryEntry = {
tester: rankWith(4, isTimeControl)
}
export const multiEnumControlRenderer: JsonFormsRendererRegistryEntry = {
renderer: MultiEnumControlRenderer as unknown,
tester: rankWith(
6,
and(
uiTypeIs('Control'),
and(
schemaMatches(
(schema) => hasType(schema, 'array') && !Array.isArray(schema.items)
)
)
)
)
}
export const renderers: JsonFormsRendererRegistryEntry[] = markRaw([
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
...vanillaRenderers,
@@ -87,5 +106,6 @@ export const renderers: JsonFormsRendererRegistryEntry[] = markRaw([
numberControlRenderer,
dateControlRenderer,
dateTimeControlRenderer,
timeControlRenderer
timeControlRenderer,
multiEnumControlRenderer
])
+30
View File
@@ -18,6 +18,16 @@ export const createVersionMutation = graphql(`
}
`)
export const setVersionMessageMutation = graphql(`
mutation Update($input: UpdateVersionInput!) {
versionMutations {
update(input: $input) {
id
}
}
}
`)
export const markReceivedVersionMutation = graphql(`
mutation MarkReceivedVersion($input: MarkReceivedVersionInput!) {
versionMutations {
@@ -77,6 +87,16 @@ export const workspaceListFragment = graphql(`
logo
role
readOnly
creationState {
completed
}
permissions {
canCreateProject {
authorized
code
message
}
}
}
`)
@@ -173,6 +193,16 @@ export const workspacesListQuery = graphql(`
}
`)
export const userInfoAndServerRoleQuery = graphql(`
query ActiveUser {
activeUser {
role
id
name
}
}
`)
export const canCreatePersonalProjectQuery = graphql(`
query CanCreatePersonalProject {
activeUser {
+85
View File
@@ -0,0 +1,85 @@
import md5 from '~/lib/common/helpers/md5'
import { useAccountStore } from '~/store/accounts'
import { useHostAppStore } from '~/store/hostApp'
const SEQ_URL = 'https://seq-dev.speckle.systems/api/events/raw'
type LogLevel = 'Verbose' | 'Debug' | 'Information' | 'Warning' | 'Error' | 'Fatal'
const collectCommonProperties = () => {
const { accounts, activeAccount } = useAccountStore()
const hashedEmail =
'@' +
md5(activeAccount.accountInfo.userInfo.email.toLowerCase() as string).toUpperCase()
return {
user: {
id: activeAccount.accountInfo.userInfo.id,
distinctId: hashedEmail
},
dui3: true,
accountCount: accounts.length
}
}
const collectResources = () => {
const hostAppStore = useHostAppStore()
return {
'@ra': {
connector: {
slug: hostAppStore.hostAppName,
hostAppVersion: hostAppStore.hostAppVersion,
version: hostAppStore.connectorVersion
},
service: {
version: hostAppStore.connectorVersion // this needs alignment with .NET SDK, actually this should be connector.version instead service.version
}
}
}
}
// const collectServices = () => {
// const hostAppStore = useHostAppStore()
// return {
// '@sa': {
// service: {
// version: hostAppStore.connectorVersion
// }
// }
// }
// }
export const logToSeq = async (
level: LogLevel,
message: string,
properties: Record<string, unknown> = {}
) => {
try {
const logEvent = {
'@t': new Date().toISOString(),
'@l': level,
'@m': message,
...collectResources(),
// ...collectServices(),
...collectCommonProperties(),
...properties
}
const response = await fetch(SEQ_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/vnd.serilog.clef',
'X-Seq-ApiKey': 'y5YnBp12ZE1Czh4tzZWn'
},
body: JSON.stringify(logEvent) + '\n'
})
if (!response.ok) {
const errorText = await response.text()
console.error(
`[Seq Logger] Failed to log: ${response.status} ${response.statusText} - ${errorText}`
)
}
} catch (err) {
console.error('[Seq Logger] Failed to log', err)
}
}
+153
View File
@@ -0,0 +1,153 @@
import type { CategoryOption } from './types'
/**
* Hardcoded Revit BuiltInCategories for the Interop Lite mapper.
*/
export const REVIT_CATEGORIES: readonly CategoryOption[] = [
// INFRASTRUCTURE
{ value: 'OST_BridgeAbutments', label: 'Abutments' },
{ value: 'OST_AbutmentFoundations', label: 'Abutment Foundations' },
{ value: 'OST_AbutmentPiles', label: 'Abutment Piles' },
{ value: 'OST_AbutmentWalls', label: 'Abutment Walls' },
{ value: 'OST_ApproachSlabs', label: 'Approach Slabs' },
{ value: 'OST_BridgeBearings', label: 'Bearings' },
{ value: 'OST_BridgeCables', label: 'Bridge Cables' },
{ value: 'OST_BridgeDecks', label: 'Bridge Decks' },
{ value: 'OST_BridgeFraming', label: 'Bridge Framing' },
{ value: 'OST_BridgeArches', label: 'Bridge Arches' },
{ value: 'OST_BridgeFramingCrossBracing', label: 'Bridge Framing - Cross Bracing' },
{ value: 'OST_BridgeFramingDiaphragms', label: 'Bridge Framing - Diaphragms' },
{ value: 'OST_BridgeGirders', label: 'Bridge Framing - Girders' },
{ value: 'OST_BridgeFramingTrusses', label: 'Bridge Framing - Trusses' },
{ value: 'OST_ExpansionJoints', label: 'Expansion Joints' },
{ value: 'OST_BridgePiers', label: 'Piers' },
{ value: 'OST_PierCaps', label: 'Pier Caps' },
{ value: 'OST_PierColumns', label: 'Pier Columns' },
{ value: 'OST_BridgeFoundations', label: 'Pier Foundations' },
{ value: 'OST_PierPiles', label: 'Pier Piles' },
{ value: 'OST_BridgeTowers', label: 'Pier Towers' },
{ value: 'OST_PierWalls', label: 'Pier Walls' },
{ value: 'OST_StructuralTendons', label: 'Structural Tendons' },
{ value: 'OST_VibrationManagement', label: 'Vibration Management' },
{ value: 'OST_VibrationDampers', label: 'Vibration Dampers' },
{ value: 'OST_VibrationIsolators', label: 'Vibration Isolators' },
// ARCHITECTURE
{ value: 'OST_AudioVisualDevices', label: 'Audio Visual Devices' },
{ value: 'OST_Casework', label: 'Casework' },
{ value: 'OST_Ceilings', label: 'Ceilings' },
{ value: 'OST_Columns', label: 'Columns' },
{ value: 'OST_CurtainWallPanels', label: 'Curtain Panels' },
// { value: 'OST_CurtaSystem', label: 'Curtain Systems' }, excluded as part of CNX-2299
{ value: 'OST_CurtainWallMullions', label: 'Curtain Wall Mullions' },
{ value: 'OST_Doors', label: 'Doors' },
{ value: 'OST_Entourage', label: 'Entourage' },
{ value: 'OST_FireProtection', label: 'Fire Protection' },
{ value: 'OST_Floors', label: 'Floors' },
{ value: 'OST_FoodServiceEquipment', label: 'Food Service Equipment' },
{ value: 'OST_Furniture', label: 'Furniture' },
{ value: 'OST_FurnitureSystems', label: 'Furniture Systems' },
{ value: 'OST_GenericModel', label: 'Generic Models' },
{ value: 'OST_Hardscape', label: 'Hardscape' },
{ value: 'OST_Lines', label: 'Lines' },
{ value: 'OST_Mass', label: 'Mass' },
{ value: 'OST_MechanicalControlDevices', label: 'Mechanical Control Devices' },
{ value: 'OST_MechanicalEquipment', label: 'Mechanical Equipment' },
{ value: 'OST_MedicalEquipment', label: 'Medical Equipment' },
{ value: 'OST_Parking', label: 'Parking' },
{ value: 'OST_Parts', label: 'Parts' },
{ value: 'OST_Planting', label: 'Planting' },
{ value: 'OST_PlumbingEquipment', label: 'Plumbing Equipment' },
{ value: 'OST_PlumbingFixtures', label: 'Plumbing Fixtures' },
{ value: 'OST_StairsRailing', label: 'Railings' },
{ value: 'OST_StairsRailingBaluster', label: 'Railings - Balusters' },
{ value: 'OST_RailingSupport', label: 'Railings - Supports' },
{ value: 'OST_RailingTermination', label: 'Railings - Terminations' },
{ value: 'OST_Ramps', label: 'Ramps' },
{ value: 'OST_Roads', label: 'Roads' },
{ value: 'OST_Roofs', label: 'Roofs' },
{ value: 'OST_Signage', label: 'Signage' },
{ value: 'OST_Site', label: 'Site' },
{ value: 'OST_SpecialtyEquipment', label: 'Specialty Equipment' },
{ value: 'OST_Stairs', label: 'Stairs' },
{ value: 'OST_TemporaryStructure', label: 'Temporary Structures' },
{ value: 'OST_Topography', label: 'Topography' },
{ value: 'OST_Toposolid', label: 'Toposolid' },
{ value: 'OST_VerticalCirculation', label: 'Vertical Circulation' },
{ value: 'OST_Walls', label: 'Walls' },
{ value: 'OST_Windows', label: 'Windows' },
// ELECTRICAL
{ value: 'OST_CableTrayFitting', label: 'Cable Tray Fittings' },
{ value: 'OST_CableTray', label: 'Cable Trays' },
{ value: 'OST_CommunicationDevices', label: 'Communication Devices' },
{ value: 'OST_ConduitFittings', label: 'Conduit Fittings' },
{ value: 'OST_Conduit', label: 'Conduits' },
{ value: 'OST_DataDevices', label: 'Data Devices' },
{ value: 'OST_ElectricalEquipment', label: 'Electrical Equipment' },
{ value: 'OST_ElectricalFixtures', label: 'Electrical Fixtures' },
{ value: 'OST_FireAlarmDevices', label: 'Fire Alarm Devices' },
{ value: 'OST_LighintgDevices', label: 'Lighting Devices' },
{ value: 'OST_LightingFixtures', label: 'Lighting Fixtures' },
{ value: 'OST_NurseCallDevices', label: 'Nurse Call Devices' },
{ value: 'OST_SecurityDevices', label: 'Security Devices' },
{ value: 'OST_TelephoneDevices', label: 'Telephone Devices' },
// STRUCTURE
{ value: 'OST_Coupler', label: 'Structural Rebar Couplers' },
{ value: 'OST_FabricAreas', label: 'Structural Fabric Areas' },
{ value: 'OST_StructConnections', label: 'Structural Connections' },
{ value: 'OST_StructConnectionAnchors', label: 'Structural Connections - Anchors' },
{ value: 'OST_StructConnectionBolts', label: 'Structural Connections - Bolts' },
{ value: 'OST_StructConnectionPlates', label: 'Structural Connections - Plates' },
{ value: 'OST_StructConnectionProfiles', label: 'Structural Connections - Profiles' },
{
value: 'OST_StructConnectionShearStuds',
label: 'Structural Connections - Shear Studs'
},
{ value: 'OST_StructConnectionWelds', label: 'Structural Connections - Welds' },
// { value: 'OST_StructuralColumns', label: 'Structural Columns' }, excluded as part of CNX-2299
{ value: 'OST_StructuralFoundation', label: 'Structural Foundations' },
{ value: 'OST_StructuralFraming', label: 'Structural Framing' },
{ value: 'OST_StructuralTruss', label: 'Structural Trusses' },
{ value: 'OST_Rebar', label: 'Structural Rebar' },
// MECHANICAL
{ value: 'OST_DuctTerminal', label: 'Air Terminals' },
{ value: 'OST_DuctAccessory', label: 'Duct Accessories' },
{ value: 'OST_DuctFitting', label: 'Duct Fittings' },
{ value: 'OST_PlaceHolderDucts', label: 'Duct Placeholders' },
{ value: 'OST_DuctCurves', label: 'Ducts' },
{ value: 'OST_MEPAncillaryFraming', label: 'MEP Ancillary Framing' },
// PIPING
{ value: 'OST_PipeAccessory', label: 'Pipe Accessories' },
{ value: 'OST_PipeFitting', label: 'Pipe Fittings' },
{ value: 'OST_PlaceHolderPipes', label: 'Pipe Placeholders' },
{ value: 'OST_PipeSegments', label: 'Pipe Segments' },
{ value: 'OST_PipeCurves', label: 'Pipes' },
{ value: 'OST_Sprinklers', label: 'Sprinklers' },
// GENERAL/MULTI-DISCIPLINE
{ value: 'OST_CableTrayRun', label: 'Cable Tray Runs' },
{ value: 'OST_Coordination_Model', label: 'Coordination Model' },
{ value: 'OST_DuctSystem', label: 'Duct Systems' },
{ value: 'OST_PipingSystem', label: 'Piping Systems' },
{ value: 'OST_StructuralFramingSystem', label: 'Structural Beam Systems' },
{ value: 'OST_StructuralStiffener', label: 'Structural Stiffeners' }
] as const
/**
* Get available categories sorted alphabetically by label.
*/
export function getAvailableCategories(): CategoryOption[] {
return [...REVIT_CATEGORIES].sort((a, b) => a.label.localeCompare(b.label))
}
/**
* Gets the human-readable label for a category value.
*/
export function getCategoryLabel(categoryValue: string): string {
const category = REVIT_CATEGORIES.find((c) => c.value === categoryValue)
return category?.label ?? categoryValue
}
+4
View File
@@ -0,0 +1,4 @@
export interface CategoryOption {
value: string // e.g. "OST_Walls"
label: string // e.g. "Walls"
}
+6
View File
@@ -6,8 +6,14 @@ export type ModelCardNotification = {
modelCardId: string
text: string
level: ModelCardNotificationLevel
secondaryCta?: {
name: string
tooltipText?: string
action: () => void
}
cta?: {
name: string
tooltipText?: string
action: () => void
}
/**
+4 -5
View File
@@ -10,16 +10,14 @@ export default defineNuxtConfig({
'@nuxt/eslint',
'@nuxtjs/tailwindcss',
'@speckle/ui-components-nuxt',
'@pinia/nuxt'
'@pinia/nuxt',
'@nuxt/image'
],
alias: {
// Rewriting all lodash calls to lodash-es for proper tree-shaking & chunk splitting
// lodash: 'lodash-es'
},
// pinia: {
// autoImports: ['defineStore', 'storeToRefs']
// },
runtimeConfig: {
public: {
mixpanelApiHost: 'UNDEFINED',
@@ -29,7 +27,8 @@ export default defineNuxtConfig({
speckleUserId: process.env.SPECKLE_USER_ID,
speckleUrl: process.env.SPECKLE_URL,
speckleSampleProjectId: process.env.SPECKLE_SAMPLE_PROJECT_ID,
speckleSampleModelId: process.env.SPECKLE_SAMPLE_MODEL_ID
speckleSampleModelId: process.env.SPECKLE_SAMPLE_MODEL_ID,
intercomAppId: ''
}
},
vite: {
+8 -6
View File
@@ -26,16 +26,18 @@
"@apollo/client": "^3.7.14",
"@headlessui/vue": "^1.7.13",
"@heroicons/vue": "^2.0.12",
"@intercom/messenger-js-sdk": "^0.0.14",
"@jsonforms/core": "3.1.0",
"@jsonforms/vue": "3.1.0",
"@jsonforms/vue-vanilla": "3.1.0",
"@nuxt/image": "^1.10.0",
"@pinia/nuxt": "^0.4.11",
"@speckle/objectloader": "^2.24.0",
"@speckle/objectsender": "^2.24.0",
"@speckle/shared": "^2.24.0",
"@speckle/tailwind-theme": "2.24.1-alpha.0",
"@speckle/ui-components": "^2.24.0",
"@speckle/ui-components-nuxt": "^2.24.0",
"@speckle/objectloader": "^2.25.0",
"@speckle/objectsender": "^2.25.0",
"@speckle/shared": "^2.25.0",
"@speckle/tailwind-theme": "2.25.0",
"@speckle/ui-components": "^2.25.0",
"@speckle/ui-components-nuxt": "^2.25.0",
"@vue/apollo-composable": "^4.0.0-beta.5",
"@vueuse/core": "^9.13.0",
"apollo-upload-client": "^17.0.0",
+78 -14
View File
@@ -1,6 +1,6 @@
<template>
<div>
<div v-if="store.hostAppName">
<div v-if="(store.hostAppName && app.$isRunningOnConnector) || app.$isDev">
<div v-if="!config.isDevMode" class="px-1">
<CommonUpdateAlert />
</div>
@@ -11,11 +11,23 @@
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%]">
<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>
<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">
@@ -57,7 +69,10 @@
</div>
</div>
<!-- TEMPORARY MESSAGE TO USER! will be deleted -->
<div class="mt-2 bg-highlight-1 rounded-md p-2">
<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"
>
@@ -74,15 +89,15 @@
full-width
@click="
app.$openUrl(
`https://speckle.systems/connectors/${store.hostAppName}`
`https://docs.speckle.systems/connectors/${store.hostAppName}?utm=dui`
)
"
>
<span class="capitalize">{{ store.hostAppName }}&nbsp;</span>
documentation
Getting started
</FormButton>
</div>
<!--
<FormButton
text
size="sm"
@@ -98,11 +113,34 @@
<span class="text-foreground-2 text-body-3xs truncate line-clamp-1">
New connectors announcement
</span>
</FormButton>
</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">
<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>
@@ -124,6 +162,27 @@
</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">
@@ -153,7 +212,8 @@ import { storeToRefs } from 'pinia'
import {
ArrowDownTrayIcon,
ArrowUpTrayIcon,
ArrowPathIcon
ArrowPathIcon,
ArrowTopRightOnSquareIcon
} from '@heroicons/vue/24/solid'
import { useAccountStore } from '~~/store/accounts'
import { useHostAppStore } from '~~/store/hostApp'
@@ -176,7 +236,7 @@ const { trackEvent } = useMixpanel()
const showSendDialog = ref(false)
const showReceiveDialog = ref(false)
app.$baseBinding.on('documentChanged', () => {
app.$baseBinding?.on('documentChanged', () => {
showSendDialog.value = false
showReceiveDialog.value = false
})
@@ -208,4 +268,8 @@ const hasNoValidProjects = computed(() => {
const reload = () => {
window.location.reload()
}
const openSpeckleConnectors = () => {
window.open('https://app.speckle.systems/connectors', '_blank')
}
</script>
+693
View File
@@ -0,0 +1,693 @@
<template>
<div class="flex flex-col space-y-2">
<div class="px-2 space-y-1">
<FormButton to="/" size="sm" :icon-left="ArrowLeftIcon" class="my-1">
Home
</FormButton>
<hr />
</div>
<!-- Step 1: Mapping Mode Selection -->
<div class="px-2">
<p class="h5">Mapping Mode</p>
<div class="space-y-2 my-2">
<FormSelectBase
:model-value="selectedMappingMode"
name="mappingMode"
label="Assign by"
class="w-full"
fixed-height
size="sm"
:items="mappingModeOptions"
:allow-unset="false"
mount-menu-on-body
@update:model-value="(value) => handleModeChange(value as string)"
>
<template #something-selected="{ value }">
<span class="text-primary text-xs">{{ value }}</span>
</template>
<template #option="{ item }">
<span class="text-xs">{{ item }}</span>
</template>
</FormSelectBase>
<!-- Mode-specific content -->
<div v-if="selectedMappingMode === 'Selection'">
<MapperSelectionMapper
:has-selection="(selectionInfo?.selectedObjectIds?.length || 0) > 0"
:selection-summary="selectionInfo?.summary || ''"
/>
</div>
<MapperLayerMapper
v-if="selectedMappingMode === 'Layer'"
v-model:selected-layers="selectedLayers"
:layer-options="layerOptions"
/>
</div>
</div>
<!-- Step 2: Category Selection -->
<div v-if="hasTargetsSelected" class="px-2">
<p class="h5">Target Category</p>
<div class="space-y-2 my-2">
<div class="flex space-x-2 items-center">
<div class="flex-1">
<FormSelectBase
key="label"
v-model="revitMapperStore.selectedCategory"
name="categoryMapping"
:placeholder="dropdownPlaceholder"
label="Target Category"
fixed-height
size="sm"
search
:search-placeholder="''"
:filter-predicate="searchFilterPredicate"
:items="categoryOptions"
:allow-unset="false"
mount-menu-on-body
>
<template #something-selected>
<span class="text-primary text-xs">
{{ displayLabel }}
</span>
</template>
<template #option="{ item }">
<span class="text-xs">{{ item.label }}</span>
</template>
</FormSelectBase>
</div>
<!-- Apply button -->
<FormButton
color="primary"
size="sm"
:disabled="!revitMapperStore.selectedCategory?.value"
@click="assignToCategory()"
>
Apply
</FormButton>
</div>
</div>
</div>
<hr />
<!-- Step 3: Mappings Summary Tables -->
<div
v-if="currentMappings.length > 0 || currentLayerMappings.length > 0"
class="px-2"
>
<p class="h5">
{{ `Assigned Categories (${currentMappings.length > 0 ? 'Object' : 'Layer'})` }}
</p>
<!-- Object Mappings Section -->
<div v-if="currentMappings.length > 0" class="my-2">
<div class="space-y-1">
<MapperMappedElementItem
v-for="mapping in currentMappings"
:key="mapping.categoryValue"
:category-label="mapping.categoryLabel"
:count-text="`${mapping.objectCount} object${
mapping.objectCount !== 1 ? 's' : ''
}`"
@select="selectMappedObjects(mapping)"
@clear="clearMapping(mapping)"
/>
</div>
</div>
<!-- Layer Mappings Section -->
<div v-if="currentLayerMappings.length > 0" class="my-2">
<div class="space-y-1">
<MapperMappedElementItem
v-for="layerMapping in currentLayerMappings"
:key="layerMapping.categoryValue"
:category-label="layerMapping.categoryLabel"
:count-text="`${layerMapping.layerCount} layer${
layerMapping.layerCount !== 1 ? 's' : ''
}`"
:tooltip-text="`Layers: ${layerMapping.layerNames.join(', ')}`"
@select="selectMappedLayers(layerMapping)"
@clear="clearLayerMapping(layerMapping)"
/>
</div>
</div>
<!-- Clear All and Select All buttons -->
<div class="flex justify-end space-x-2">
<!-- Selection mode buttons -->
<div
v-if="selectedMappingMode === 'Selection' && currentMappings.length > 0"
class="flex space-x-2"
>
<FormButton size="sm" color="outline" @click="selectAllMappedObjects()">
Select All
</FormButton>
<FormButton size="sm" color="danger" @click="clearAllMappings()">
Clear All Objects
</FormButton>
</div>
<!-- Layer mode buttons -->
<div
v-else-if="selectedMappingMode === 'Layer' && currentLayerMappings.length > 0"
class="flex space-x-2"
>
<FormButton size="sm" color="outline" @click="selectAllMappedLayers()">
Select All
</FormButton>
<FormButton size="sm" color="danger" @click="clearAllLayerMappings()">
Clear All Layers
</FormButton>
</div>
</div>
<!-- Mode Confirmation Dialog -->
<CommonDialog
v-model:open="showModeConfirmDialog"
title="Switch Category Assignment Mode"
fullscreen="none"
>
<div class="text-sm text-foreground">
{{ conflictMessage }}
</div>
<div class="mt-4 flex justify-end space-x-2">
<FormButton size="sm" color="outline" @click="cancelModeChange()">
Cancel
</FormButton>
<FormButton size="sm" color="danger" @click="confirmModeChange()">
Clear & Switch
</FormButton>
</div>
</CommonDialog>
</div>
</div>
</template>
<script setup lang="ts">
// === IMPORTS ===
import { storeToRefs } from 'pinia'
import { ArrowLeftIcon } from '@heroicons/vue/20/solid'
import { useSelectionStore } from '~/store/selection'
import { useRevitMapper } from '~/store/revitMapper'
import type {
Category,
CategoryMapping,
LayerCategoryMapping
} from '~/lib/bindings/definitions/IRevitMapperBinding'
// Import categories
import { getAvailableCategories, getCategoryLabel } from '~/lib/mapper/revit-categories'
import { useMixpanel } from '~/lib/core/composables/mixpanel'
// === STORES ===
const selectionStore = useSelectionStore()
const revitMapperStore = useRevitMapper()
const { selectionInfo } = storeToRefs(selectionStore)
const { trackEvent } = useMixpanel()
// === STATE ===
const selectedMappingMode = ref<string | undefined>(undefined)
const mappingModeOptions = ['Selection', 'Layer']
const categoryOptions = ref<Category[]>([])
const mappings = ref<CategoryMapping[]>([])
// Layer-specific state
const selectedLayers = ref<LayerOption[]>([])
const layerOptions = ref<LayerOption[]>([])
const layerMappings = ref<LayerCategoryMapping[]>([])
// Mode switching state
const showModeConfirmDialog = ref(false)
const pendingMode = ref<string>('')
const conflictMessage = ref('')
// === TYPES ===
interface LayerOption {
id: string
name: string
}
// === MAPPING CATEGORY STATE MGMT ===
const app = useNuxtApp()
const { $revitMapperBinding, $baseBinding } = app
// const categoryState = useRevitCategoryState(categoryOptions, $revitMapperBinding)
// === COMPUTED ===
const hasTargetsSelected = computed(() => {
if (selectedMappingMode.value === 'Selection') {
return (selectionInfo.value?.selectedObjectIds?.length || 0) > 0
} else if (selectedMappingMode.value === 'Layer') {
return selectedLayers.value.length > 0
}
return false
})
// Show appropriate mappings based on current mode
const currentMappings = computed(() => {
return selectedMappingMode.value === 'Selection' ? mappings.value : []
})
const currentLayerMappings = computed(() => {
return selectedMappingMode.value === 'Layer' ? layerMappings.value : []
})
const dropdownPlaceholder = computed(() => {
if (revitMapperStore.categoryStatus) {
return revitMapperStore.categoryStatus?.isMultiple
? 'Multiple categories'
: 'Select a category'
}
return undefined
})
const displayLabel = computed(() => {
const multiple = revitMapperStore.categoryStatus?.isMultiple
return multiple
? 'Multiple categories'
: revitMapperStore.selectedCategory?.label || ''
})
// === METHODS ===
// Search predicate for category dropdown
const searchFilterPredicate = (item: Category, query: string) => {
return item.label.toLowerCase().includes(query.toLowerCase())
}
// Handle mode changes with conflict checking
const handleModeChange = (newMode: string) => {
// If switching to same mode, do nothing
if (newMode === selectedMappingMode.value) return
// Check for conflicts - ONLY show dialog if there are existing mappings
if (newMode === 'Layer' && mappings.value.length > 0) {
// Switching to Layer mode with existing object mappings
pendingMode.value = newMode
conflictMessage.value = `Switching to Layer assignment mode will clear all current object category assignments. Continue?`
showModeConfirmDialog.value = true
} else if (newMode === 'Selection' && layerMappings.value.length > 0) {
// Switching to Selection mode with existing layer mappings
pendingMode.value = newMode
conflictMessage.value = `Switching to Selection assignment mode will clear all current layer category assignments. Continue?`
showModeConfirmDialog.value = true
} else {
// No conflicts, switch directly (no existing mappings or switching to same mode)
selectedMappingMode.value = newMode
}
}
// Cancel mode change
const cancelModeChange = () => {
showModeConfirmDialog.value = false
pendingMode.value = ''
conflictMessage.value = ''
}
// Confirm mode change and clear conflicting mappings
const confirmModeChange = async () => {
try {
if (pendingMode.value === 'Layer') {
// Clear all object mappings before switching to Layer mode
await $revitMapperBinding?.clearAllObjectsCategoryAssignments()
} else if (pendingMode.value === 'Selection') {
// Clear all layer mappings before switching to Selection mode
await $revitMapperBinding?.clearAllLayerCategoryAssignments()
}
// Track the manual mode switch
trackEvent('DUI3 Action', {
name: 'Mapper Mode Changed',
mode: selectedMappingMode.value
})
// Switch mode
selectedMappingMode.value = pendingMode.value
await refreshMappings()
// Close dialog
showModeConfirmDialog.value = false
pendingMode.value = ''
conflictMessage.value = ''
} catch (error) {
console.error('Failed to clear category assignments during mode switch:', error)
}
}
// Assign selected objects/layers to the chosen category
const assignToCategory = async () => {
if (!revitMapperStore.selectedCategory?.value || !hasTargetsSelected.value) return
try {
let assignedCount = 0
const { selectedCategory } = storeToRefs(revitMapperStore)
const categoryValue = selectedCategory?.value?.value
if (selectedMappingMode.value === 'Selection' && categoryValue) {
const objectIds = selectionInfo.value?.selectedObjectIds || []
await $revitMapperBinding?.assignObjectsToCategory(objectIds, categoryValue)
assignedCount = objectIds.length
// Track the assignment
trackEvent('DUI3 Action', {
name: 'Mapper Assign Category',
category: categoryValue,
count: assignedCount,
mappingType: 'object'
})
} else if (selectedMappingMode.value === 'Layer' && categoryValue) {
const layerIds = selectedLayers.value.map((layer) => layer.id)
await $revitMapperBinding?.assignLayerToCategory(layerIds, categoryValue)
assignedCount = selectedLayers.value.length
// Track the assignment
trackEvent('DUI3 Action', {
name: 'Mapper Assign Category',
category: categoryValue,
count: assignedCount,
mappingType: 'layer'
})
selectedLayers.value = []
}
selectedCategory.value = undefined
await refreshMappings()
} catch (error) {
console.error('Failed to assign to category:', error)
}
}
// Clear a specific object mapping
const clearMapping = async (mapping: CategoryMapping) => {
try {
await $revitMapperBinding?.clearObjectsCategoryAssignment(mapping.objectIds)
await refreshMappings()
} catch (error) {
console.error('Failed to clear category assignment:', error)
}
}
// Clear all object mappings
const clearAllMappings = async () => {
try {
await $revitMapperBinding?.clearAllObjectsCategoryAssignments()
await refreshMappings()
} catch (error) {
console.error('Failed to clear all category assignments:', error)
}
}
// Clear a specific layer mapping
const clearLayerMapping = async (layerMapping: LayerCategoryMapping) => {
try {
await $revitMapperBinding?.clearLayerCategoryAssignment(layerMapping.layerIds)
await refreshMappings()
} catch (error) {
console.error('Failed to clear layer assignment:', error)
}
}
// Clear all layer mappings
const clearAllLayerMappings = async () => {
try {
await $revitMapperBinding?.clearAllLayerCategoryAssignments()
await refreshMappings()
} catch (error) {
console.error('Failed to clear all layer assignments:', error)
}
}
// Select mapped objects in Rhino
const selectMappedObjects = async (mapping: CategoryMapping) => {
try {
await $baseBinding?.highlightObjects(mapping.objectIds)
} catch (error) {
console.error('Failed to highlight objects:', error)
}
}
// Select mapped layers (highlight objects AND restore UI state)
const selectMappedLayers = async (layerMapping: LayerCategoryMapping) => {
try {
// 1. Highlight objects in Rhino
const effectiveObjectIds =
(await $revitMapperBinding?.getEffectiveObjectsForLayerMapping(
layerMapping.layerIds,
layerMapping.categoryValue
)) || []
if (effectiveObjectIds.length > 0) {
await $baseBinding?.highlightObjects(effectiveObjectIds)
}
// 2. Restore UI state - populate layer selection
const layersToSelect = layerOptions.value.filter((layer) =>
layerMapping.layerIds.includes(layer.id)
)
selectedLayers.value = layersToSelect
// 3. Pre-select category in dropdown
const categoryToSelect = categoryOptions.value.find(
(cat) => cat.value === layerMapping.categoryValue
)
const { selectedCategory, currentCategories } = storeToRefs(revitMapperStore)
selectedCategory.value = categoryToSelect
// 4. Update reactive state
currentCategories.value = [layerMapping.categoryValue]
} catch (error) {
console.error('Failed to highlight effective objects:', error)
}
}
// Select all mapped objects (Selection mode)
const selectAllMappedObjects = async () => {
try {
const allObjectIds = currentMappings.value.flatMap((mapping) => mapping.objectIds)
if (allObjectIds.length > 0) {
await $baseBinding?.highlightObjects(allObjectIds)
}
} catch (error) {
console.error('Failed to select all objects with categories assigned:', error)
}
}
// Select all objects affected by layer mappings (Layer mode)
const selectAllMappedLayers = async () => {
try {
const allEffectiveObjectIds: string[] = []
// Get effective objects for each layer mapping
for (const layerMapping of currentLayerMappings.value) {
const effectiveObjectIds =
(await $revitMapperBinding?.getEffectiveObjectsForLayerMapping(
layerMapping.layerIds,
layerMapping.categoryValue
)) || []
allEffectiveObjectIds.push(...effectiveObjectIds)
}
// Remove duplicates and highlight
const uniqueObjectIds = [...new Set(allEffectiveObjectIds)]
if (uniqueObjectIds.length > 0) {
await $baseBinding?.highlightObjects(uniqueObjectIds)
}
} catch (error) {
console.error(
'Failed to select all objects with categories assigned by layer:',
error
)
}
}
// Load available categories, layers, and current mappings
const loadData = async () => {
try {
const [categories, rawMappings, rawLayerMappings, layers] = await Promise.all([
getAvailableCategories() || [],
$revitMapperBinding?.getCurrentObjectsMappings() || [],
$revitMapperBinding?.getCurrentLayerMappings() || [],
loadAvailableLayers()
])
categoryOptions.value = categories
// Transform mappings to include human-readable labels
mappings.value = rawMappings.map((mapping) => ({
...mapping,
categoryLabel: getCategoryLabel(mapping.categoryValue)
}))
layerMappings.value = rawLayerMappings.map((mapping) => ({
...mapping,
categoryLabel: getCategoryLabel(mapping.categoryValue)
}))
layerOptions.value = layers
// IMPORTANT: Determine initial mapping mode based on existing mappings
// This preserves the user's last used mode and prevents mixed state scenarios
if (!selectedMappingMode.value) {
if (rawLayerMappings.length > 0 && rawMappings.length === 0) {
// Only layer mappings exist - user was in Layer mode
selectedMappingMode.value = 'Layer'
} else if (rawMappings.length > 0 && rawLayerMappings.length === 0) {
// Only object mappings exist - user was in Selection mode
selectedMappingMode.value = 'Selection'
} else if (rawLayerMappings.length > 0 && rawMappings.length > 0) {
// Mixed state detected - this shouldn't happen, but default to Selection
// and let the conflict handling take care of it
selectedMappingMode.value = 'Selection'
console.warn(
'Mixed assignment state detected - both object and layer assignments exist'
)
} else {
// No existing mappings - default to Selection mode
selectedMappingMode.value = 'Selection'
}
}
} catch (error) {
console.error('Failed to load categorizer data:', error)
// Fallback to Selection mode if loading fails
if (!selectedMappingMode.value) {
selectedMappingMode.value = 'Selection'
}
}
}
// Refresh both object and layer mappings
const refreshMappings = async () => {
try {
if (!$revitMapperBinding) {
console.warn('No Revit category assignment binding available')
return
}
const [rawMappings, rawLayerMappings] = await Promise.all([
$revitMapperBinding.getCurrentObjectsMappings(),
$revitMapperBinding.getCurrentLayerMappings()
])
// Transform to resolve labels
mappings.value = rawMappings.map((mapping) => ({
...mapping,
categoryLabel: getCategoryLabel(mapping.categoryValue)
}))
layerMappings.value = rawLayerMappings.map((mapping) => ({
...mapping,
categoryLabel: getCategoryLabel(mapping.categoryValue)
}))
} catch (error) {
console.error('Failed to refresh category assignments:', error)
}
}
// Load available layers from Rhino document
const loadAvailableLayers = async (): Promise<LayerOption[]> => {
try {
// Call the backend method to get available layers
const layers = (await $revitMapperBinding?.getAvailableLayers()) || []
return layers
} catch (error) {
console.error('Failed to load layers:', error)
return []
}
}
// === WATCHER ===
// Main watcher
watch(
() => ({
mode: selectedMappingMode.value,
objectIds: selectionInfo.value?.selectedObjectIds || [],
layerIds: selectedLayers.value.map((l) => l.id)
}),
async ({ mode, objectIds, layerIds }) => {
if (mode === 'Selection') {
await revitMapperStore.updateFromTargets(objectIds, false)
} else if (mode === 'Layer') {
// In Layer mode, we need to watch both manual layer selection AND object selection
// This keeps dropdowns clear when objects are deselected (like Selection mode)
// while still supporting manual layer selection
if (layerIds.length > 0) {
// User has manually selected layers in UI - use layer mode logic
await revitMapperStore.updateFromTargets(layerIds, true)
} else {
// No manual layer selection - use object mode logic (like Selection mode)
// This handles the case where selectMappedLayers populated the UI but objects were deselected
await revitMapperStore.updateFromTargets(objectIds, false)
}
}
},
{ immediate: true, deep: true }
)
// This handles clearing selectedLayers when objects are deselected in Layer mode
watch(
() => selectionInfo.value?.selectedObjectIds?.length || 0,
async (newCount, oldCount) => {
// Only act in Layer mode when selection count goes to 0 and we have selected layers
if (
selectedMappingMode.value === 'Layer' &&
newCount === 0 &&
oldCount > 0 &&
selectedLayers.value.length > 0
) {
// nextTick to avoid interfering with the main watcher? not nice :(
await nextTick()
selectedLayers.value = []
}
}
)
// === LIFECYCLE ===
onMounted(async () => {
await selectionStore.refreshSelectionFromHostApp()
await loadData()
// Listen for mappings changes
$revitMapperBinding?.on('mappingsChanged', (newMappings: CategoryMapping[]) => {
mappings.value = newMappings.map((mapping) => ({
...mapping,
categoryLabel: getCategoryLabel(mapping.categoryValue)
}))
refreshLayerMappings()
})
// Listen for layer list changes
$revitMapperBinding?.on('layersChanged', (newLayers: LayerOption[]) => {
layerOptions.value = newLayers
selectedLayers.value = []
})
// Track mapper opened with the initial mode (after loadData determines it)
trackEvent('DUI3 Action', {
name: 'Mapper Opened',
mode: selectedMappingMode.value
})
})
// Refresh just layer mappings
const refreshLayerMappings = async () => {
try {
const rawLayerMappings =
(await $revitMapperBinding?.getCurrentLayerMappings()) || []
layerMappings.value = rawLayerMappings.map((mapping) => ({
...mapping,
categoryLabel: getCategoryLabel(mapping.categoryValue)
}))
} catch (error) {
console.error('Failed to refresh layer category assignments:', error)
}
}
</script>
+18 -3
View File
@@ -1,7 +1,10 @@
<template>
<div class="flex flex-col space-y-2">
<div class="px-2 mt-2">
<FormButton to="/" size="sm" :icon-left="ArrowLeftIcon">Home</FormButton>
<FormButton to="/" size="sm" :icon-left="ArrowLeftIcon" class="my-2">
Home
</FormButton>
<hr />
<p class="h5">Document info</p>
<p class="text-sm text-foreground-2 py-2">
Current document info. This should change on document swaps, closure, opening,
@@ -11,6 +14,7 @@
<pre>{{ documentInfo }}</pre>
</div>
</div>
<hr />
<div class="px-2">
<p class="h5">Send Filters</p>
<p class="text-sm text-foreground-2 space-x-2">Available send filters:</p>
@@ -32,6 +36,7 @@
<pre>{{ sendFilters }}</pre>
</div>
</div>
<hr />
<div class="px-2">
<p class="h5 mb-4">Chromium 65 Scrollable Dialogs Test</p>
<FormButton @click="showBigDialog = !showBigDialog">Show Big Dialog</FormButton>
@@ -41,6 +46,14 @@
</div>
</CommonDialog>
</div>
<hr />
<div class="px-2">
<p class="h5">Settings</p>
<div class="border rounded-lg p-1">
<ConfigDialog></ConfigDialog>
</div>
</div>
<hr />
<div class="px-2">
<p class="h5">Selection info</p>
<p class="text-sm text-foreground-2 py-2">
@@ -56,6 +69,7 @@
<pre>{{ selectionInfo }}</pre>
</div>
</div>
<hr />
<div class="px-2">
<p class="h5">Document State</p>
<p class="text-sm text-foreground-2 py-2">
@@ -70,6 +84,7 @@
<pre>{{ projectModelGroups }}</pre>
</div>
</div>
<hr />
<div class="px-2">
<p class="h5">Binding tests</p>
<p class="text-sm text-foreground-2 py-2">
@@ -223,7 +238,7 @@ const runTests = async () => {
}
}
$testBindings.on('emptyTestEvent', () => {
$testBindings?.on('emptyTestEvent', () => {
setTimeout(() => {
const myTest = tests.value.find((t) => t.name === 'Simple event capture')
@@ -233,7 +248,7 @@ $testBindings.on('emptyTestEvent', () => {
}, 1000)
})
$testBindings.on('testEvent', (args: TestEventArgs) => {
$testBindings?.on('testEvent', (args: TestEventArgs) => {
setTimeout(() => {
const myTest = tests.value.find((t) => t.name === 'Event capture with args')
+96 -27
View File
@@ -4,26 +4,53 @@ import { SketchupBridge } from '~/lib/bridge/sketchup'
import type { IBasicConnectorBinding } from '~/lib/bindings/definitions/IBasicConnectorBinding'
import type { IAccountBinding } from '~/lib/bindings/definitions/IAccountBinding'
import { IAccountBindingKey } from '~/lib/bindings/definitions/IAccountBinding'
import {
IAccountBindingKey,
MockedAccountBinding
} from '~/lib/bindings/definitions/IAccountBinding'
import type { ITestBinding } from '~/lib/bindings/definitions/ITestBinding'
import { ITestBindingKey } from '~/lib/bindings/definitions/ITestBinding'
import {
ITestBindingKey,
MockedTestBinding
} from '~/lib/bindings/definitions/ITestBinding'
import type { IConfigBinding } from '~/lib/bindings/definitions/IConfigBinding'
import { IConfigBindingKey } from '~/lib/bindings/definitions/IConfigBinding'
import {
IConfigBindingKey,
MockedConfigBinding
} from '~/lib/bindings/definitions/IConfigBinding'
import { IBasicConnectorBindingKey } from '~/lib/bindings/definitions/IBasicConnectorBinding'
import {
IBasicConnectorBindingKey,
MockedBaseBinding
} from '~/lib/bindings/definitions/IBasicConnectorBinding'
import type { ISendBinding } from '~/lib/bindings/definitions/ISendBinding'
import { ISendBindingKey } from '~/lib/bindings/definitions/ISendBinding'
import {
ISendBindingKey,
MockedSendBinding
} from '~/lib/bindings/definitions/ISendBinding'
import type { IReceiveBinding } from '~/lib/bindings/definitions/IReceiveBinding'
import { IReceiveBindingKey } from '~/lib/bindings/definitions/IReceiveBinding'
import {
IReceiveBindingKey,
MockedReceiveBinding
} from '~/lib/bindings/definitions/IReceiveBinding'
import type { ISelectionBinding } from '~/lib/bindings/definitions/ISelectionBinding'
import { ISelectionBindingKey } from '~/lib/bindings/definitions/ISelectionBinding'
import {
ISelectionBindingKey,
MockedSelectionBinding
} from '~/lib/bindings/definitions/ISelectionBinding'
import type { ITopLevelExpectionHandlerBinding } from '~/lib/bindings/definitions/ITopLevelExceptionHandlerBinding'
import { ITopLevelExpectionHandlerBindingKey } from '~/lib/bindings/definitions/ITopLevelExceptionHandlerBinding'
import type { IRevitMapperBinding } from '~/lib/bindings/definitions/IRevitMapperBinding'
import {
IRevitMapperBindingKey,
MockedMapperBinding
} from '~/lib/bindings/definitions/IRevitMapperBinding'
// Makes TS happy
declare let globalThis: Record<string, unknown> & {
CefSharp?: { BindObjectAsync: (name: string) => Promise<void> }
@@ -32,34 +59,73 @@ declare let globalThis: Record<string, unknown> & {
DG?: { LoadObject: (name: string) => Promise<void> }
}
const isWebview = () => !!(globalThis.chrome && globalThis.chrome.webview)
const isSketchup = () => !!globalThis.sketchup
const isCefSharp = () => !!globalThis.CefSharp
const isArchicad = () => isCefSharp() && !!globalThis.DG
const isConnector = () => isWebview() || isSketchup() || isCefSharp() || isArchicad()
/**
* Here we are loading any bindings that we expect to have from all
* connectors. If some are not present, that's okay - we're going to
* strip or customize functionality from the ui itself.
*/
export default defineNuxtPlugin(async () => {
const isRunningOnConnector = isConnector()
globalThis['isRunningOnConnector'] = isRunningOnConnector
const isDev = import.meta.dev
globalThis['isDev'] = isDev
if (!isRunningOnConnector) {
// The state that we wouldn't wanna show any connector related visuals on production like in dui.speckle.systems
console.warn(
'⚠️ You are a bad boy because you are not running DUI in a connector! ⚠️'
)
}
// Registers a set of non existent bindings as a test.
const nonExistantBindings = await tryHoistBinding('nonExistantBindings')
// Registers some default test bindings.
const testBindings = await tryHoistBinding<ITestBinding>(ITestBindingKey)
const testBindings =
isRunningOnConnector || !isDev
? await tryHoistBinding<ITestBinding>(ITestBindingKey)
: hoistMockBinding(new MockedTestBinding(), ITestBindingKey)
// Actual bindings follow below.
const configBinding = await tryHoistBinding<IConfigBinding>(IConfigBindingKey)
const configBinding =
isRunningOnConnector || !isDev
? await tryHoistBinding<IConfigBinding>(IConfigBindingKey)
: hoistMockBinding(new MockedConfigBinding(), IConfigBindingKey)
const accountBinding = await tryHoistBinding<IAccountBinding>(IAccountBindingKey)
const accountBinding =
isRunningOnConnector || !isDev
? await tryHoistBinding<IAccountBinding>(IAccountBindingKey)
: hoistMockBinding(new MockedAccountBinding(), IAccountBindingKey)
const baseBinding = await tryHoistBinding<IBasicConnectorBinding>(
IBasicConnectorBindingKey
)
const baseBinding =
isRunningOnConnector || !isDev
? await tryHoistBinding<IBasicConnectorBinding>(IBasicConnectorBindingKey)
: hoistMockBinding(new MockedBaseBinding(), IBasicConnectorBindingKey)
const sendBinding = await tryHoistBinding<ISendBinding>(ISendBindingKey)
const sendBinding =
isRunningOnConnector || !isDev
? await tryHoistBinding<ISendBinding>(ISendBindingKey)
: hoistMockBinding(new MockedSendBinding(), ISendBindingKey)
const receiveBinding = await tryHoistBinding<IReceiveBinding>(IReceiveBindingKey)
const receiveBinding =
isRunningOnConnector || !isDev
? await tryHoistBinding<IReceiveBinding>(IReceiveBindingKey)
: hoistMockBinding(new MockedReceiveBinding(), IReceiveBindingKey)
const selectionBinding = await tryHoistBinding<ISelectionBinding>(
ISelectionBindingKey
)
const selectionBinding =
isRunningOnConnector || !isDev
? await tryHoistBinding<ISelectionBinding>(ISelectionBindingKey)
: hoistMockBinding(new MockedSelectionBinding(), ISendBindingKey)
const revitMapperBinding =
isRunningOnConnector || !isDev
? await tryHoistBinding<IRevitMapperBinding>(IRevitMapperBindingKey)
: hoistMockBinding(new MockedMapperBinding(), IRevitMapperBindingKey)
const topLevelExceptionHandlerBinding =
await tryHoistBinding<ITopLevelExpectionHandlerBinding>(
@@ -78,6 +144,8 @@ export default defineNuxtPlugin(async () => {
return {
provide: {
isRunningOnConnector,
isDev,
nonExistantBindings,
testBindings,
configBinding,
@@ -88,7 +156,8 @@ export default defineNuxtPlugin(async () => {
selectionBinding,
topLevelExceptionHandlerBinding,
showDevTools,
openUrl
openUrl,
revitMapperBinding
}
}
})
@@ -137,11 +206,11 @@ const tryHoistBinding = async <T>(name: string) => {
return bridge as unknown as T
}
// const hoistMockBinding = (mockBinding: BaseBridge, name: string) => {
// globalThis[name] = mockBinding
// console.log(
// `%c✔ Mocked ${name} binding added succesfully.`,
// 'color: green; font-weight: bold; font-size: small'
// )
// return mockBinding
// }
const hoistMockBinding = <T>(mockBinding: T, name: string) => {
globalThis[name] = mockBinding
console.log(
`%c✔ Mocked ${name} binding added succesfully.`,
'color: green; font-weight: bold; font-size: small'
)
return mockBinding
}
+116
View File
@@ -0,0 +1,116 @@
import { watch, computed, ref } from 'vue'
import Intercom, {
shutdown,
show,
hide,
update,
trackEvent
} from '@intercom/messenger-js-sdk'
import { useAccountStore } from '~/store/accounts'
import { storeToRefs } from 'pinia'
const disabledRoutes: string[] = []
export const useIntercom = () => {
const route = useRoute()
const accountStore = useAccountStore()
const { activeAccount } = storeToRefs(accountStore)
const isInitialized = ref(false)
const isRouteBlacklisted = computed(() => {
return disabledRoutes.some((disabledRoute) => route.path.includes(disabledRoute))
})
const shouldEnableIntercom = computed(() => !isRouteBlacklisted.value)
const bootIntercom = () => {
if (!shouldEnableIntercom.value || isInitialized.value || !activeAccount.value)
return
isInitialized.value = true
Intercom({
/* eslint-disable camelcase */
app_id: 'hoiaq4wn', // note: needs to be harcoded as this is statically served
user_id: activeAccount.value.accountInfo.userInfo.id || '',
name: activeAccount.value.accountInfo.userInfo.name || '',
email: activeAccount.value.accountInfo.userInfo.email || ''
})
window.Intercom = Intercom
}
const showIntercom = () => {
if (!isInitialized.value) return
show()
}
const hideIntercom = () => {
if (!isInitialized.value) return
hide()
}
const shutdownIntercom = () => {
if (!isInitialized.value) return
shutdown()
isInitialized.value = false
}
const trackIntercom = (event: string, metadata?: Record<string, unknown>) => {
if (!isInitialized.value) return
trackEvent(event, metadata)
}
const updateConnectorDetails = (
hostAppName: string,
hostAppVersion: string,
connectorVersion: string
) => {
update({
page_title: `CNX: (hostApp: ${hostAppName}:v${hostAppVersion}),(version: ${connectorVersion})`
})
}
// On route change, check if we need to shutodwn or boot Intercom
watch(route, () => {
if (isRouteBlacklisted.value) {
shutdownIntercom()
} else {
bootIntercom()
}
})
watch(activeAccount, (newValue) => {
if (newValue) {
if (!isInitialized.value) {
bootIntercom() // if active account changed and itercom is not initialised, do it
return // we do not need to update, as that's done by default in the init
}
update({
user_id: activeAccount.value.accountInfo.userInfo.id || '',
name: activeAccount.value.accountInfo.userInfo.name,
email: activeAccount.value.accountInfo.userInfo.email
})
} else {
if (isInitialized.value) {
shutdownIntercom()
}
}
})
return {
show: showIntercom,
hide: hideIntercom,
shutdown: shutdownIntercom,
track: trackIntercom,
updateConnectorDetails
}
}
export default defineNuxtPlugin(() => {
return {
provide: {
intercom: useIntercom()
}
}
})
+48
View File
@@ -0,0 +1,48 @@
<svg width="94" height="97" viewBox="0 0 94 97" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M93.9875 11.8396L13.3384 16.262V96.33L93.9875 91.9076V11.8396Z" fill="#047EFB"/>
<path d="M80.6801 -9.55722e-05L0.0146484 4.41339L13.3205 16.249L93.986 11.8356L80.6801 -9.55722e-05Z" fill="#7BBCFF"/>
<path d="M13.3328 16.2685L0.0107422 4.40778V84.4705L13.3328 96.3312V16.2685Z" fill="#313BCF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.4877 66.6814C35.7855 65.708 37.6268 65.9711 38.6002 67.2689C39.6031 68.6061 44.6573 73.4377 53.8752 73.4377C63.093 73.4377 68.1473 68.6061 69.1502 67.2689C70.1236 65.9711 71.9648 65.708 73.2627 66.6814C74.5605 67.6548 74.8236 69.4961 73.8502 70.7939C71.9156 73.3734 65.2198 79.3127 53.8752 79.3127C42.5305 79.3127 35.8348 73.3734 33.9002 70.7939C32.9268 69.4961 33.1898 67.6548 34.4877 66.6814Z" fill="url(#paint0_radial_5165_11144)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.4877 66.6814C35.7855 65.708 37.6268 65.9711 38.6002 67.2689C39.6031 68.6061 44.6573 73.4377 53.8752 73.4377C63.093 73.4377 68.1473 68.6061 69.1502 67.2689C70.1236 65.9711 71.9648 65.708 73.2627 66.6814C74.5605 67.6548 74.8236 69.4961 73.8502 70.7939C71.9156 73.3734 65.2198 79.3127 53.8752 79.3127C42.5305 79.3127 35.8348 73.3734 33.9002 70.7939C32.9268 69.4961 33.1898 67.6548 34.4877 66.6814Z" fill="url(#paint1_radial_5165_11144)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.4877 66.6814C35.7855 65.708 37.6268 65.9711 38.6002 67.2689C39.6031 68.6061 44.6573 73.4377 53.8752 73.4377C63.093 73.4377 68.1473 68.6061 69.1502 67.2689C70.1236 65.9711 71.9648 65.708 73.2627 66.6814C74.5605 67.6548 74.8236 69.4961 73.8502 70.7939C71.9156 73.3734 65.2198 79.3127 53.8752 79.3127C42.5305 79.3127 35.8348 73.3734 33.9002 70.7939C32.9268 69.4961 33.1898 67.6548 34.4877 66.6814Z" fill="url(#paint2_radial_5165_11144)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M39.6562 29.375C39.6562 28.5638 38.9987 27.9062 38.1875 27.9062C35.9131 27.9062 33.0689 28.7414 30.5953 30.3459C28.1034 31.9623 25.8589 34.4462 25.0126 37.8313C24.8159 38.6182 25.2943 39.4157 26.0813 39.6124C26.8682 39.8091 27.6657 39.3307 27.8624 38.5437C28.4849 36.0538 30.157 34.1315 32.1938 32.8103C34.2488 31.4774 36.5452 30.8438 38.1875 30.8438C38.9987 30.8438 39.6562 30.1862 39.6562 29.375Z" fill="url(#paint3_linear_5165_11144)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M77.6399 32.8386C79.3816 34.4921 80.4348 36.4815 80.8562 37.7457C81.1127 38.5153 81.9445 38.9311 82.7141 38.6746C83.4836 38.4181 83.8995 37.5863 83.643 36.8168C83.0852 35.1435 81.7885 32.7267 79.6624 30.7083C77.5114 28.6661 74.4714 27 70.4996 27C69.6884 27 69.0309 27.6576 69.0309 28.4688C69.0309 29.2799 69.6884 29.9375 70.4996 29.9375C73.5778 29.9375 75.9232 31.2089 77.6399 32.8386Z" fill="url(#paint4_linear_5165_11144)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M39.9309 45.9902C43.1513 45.9902 44.186 48.0385 44.3321 48.6262C44.7232 50.2007 46.3167 51.16 47.8911 50.7688C49.4656 50.3777 50.4249 48.7842 50.0337 47.2097C49.374 44.554 46.3799 40.1152 39.9309 40.1152C33.482 40.1152 30.4879 44.554 29.8281 47.2097C29.437 48.7842 30.3963 50.3777 31.9708 50.7688C33.5452 51.16 35.1387 50.2007 35.5298 48.6262C35.6758 48.0385 36.7106 45.9902 39.9309 45.9902Z" fill="url(#paint5_radial_5165_11144)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M68.1902 45.9902C71.4106 45.9902 72.4453 48.0385 72.5913 48.6262C72.9825 50.2007 74.5759 51.16 76.1504 50.7688C77.7249 50.3777 78.6842 48.7842 78.293 47.2097C77.6333 44.554 74.6391 40.1152 68.1902 40.1152C61.7413 40.1152 58.7472 44.554 58.0874 47.2097C57.6963 48.7842 58.6556 50.3777 60.23 50.7688C61.8045 51.16 63.398 50.2007 63.7891 48.6262C63.9351 48.0385 64.9699 45.9902 68.1902 45.9902Z" fill="url(#paint6_radial_5165_11144)"/>
<defs>
<radialGradient id="paint0_radial_5165_11144" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(53.8752 62.7893) rotate(90) scale(18.1758 28.851)">
<stop offset="0.464452" stop-color="#241A1A"/>
<stop offset="0.663489" stop-color="#554248"/>
<stop offset="1" stop-color="#4E2553"/>
<stop offset="1" stop-color="#4A274E"/>
</radialGradient>
<radialGradient id="paint1_radial_5165_11144" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(53.8752 70.4119) rotate(90) scale(26.0743 25.7783)">
<stop offset="0.483976" stop-color="#4F302E" stop-opacity="0"/>
<stop offset="1" stop-color="#4F302E"/>
</radialGradient>
<radialGradient id="paint2_radial_5165_11144" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(72.5442 67.9849) rotate(39.3327) scale(2.9658 2.13554)">
<stop stop-color="#5B4A50"/>
<stop offset="1" stop-color="#5B4A50" stop-opacity="0"/>
</radialGradient>
<linearGradient id="paint3_linear_5165_11144" x1="37.2083" y1="40.8312" x2="37.2083" y2="35.5437" gradientUnits="userSpaceOnUse">
<stop offset="0.0299084" stop-color="#524049"/>
<stop offset="1" stop-color="#4A2C42"/>
</linearGradient>
<linearGradient id="paint4_linear_5165_11144" x1="81.2704" y1="39.925" x2="81.2704" y2="34.6375" gradientUnits="userSpaceOnUse">
<stop offset="0.0299084" stop-color="#524049"/>
<stop offset="1" stop-color="#4A2C42"/>
</linearGradient>
<radialGradient id="paint5_radial_5165_11144" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(39.931 47.918) rotate(90) scale(9.73047 14.1637)">
<stop offset="0.337213" stop-color="#4D274B"/>
<stop offset="0.628228" stop-color="#514047"/>
<stop offset="1" stop-color="#4E2553"/>
<stop offset="1" stop-color="#4A342F"/>
</radialGradient>
<radialGradient id="paint6_radial_5165_11144" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(68.1902 47.918) rotate(90) scale(9.73047 14.1637)">
<stop offset="0.337213" stop-color="#4D274B"/>
<stop offset="0.628228" stop-color="#514047"/>
<stop offset="1" stop-color="#4E2553"/>
<stop offset="1" stop-color="#4A342F"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

+21
View File
@@ -0,0 +1,21 @@
<svg width="117" height="121" viewBox="0 0 117 121" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1246_23056)">
<path d="M115.033 15.0278L16.3086 20.4414V118.454L115.033 113.04V15.0278Z" fill="#136CFF"/>
<path d="M73.4005 93.725C73.1345 93.7395 72.8685 93.7124 72.6025 93.6412C68.0572 92.4058 63.2665 92.6668 58.7212 94.3975C57.368 94.9102 55.9088 94.2468 55.4672 92.9073C55.0256 91.5678 55.7694 90.0624 57.1278 89.5442C62.7113 87.4167 68.6124 87.0952 74.1959 88.6143C75.5518 88.9846 76.2956 90.4089 75.8565 91.7964C75.5027 92.9092 74.4903 93.6656 73.4005 93.725Z" fill="#0A2948"/>
<path d="M97.5176 53.6163C95.1855 60.8748 88.5173 66.3561 80.7464 66.7796C80.3745 66.7999 80 66.8073 79.623 66.8045C75.8086 66.7707 72.2834 65.5028 69.4219 63.2453C69.5148 64.5262 69.6181 65.8247 69.7731 67.227C70.1657 70.8115 72.0561 73.5506 75.2378 75.1464C77.3555 76.2105 79.8864 76.6805 82.5025 76.7042C86.9807 76.7433 91.7223 75.4743 95.0874 73.6568C98.6436 71.7354 100.955 68.4997 101.43 64.7769C101.903 61.075 100.516 57.2303 97.5176 53.6163Z" fill="white"/>
<path d="M74.3206 43.2249C70.971 44.8364 69.2923 49.477 69.2148 57.2442L69.3156 57.4153C71.6321 60.7159 75.444 62.8594 79.8731 62.8986C80.1675 62.9007 80.4619 62.8951 80.7537 62.8791C87.5562 62.5084 93.2766 57.1643 94.3199 50.3863C94.0229 50.1297 93.7492 49.8666 93.4341 49.6136C83.8399 41.9007 76.7327 42.0621 74.3206 43.2249Z" fill="#071F36"/>
<path d="M80.7486 66.7772C88.5196 66.3537 95.1877 60.8726 97.5198 53.6146C96.6056 52.5136 95.5286 51.437 94.3148 50.3887C93.2715 57.1661 87.5511 62.5098 80.7486 62.8805C80.4568 62.8964 80.1624 62.902 79.868 62.8999C75.4389 62.8607 71.627 60.7174 69.3105 57.4171L69.2097 57.246C69.1917 59.068 69.264 61.0696 69.4241 63.243C72.2856 65.5004 75.8108 66.7683 79.6252 66.802C80.0023 66.8074 80.3767 66.7974 80.7486 66.7772Z" fill="#01D1FD"/>
<path d="M74.48 54.2018C76.0589 54.1157 77.3389 52.7583 77.3389 51.1698C77.3389 49.5814 76.0589 48.3635 74.48 48.4495C72.9011 48.5356 71.6211 49.893 71.6211 51.4815C71.6211 53.0699 72.9011 54.2878 74.48 54.2018Z" fill="white"/>
<path d="M50.5822 68.4173C42.8113 68.8408 36.1431 64.0862 33.811 57.0819C30.8127 61.0227 29.4284 65.0185 29.8984 68.6664C30.3762 72.3372 32.685 75.3237 36.2412 76.8549C39.6063 78.3057 44.3453 79.058 48.826 78.5306C51.4422 78.2218 53.9731 77.4785 56.0908 76.1811C59.2699 74.2386 61.1604 71.2934 61.5555 67.666C61.7104 66.2494 61.8137 64.9448 61.9067 63.6564C59.0582 66.2095 55.5278 67.8672 51.7056 68.3171C51.3285 68.3662 50.9541 68.397 50.5822 68.4173Z" fill="white"/>
<path d="M50.5779 64.5182C50.8697 64.5023 51.1642 64.4759 51.4586 64.4416C55.8877 63.9197 59.6995 61.3607 62.0161 57.8077L62.1168 57.6255C62.0367 49.867 60.3607 45.4092 57.0111 44.1628C54.599 43.2628 47.4892 43.8763 37.8975 52.6322C37.5825 52.9196 37.3113 53.2124 37.0117 53.5015C38.0551 60.1683 43.7754 64.8889 50.5779 64.5182Z" fill="#071F36"/>
<path d="M62.1186 57.627L62.0179 57.8091C59.7014 61.3618 55.8895 63.9206 51.4604 64.4425C51.166 64.4767 50.8716 64.5032 50.5797 64.5191C43.7773 64.8898 38.0569 60.1695 37.0136 53.5059C35.7997 54.6865 34.7228 55.8804 33.8086 57.0811C36.1406 64.0849 42.8088 68.8392 50.5797 68.4158C50.9516 68.3955 51.3261 68.3621 51.7032 68.3182C55.5254 67.8683 59.0557 66.2108 61.9043 63.6577C62.0644 61.4643 62.1393 59.4521 62.1186 57.627Z" fill="#01D1FD"/>
<path d="M44.4386 55.8289C46.0139 55.6443 47.2122 54.2124 47.1115 52.633C47.0134 51.0535 45.655 49.922 44.0796 50.1066C42.5042 50.2912 41.3059 51.7231 41.4041 53.3026C41.5048 54.882 42.8632 56.0135 44.4386 55.8289Z" fill="white"/>
<path d="M98.7479 0.534875L0.00390625 5.9375L16.2919 20.4257L115.036 15.0231L98.7479 0.534875Z" fill="#6FA3FF"/>
<path d="M16.3038 20.4495L-0.00390625 5.93054V103.937L16.3038 118.456V20.4495Z" fill="#0357E1"/>
</g>
<defs>
<clipPath id="clip0_1246_23056">
<rect width="116.289" height="120" fill="white" transform="translate(0 0.536133)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+28 -2
View File
@@ -16,6 +16,7 @@ import { getMainDefinition } from '@apollo/client/utilities'
import { setContext } from '@apollo/client/link/context'
import { useHostAppStore } from '~/store/hostApp'
import { ToastNotificationType } from '@speckle/ui-components'
import { logToSeq } from '~/lib/logger/composables/useLogger'
export type DUIAccount = {
/** account info coming from the host app */
@@ -83,7 +84,13 @@ export const useAccountStore = defineStore('accountStore', () => {
if (!acc.client) continue
if (!acc.accountInfo.serverInfo.frontend2) continue
try {
await acc.client.query({ query: accountTestQuery })
await acc.client.query({
query: accountTestQuery,
context: {
url: acc.accountInfo.serverInfo.url
}
})
acc.isValid = true
} catch {
// TODO: properly dispose and kill this client. It's unclear how to do it.
@@ -100,7 +107,8 @@ export const useAccountStore = defineStore('accountStore', () => {
const refreshAccounts = async () => {
isLoading.value = true
const accs = await $accountBinding.getAccounts()
const accs = (await $accountBinding?.getAccounts()) || []
const newAccs: DUIAccount[] = []
for (const acc of accs) {
@@ -112,6 +120,24 @@ export const useAccountStore = defineStore('accountStore', () => {
// Handle apollo client errors as top level
const errorLink = onError((res: ErrorResponse) => {
logToSeq('Error', 'Apollo GraphQL Error (DUI3)', {
operationName: res.operation?.operationName ?? 'Unknown',
serverUrl: res.operation.getContext().url as string,
graphQLErrors: res.graphQLErrors?.map((err) => ({
message: err.message,
path: err.path,
code: err.extensions?.code,
locations: err.locations
})),
networkError: res.networkError
? {
message: res.networkError.message,
name: res.networkError.name,
stack: res.networkError.stack
}
: undefined
})
if (res.graphQLErrors) {
if (
res.graphQLErrors?.some(
+38 -4
View File
@@ -43,6 +43,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
const accountsStore = useAccountStore()
const { checkUpdate } = useUpdateConnector()
const isDistributedBySpeckle = ref<boolean>(true)
const latestAvailableVersion = ref<Version | null>(null)
const currentNotification = ref<Nullable<ToastNotification>>(null)
@@ -85,6 +86,10 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
hostAppError.value = error
}
const setIsDistributedBySpeckle = (val: boolean) => {
isDistributedBySpeckle.value = val
}
/**
* Model Card Operations
*/
@@ -191,6 +196,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
}
const sendSettings = ref<CardSetting[]>()
const receiveSettings = ref<CardSetting[]>()
/**
* Send filters
@@ -516,7 +522,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
app.$sendBinding?.on('setModelError', setModelError)
app.$receiveBinding?.on('setModelError', setModelError)
app.$baseBinding.on('setModelError', setModelError)
app.$baseBinding?.on('setModelError', setModelError)
/**
* Used internally in this store store only for initialisation.
@@ -530,7 +536,9 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
const getConnectorVersion = async () => {
connectorVersion.value = await app.$baseBinding.getConnectorVersion()
// Checks whether new version available for the connector or not and throws a toast notification if any.
await checkUpdate()
if (app.$isRunningOnConnector) {
await checkUpdate()
}
}
/**
@@ -615,6 +623,10 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
sendSettings.value = await app.$sendBinding.getSendSettings()
}
const getReceiveSettings = async () => {
receiveSettings.value = await app.$receiveBinding.getReceiveSettings()
}
const tryToUpgradeModelCardSettings = (
settings: CardSetting[],
typeDiscriminator: string
@@ -673,11 +685,11 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
})
}
app.$baseBinding.on(
app.$baseBinding?.on(
'documentChanged',
() =>
setTimeout(async () => {
void trackEvent('DUI3 Action', { name: 'Document changed' })
// void trackEvent('DUI3 Action', { name: 'Document changed' }) // noisy
void refreshDocumentInfo()
await refreshDocumentModelStore() // need to awaited since upgrading the card settings need documentModelStore in place
void refreshSendFilters()
@@ -693,7 +705,26 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
await refreshDocumentModelStore()
await refreshSendFilters()
await getSendSettings()
await getReceiveSettings()
tryToUpgradeModelCardSettings(sendSettings.value || [], 'SenderModelCard')
// Intercom shenanningans below
// Do not poke intercom in ancient revit version
if (
(hostAppName.value?.toLowerCase() === 'revit' &&
hostAppVersion.value?.includes('2022')) ||
!isDistributedBySpeckle.value
)
return
// guards against intercom being sometimes slower to init
setTimeout(() => {
app.$intercom.updateConnectorDetails(
hostAppName.value as string,
hostAppVersion.value as string,
connectorVersion.value as string
)
}, 1000)
}
initializeApp()
@@ -709,6 +740,7 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
models,
sendFilters,
sendSettings,
receiveSettings,
selectionFilter,
everythingFilter,
currentNotification,
@@ -717,6 +749,8 @@ export const useHostAppStore = defineStore('hostAppStore', () => {
availableViews,
navisworksAvailableSavedSets,
availableSelectSendFilters,
isDistributedBySpeckle,
setIsDistributedBySpeckle,
setNotification,
setModelError,
setLatestAvailableVersion,
+75
View File
@@ -0,0 +1,75 @@
import { defineStore } from 'pinia'
import type { Category } from '~/lib/bindings/definitions/IRevitMapperBinding'
import { REVIT_CATEGORIES } from '~/lib/mapper/revit-categories'
export const useRevitMapper = defineStore('revitMapper', () => {
const app = useNuxtApp()
const { $revitMapperBinding } = app
const currentCategories = ref<string[]>([])
const selectedCategory = ref<Category | undefined>()
const categoryOptions = REVIT_CATEGORIES
const categoryStatus = computed(() => {
if (currentCategories.value.length === 0) {
return undefined
}
if (currentCategories.value.length === 1) {
const category = categoryOptions.find(
(cat) => cat.value === currentCategories.value[0]
)
return category ? { label: category.label, value: category.value } : undefined
}
return {
label: 'Multiple categories',
value: 'multiple',
isMultiple: true
}
})
const updateFromTargets = async (
targetIds: string[],
isLayerMode: boolean
): Promise<void> => {
if (!targetIds.length || !$revitMapperBinding) {
clear()
return
}
try {
// Call connector method based on mode
const categories = isLayerMode
? await $revitMapperBinding.getCategoryMappingsForLayers(targetIds)
: await $revitMapperBinding.getCategoryMappingsForObjects(targetIds)
currentCategories.value = categories
// Update dropdown selection based on categories found
if (categories.length === 1) {
selectedCategory.value = categoryOptions.find(
(cat) => cat.value === categories[0]
)
} else {
// Multiple or no categories - clear dropdown selection
selectedCategory.value = undefined
}
} catch (error) {
console.error('Failed to get category mappings:', error)
clear()
}
}
const clear = () => {
currentCategories.value = []
selectedCategory.value = undefined
}
return {
currentCategories,
selectedCategory,
categoryStatus,
updateFromTargets,
clear
}
})
+477 -49
View File
@@ -1750,6 +1750,13 @@ __metadata:
languageName: node
linkType: hard
"@fastify/accept-negotiator@npm:^1.1.0":
version: 1.1.0
resolution: "@fastify/accept-negotiator@npm:1.1.0"
checksum: 10c0/1cb9a298c992b812869158ddc6093557a877b30e5f77618a7afea985a0667c50bc7113593bf0f7f9dc9b82b94c16e8ab127a0afc3efde6677fd645539f6d08e5
languageName: node
linkType: hard
"@fastify/busboy@npm:^3.1.1":
version: 3.1.1
resolution: "@fastify/busboy@npm:3.1.1"
@@ -2437,6 +2444,13 @@ __metadata:
languageName: node
linkType: hard
"@intercom/messenger-js-sdk@npm:^0.0.14":
version: 0.0.14
resolution: "@intercom/messenger-js-sdk@npm:0.0.14"
checksum: 10c0/b5873e85938380534c3887a0fc8d2c91f4bbd819e9b25d38e07e6985c3562905e694e6f9399d778ef744b712739cad1263a4a5655a5ee213f342a1929d44d77f
languageName: node
linkType: hard
"@ioredis/commands@npm:^1.1.1":
version: 1.2.0
resolution: "@ioredis/commands@npm:1.2.0"
@@ -3071,6 +3085,28 @@ __metadata:
languageName: node
linkType: hard
"@nuxt/image@npm:^1.10.0":
version: 1.10.0
resolution: "@nuxt/image@npm:1.10.0"
dependencies:
"@nuxt/kit": "npm:^3.16.0"
consola: "npm:^3.4.2"
defu: "npm:^6.1.4"
h3: "npm:^1.15.1"
image-meta: "npm:^0.2.1"
ipx: "npm:^2.1.0"
knitwork: "npm:^1.2.0"
ohash: "npm:^2.0.11"
pathe: "npm:^2.0.3"
std-env: "npm:^3.8.1"
ufo: "npm:^1.5.4"
dependenciesMeta:
ipx:
optional: true
checksum: 10c0/85ca9f301e8745ba28e740b333d8c26cf7817e1e86fc7b12fe41601f300afff682a50b17371b4bd52579c274f5d61d56f748e71f585e93161c68d2363081f2f7
languageName: node
linkType: hard
"@nuxt/kit@npm:3.17.3, @nuxt/kit@npm:^3.15.4, @nuxt/kit@npm:^3.16.0, @nuxt/kit@npm:^3.16.2, @nuxt/kit@npm:^3.17.2, @nuxt/kit@npm:^3.17.3, @nuxt/kit@npm:^3.5.0":
version: 3.17.3
resolution: "@nuxt/kit@npm:3.17.3"
@@ -3818,35 +3854,35 @@ __metadata:
languageName: node
linkType: hard
"@speckle/objectloader@npm:^2.24.0":
version: 2.24.0
resolution: "@speckle/objectloader@npm:2.24.0"
"@speckle/objectloader@npm:^2.25.0":
version: 2.25.0
resolution: "@speckle/objectloader@npm:2.25.0"
dependencies:
"@babel/core": "npm:^7.17.9"
"@speckle/shared": "npm:^2.24.0"
"@speckle/shared": "npm:^2.25.0"
core-js: "npm:^3.21.1"
lodash: "npm:^4.17.21"
lodash-es: "npm:^4.17.21"
regenerator-runtime: "npm:^0.13.7"
checksum: 10c0/d4352caa1162b07ac71575dfbc7080e811428482203ce8e8b4ab0c23826cd67824470dd52a33a42c81032d42bbd825277dbba2b920ab172018d208cede8dd805
checksum: 10c0/e1c3021e74a140d790ee6645ddb78254f70bee4a6750de1a77c9216f51ccb20d4dd43d544baaf8a458ba3a0f9e0dfe1b2703d9d4fd43e524985443e8566aa5cc
languageName: node
linkType: hard
"@speckle/objectsender@npm:^2.24.0":
version: 2.24.0
resolution: "@speckle/objectsender@npm:2.24.0"
"@speckle/objectsender@npm:^2.25.0":
version: 2.25.0
resolution: "@speckle/objectsender@npm:2.25.0"
dependencies:
"@speckle/shared": "npm:^2.24.0"
"@speckle/shared": "npm:^2.25.0"
lodash: "npm:^4.17.21"
lodash-es: "npm:^4.17.21"
reflect-metadata: "npm:^0.2.2"
checksum: 10c0/5d463e696858cf5b1baaf327a09181bbcbc64f852b403fecc87b0565ddb055909779e5780ca4f818a44a694b7c74de493cd26ec728531b1dfb7b31a0044730c0
checksum: 10c0/8956f049847037e33c824053adeefbd119b978343d29764f316dc9af6f5c13fa8bb35d3025ce5a876a25d408d5d84c14ff17fe56c70f984d6ec1fe1ab0ea384e
languageName: node
linkType: hard
"@speckle/shared@npm:^2.24.0":
version: 2.24.0
resolution: "@speckle/shared@npm:2.24.0"
"@speckle/shared@npm:^2.25.0":
version: 2.25.0
resolution: "@speckle/shared@npm:2.25.0"
dependencies:
dayjs: "npm:^1.11.13"
lodash: "npm:^4.17.21"
@@ -3856,6 +3892,7 @@ __metadata:
type-fest: "npm:^3.11.1"
peerDependencies:
"@tiptap/core": ^2.0.0-beta.176
bull: "*"
knex: "*"
mixpanel: ^0.17.0
pino: ^8.7.0
@@ -3864,42 +3901,42 @@ __metadata:
ua-parser-js: ^1.0.38
znv: ^0.4.0
zod: ^3.22.4
checksum: 10c0/5e9be7e83a74a6de2094999dfbe3f41356790886381e44648250a4bc883764d47799157e526a89285e40d029332d5487b3c013d91fee084b2fb1b74537e831e8
checksum: 10c0/c6fac64887926b23ab88502c8a97ec0cbc67d59094daacf22c838902fd3568a614fab64dff8542871961084276368976fc586c75c0463843ac1f68ffba3be1b8
languageName: node
linkType: hard
"@speckle/tailwind-theme@npm:2.24.1-alpha.0":
version: 2.24.1-alpha.0
resolution: "@speckle/tailwind-theme@npm:2.24.1-alpha.0"
"@speckle/tailwind-theme@npm:2.25.0":
version: 2.25.0
resolution: "@speckle/tailwind-theme@npm:2.25.0"
dependencies:
"@tailwindcss/forms": "npm:^0.5.3"
peerDependencies:
postcss: ^8.4.18
postcss-nesting: ^10.2.0
tailwindcss: ^3.3.2
checksum: 10c0/ece3ecfa80162f0a4dc0f6bcb28839331e2c4208bf922c478d00c859248f6e77b8267e06f4c505f98fd625682006b22f8c5f63c2749c7b4efa277dcab75c2d0f
checksum: 10c0/ba647af26d446b1d09fd0f82de80e9691825264ebb0e2cbd4f4049e44912c941dc10b7d603a1e41864e16f1fd76fe52df1aeb2102c001d0dc7b615725288f1ac
languageName: node
linkType: hard
"@speckle/ui-components-nuxt@npm:^2.24.0":
version: 2.24.0
resolution: "@speckle/ui-components-nuxt@npm:2.24.0"
"@speckle/ui-components-nuxt@npm:^2.25.0":
version: 2.25.0
resolution: "@speckle/ui-components-nuxt@npm:2.25.0"
dependencies:
lodash-es: "npm:^4.0.0"
peerDependencies:
"@nuxt/kit": ^3.2.0
"@speckle/ui-components": "*"
checksum: 10c0/dbd89f3511a586c63104d787220e24aeeb8fbf40c3c11a21c107633eecf0a6fddf2730a23070d4ed4f6822863cce008b421b228498a4ad764fc81c3b4cd541c1
checksum: 10c0/73ab79982176abff0de491ff7e33d25266ffd824a8212bea482396b3f4b16914adb46a28083e2cb017d26659ef0aacdc826a7766f2d5afc56122d2b91d098a40
languageName: node
linkType: hard
"@speckle/ui-components@npm:^2.24.0":
version: 2.24.0
resolution: "@speckle/ui-components@npm:2.24.0"
"@speckle/ui-components@npm:^2.25.0":
version: 2.25.0
resolution: "@speckle/ui-components@npm:2.25.0"
dependencies:
"@headlessui/vue": "npm:^1.7.18"
"@heroicons/vue": "npm:^2.0.12"
"@speckle/shared": "npm:^2.24.0"
"@speckle/shared": "npm:^2.25.0"
"@storybook/test": "npm:^8.1.10"
"@vueuse/core": "npm:^9.13.0"
lodash: "npm:^4.0.0"
@@ -3911,7 +3948,7 @@ __metadata:
peerDependencies:
vee-validate: ^4.7.0
vue: ^3.3.0
checksum: 10c0/e1632132cb6635423e7aec1c8c9b671012db98d3b71f21a080f1a6d7b1b86013ad901593ab67d915c2f817168d8bf9c66df7c837eea45e6f7d90b3b5c0032d39
checksum: 10c0/be321d8fb492e62e6e38d42d3d9622c4091cc0b2bcc0b90ae15954bd31359a1680ef7963a2f026ece0fd327663e0fba614023585e8fa987d19064f73ad9fca8b
languageName: node
linkType: hard
@@ -5639,6 +5676,62 @@ __metadata:
languageName: node
linkType: hard
"bare-events@npm:^2.5.4":
version: 2.6.0
resolution: "bare-events@npm:2.6.0"
checksum: 10c0/9bdd727a8df81aae14746c9bb860102f6c5aafc028f17e3a8620f40dc8bfe816ed46b0c50cb3200d1a1099f8028da27110cf711267b296767f37d3e4c6a9d4a6
languageName: node
linkType: hard
"bare-fs@npm:^4.0.1":
version: 4.1.6
resolution: "bare-fs@npm:4.1.6"
dependencies:
bare-events: "npm:^2.5.4"
bare-path: "npm:^3.0.0"
bare-stream: "npm:^2.6.4"
peerDependencies:
bare-buffer: "*"
peerDependenciesMeta:
bare-buffer:
optional: true
checksum: 10c0/a02ef4a76b2e58a0b142b5f5d1b629a96ddf62abb2f70801361f8f7f85edf157d777707bff3e62e9e67c75445667b7047cf8a99de39c1a60032d1818850ff0ea
languageName: node
linkType: hard
"bare-os@npm:^3.0.1":
version: 3.6.1
resolution: "bare-os@npm:3.6.1"
checksum: 10c0/13064789b3d0d3051d6a89424e6d861c08be101798d69faa78821cffb428b36d1fd4e17c824d5a4939bcd96dbff42c11921494139c8e53c3e520bc0e3f83aeee
languageName: node
linkType: hard
"bare-path@npm:^3.0.0":
version: 3.0.0
resolution: "bare-path@npm:3.0.0"
dependencies:
bare-os: "npm:^3.0.1"
checksum: 10c0/56a3ca82a9f808f4976cb1188640ac206546ce0ddff582afafc7bd2a6a5b31c3bd16422653aec656eeada2830cfbaa433c6cbf6d6b4d9eba033d5e06d60d9a68
languageName: node
linkType: hard
"bare-stream@npm:^2.6.4":
version: 2.6.5
resolution: "bare-stream@npm:2.6.5"
dependencies:
streamx: "npm:^2.21.0"
peerDependencies:
bare-buffer: "*"
bare-events: "*"
peerDependenciesMeta:
bare-buffer:
optional: true
bare-events:
optional: true
checksum: 10c0/1242286f8f3147e9fd353cdaa9cf53226a807ac0dde8177c13f1463aa4cd1f88e07407c883a1b322b901e9af2d1cd30aacd873529031132c384622972e0419df
languageName: node
linkType: hard
"base64-js@npm:^1.3.1":
version: 1.5.1
resolution: "base64-js@npm:1.5.1"
@@ -6152,6 +6245,13 @@ __metadata:
languageName: node
linkType: hard
"chownr@npm:^1.1.1":
version: 1.1.4
resolution: "chownr@npm:1.1.4"
checksum: 10c0/ed57952a84cc0c802af900cf7136de643d3aba2eecb59d29344bc2f3f9bf703a301b9d84cdc71f82c3ffc9ccde831b0d92f5b45f91727d6c9da62f23aef9d9db
languageName: node
linkType: hard
"chownr@npm:^2.0.0":
version: 2.0.0
resolution: "chownr@npm:2.0.0"
@@ -6322,7 +6422,7 @@ __metadata:
languageName: node
linkType: hard
"color-string@npm:^1.6.0":
"color-string@npm:^1.6.0, color-string@npm:^1.9.0":
version: 1.9.1
resolution: "color-string@npm:1.9.1"
dependencies:
@@ -6351,6 +6451,16 @@ __metadata:
languageName: node
linkType: hard
"color@npm:^4.2.3":
version: 4.2.3
resolution: "color@npm:4.2.3"
dependencies:
color-convert: "npm:^2.0.1"
color-string: "npm:^1.9.0"
checksum: 10c0/7fbe7cfb811054c808349de19fb380252e5e34e61d7d168ec3353e9e9aacb1802674bddc657682e4e9730c2786592a4de6f8283e7e0d3870b829bb0b7b2f6118
languageName: node
linkType: hard
"colord@npm:^2.9.3":
version: 2.9.3
resolution: "colord@npm:2.9.3"
@@ -6382,7 +6492,7 @@ __metadata:
languageName: node
linkType: hard
"commander@npm:^2.20.0":
"commander@npm:^2.20.0, commander@npm:^2.20.3":
version: 2.20.3
resolution: "commander@npm:2.20.3"
checksum: 10c0/74c781a5248c2402a0a3e966a0a2bba3c054aad144f5c023364be83265e796b20565aa9feff624132ff629aa64e16999fa40a743c10c12f7c61e96a794b99288
@@ -6898,6 +7008,13 @@ __metadata:
languageName: node
linkType: hard
"cssfilter@npm:0.0.10":
version: 0.0.10
resolution: "cssfilter@npm:0.0.10"
checksum: 10c0/478a227a616fb6e9bb338eb95f690df141b86231ec737cbea574484f31a09a51db894b4921afc4987459dae08d584355fd689ff2a7a7c7a74de4bb4c072ce553
languageName: node
linkType: hard
"cssnano-preset-default@npm:^7.0.7":
version: 7.0.7
resolution: "cssnano-preset-default@npm:7.0.7"
@@ -7118,6 +7235,15 @@ __metadata:
languageName: node
linkType: hard
"decompress-response@npm:^6.0.0":
version: 6.0.0
resolution: "decompress-response@npm:6.0.0"
dependencies:
mimic-response: "npm:^3.1.0"
checksum: 10c0/bd89d23141b96d80577e70c54fb226b2f40e74a6817652b80a116d7befb8758261ad073a8895648a29cc0a5947021ab66705cb542fa9c143c82022b27c5b175e
languageName: node
linkType: hard
"deep-eql@npm:^5.0.1":
version: 5.0.2
resolution: "deep-eql@npm:5.0.2"
@@ -7132,6 +7258,13 @@ __metadata:
languageName: node
linkType: hard
"deep-extend@npm:^0.6.0":
version: 0.6.0
resolution: "deep-extend@npm:0.6.0"
checksum: 10c0/1c6b0abcdb901e13a44c7d699116d3d4279fdb261983122a3783e7273844d5f2537dc2e1c454a23fcf645917f93fbf8d07101c1d03c015a87faa662755212566
languageName: node
linkType: hard
"deep-is@npm:^0.1.3":
version: 0.1.4
resolution: "deep-is@npm:0.1.4"
@@ -7264,7 +7397,7 @@ __metadata:
languageName: node
linkType: hard
"destr@npm:^2.0.3, destr@npm:^2.0.5":
"destr@npm:^2.0.2, destr@npm:^2.0.3, destr@npm:^2.0.5":
version: 2.0.5
resolution: "destr@npm:2.0.5"
checksum: 10c0/efabffe7312a45ad90d79975376be958c50069f1156b94c181199763a7f971e113bd92227c26b94a169c71ca7dbc13583b7e96e5164743969fc79e1ff153e646
@@ -7294,7 +7427,7 @@ __metadata:
languageName: node
linkType: hard
"detect-libc@npm:^2.0.0":
"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.2":
version: 2.0.4
resolution: "detect-libc@npm:2.0.4"
checksum: 10c0/c15541f836eba4b1f521e4eecc28eefefdbc10a94d3b8cb4c507689f332cc111babb95deda66f2de050b22122113189986d5190be97d51b5a2b23b938415e67c
@@ -8345,6 +8478,13 @@ __metadata:
languageName: node
linkType: hard
"expand-template@npm:^2.0.3":
version: 2.0.3
resolution: "expand-template@npm:2.0.3"
checksum: 10c0/1c9e7afe9acadf9d373301d27f6a47b34e89b3391b1ef38b7471d381812537ef2457e620ae7f819d2642ce9c43b189b3583813ec395e2938319abe356a9b2f51
languageName: node
linkType: hard
"exponential-backoff@npm:^3.1.1":
version: 3.1.2
resolution: "exponential-backoff@npm:3.1.2"
@@ -9068,6 +9208,13 @@ __metadata:
languageName: node
linkType: hard
"github-from-package@npm:0.0.0":
version: 0.0.0
resolution: "github-from-package@npm:0.0.0"
checksum: 10c0/737ee3f52d0a27e26332cde85b533c21fcdc0b09fb716c3f8e522cfaa9c600d4a631dec9fcde179ec9d47cca89017b7848ed4d6ae6b6b78f936c06825b1fcc12
languageName: node
linkType: hard
"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2":
version: 5.1.2
resolution: "glob-parent@npm:5.1.2"
@@ -9386,7 +9533,7 @@ __metadata:
languageName: node
linkType: hard
"h3@npm:^1.12.0, h3@npm:^1.15.1, h3@npm:^1.15.2, h3@npm:^1.15.3":
"h3@npm:^1.10.0, h3@npm:^1.12.0, h3@npm:^1.15.1, h3@npm:^1.15.2, h3@npm:^1.15.3":
version: 1.15.3
resolution: "h3@npm:1.15.3"
dependencies:
@@ -9673,7 +9820,7 @@ __metadata:
languageName: node
linkType: hard
"image-meta@npm:^0.2.1":
"image-meta@npm:^0.2.0, image-meta@npm:^0.2.1":
version: 0.2.1
resolution: "image-meta@npm:0.2.1"
checksum: 10c0/c8a100b666663ad53ffe95c22647e79802d6eac6dfa3e1a00e4cf034129b4a13e7861b5c5a7cee46604a45a9e0c8ed91e73233c7bf9f48fbece5f0300ef6912c
@@ -9776,7 +9923,7 @@ __metadata:
languageName: node
linkType: hard
"ini@npm:^1.3.4, ini@npm:^1.3.5":
"ini@npm:^1.3.4, ini@npm:^1.3.5, ini@npm:~1.3.0":
version: 1.3.8
resolution: "ini@npm:1.3.8"
checksum: 10c0/ec93838d2328b619532e4f1ff05df7909760b6f66d9c9e2ded11e5c1897d6f2f9980c54dd638f88654b00919ce31e827040631eab0a3969e4d1abefa0719516a
@@ -9856,6 +10003,32 @@ __metadata:
languageName: node
linkType: hard
"ipx@npm:^2.1.0":
version: 2.1.0
resolution: "ipx@npm:2.1.0"
dependencies:
"@fastify/accept-negotiator": "npm:^1.1.0"
citty: "npm:^0.1.5"
consola: "npm:^3.2.3"
defu: "npm:^6.1.4"
destr: "npm:^2.0.2"
etag: "npm:^1.8.1"
h3: "npm:^1.10.0"
image-meta: "npm:^0.2.0"
listhen: "npm:^1.5.6"
ofetch: "npm:^1.3.3"
pathe: "npm:^1.1.2"
sharp: "npm:^0.32.6"
svgo: "npm:^3.2.0"
ufo: "npm:^1.3.2"
unstorage: "npm:^1.10.1"
xss: "npm:^1.0.14"
bin:
ipx: bin/ipx.mjs
checksum: 10c0/04a4f968c9f7bdf9838f3381d85892c55042e9d68a59338d68659ffcb1a779a345a4473a4ef68eede3255954b64bacbd66b778e60952131fc928456286efa444
languageName: node
linkType: hard
"iron-webcrypto@npm:^1.2.1":
version: 1.2.1
resolution: "iron-webcrypto@npm:1.2.1"
@@ -10654,7 +10827,7 @@ __metadata:
languageName: node
linkType: hard
"listhen@npm:^1.9.0":
"listhen@npm:^1.5.6, listhen@npm:^1.9.0":
version: 1.9.0
resolution: "listhen@npm:1.9.0"
dependencies:
@@ -11238,6 +11411,13 @@ __metadata:
languageName: node
linkType: hard
"mimic-response@npm:^3.1.0":
version: 3.1.0
resolution: "mimic-response@npm:3.1.0"
checksum: 10c0/0d6f07ce6e03e9e4445bee655202153bdb8a98d67ee8dc965ac140900d7a2688343e6b4c9a72cfc9ef2f7944dfd76eef4ab2482eb7b293a68b84916bac735362
languageName: node
linkType: hard
"min-indent@npm:^1.0.0, min-indent@npm:^1.0.1":
version: 1.0.1
resolution: "min-indent@npm:1.0.1"
@@ -11290,7 +11470,7 @@ __metadata:
languageName: node
linkType: hard
"minimist@npm:^1.2.5":
"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5":
version: 1.2.8
resolution: "minimist@npm:1.2.8"
checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6
@@ -11397,6 +11577,13 @@ __metadata:
languageName: node
linkType: hard
"mkdirp-classic@npm:^0.5.2, mkdirp-classic@npm:^0.5.3":
version: 0.5.3
resolution: "mkdirp-classic@npm:0.5.3"
checksum: 10c0/95371d831d196960ddc3833cc6907e6b8f67ac5501a6582f47dfae5eb0f092e9f8ce88e0d83afcae95d6e2b61a01741ba03714eeafb6f7a6e9dcc158ac85b168
languageName: node
linkType: hard
"mkdirp@npm:^1.0.3":
version: 1.0.4
resolution: "mkdirp@npm:1.0.4"
@@ -11524,6 +11711,13 @@ __metadata:
languageName: node
linkType: hard
"napi-build-utils@npm:^2.0.0":
version: 2.0.0
resolution: "napi-build-utils@npm:2.0.0"
checksum: 10c0/5833aaeb5cc5c173da47a102efa4680a95842c13e0d9cc70428bd3ee8d96bb2172f8860d2811799b5daa5cbeda779933601492a2028a6a5351c6d0fcf6de83db
languageName: node
linkType: hard
"napi-postinstall@npm:^0.2.2":
version: 0.2.4
resolution: "napi-postinstall@npm:0.2.4"
@@ -11679,6 +11873,24 @@ __metadata:
languageName: node
linkType: hard
"node-abi@npm:^3.3.0":
version: 3.75.0
resolution: "node-abi@npm:3.75.0"
dependencies:
semver: "npm:^7.3.5"
checksum: 10c0/c43a2409407df3737848fd96202b0a49e15039994aecce963969e9ef7342a8fc544aba94e0bfd8155fb9de5f5fe9a4b6ccad8bf509e7c46caf096fc4491d63f2
languageName: node
linkType: hard
"node-addon-api@npm:^6.1.0":
version: 6.1.0
resolution: "node-addon-api@npm:6.1.0"
dependencies:
node-gyp: "npm:latest"
checksum: 10c0/d2699c4ad15740fd31482a3b6fca789af7723ab9d393adc6ac45250faaee72edad8f0b10b2b9d087df0de93f1bdc16d97afdd179b26b9ebc9ed68b569faa4bac
languageName: node
linkType: hard
"node-addon-api@npm:^7.0.0":
version: 7.1.1
resolution: "node-addon-api@npm:7.1.1"
@@ -12039,7 +12251,7 @@ __metadata:
languageName: node
linkType: hard
"ofetch@npm:^1.4.1":
"ofetch@npm:^1.3.3, ofetch@npm:^1.4.1":
version: 1.4.1
resolution: "ofetch@npm:1.4.1"
dependencies:
@@ -13227,6 +13439,28 @@ __metadata:
languageName: node
linkType: hard
"prebuild-install@npm:^7.1.1":
version: 7.1.3
resolution: "prebuild-install@npm:7.1.3"
dependencies:
detect-libc: "npm:^2.0.0"
expand-template: "npm:^2.0.3"
github-from-package: "npm:0.0.0"
minimist: "npm:^1.2.3"
mkdirp-classic: "npm:^0.5.3"
napi-build-utils: "npm:^2.0.0"
node-abi: "npm:^3.3.0"
pump: "npm:^3.0.0"
rc: "npm:^1.2.7"
simple-get: "npm:^4.0.0"
tar-fs: "npm:^2.0.0"
tunnel-agent: "npm:^0.6.0"
bin:
prebuild-install: bin.js
checksum: 10c0/25919a42b52734606a4036ab492d37cfe8b601273d8dfb1fa3c84e141a0a475e7bad3ab848c741d2f810cef892fcf6059b8c7fe5b29f98d30e0c29ad009bedff
languageName: node
linkType: hard
"precinct@npm:^11.0.0":
version: 11.0.5
resolution: "precinct@npm:11.0.5"
@@ -13495,6 +13729,20 @@ __metadata:
languageName: node
linkType: hard
"rc@npm:^1.2.7":
version: 1.2.8
resolution: "rc@npm:1.2.8"
dependencies:
deep-extend: "npm:^0.6.0"
ini: "npm:~1.3.0"
minimist: "npm:^1.2.0"
strip-json-comments: "npm:~2.0.1"
bin:
rc: ./cli.js
checksum: 10c0/24a07653150f0d9ac7168e52943cc3cb4b7a22c0e43c7dff3219977c2fdca5a2760a304a029c20811a0e79d351f57d46c9bde216193a0f73978496afc2b85b15
languageName: node
linkType: hard
"react-is@npm:^16.13.1, react-is@npm:^16.7.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
@@ -14093,7 +14341,7 @@ __metadata:
languageName: node
linkType: hard
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0":
"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0":
version: 5.2.1
resolution: "safe-buffer@npm:5.2.1"
checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3
@@ -14173,7 +14421,7 @@ __metadata:
languageName: node
linkType: hard
"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.6, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.6.0, semver@npm:^7.6.3, semver@npm:^7.7.1, semver@npm:^7.7.2":
"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.6, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.3, semver@npm:^7.7.1, semver@npm:^7.7.2":
version: 7.7.2
resolution: "semver@npm:7.7.2"
bin:
@@ -14312,6 +14560,23 @@ __metadata:
languageName: node
linkType: hard
"sharp@npm:^0.32.6":
version: 0.32.6
resolution: "sharp@npm:0.32.6"
dependencies:
color: "npm:^4.2.3"
detect-libc: "npm:^2.0.2"
node-addon-api: "npm:^6.1.0"
node-gyp: "npm:latest"
prebuild-install: "npm:^7.1.1"
semver: "npm:^7.5.4"
simple-get: "npm:^4.0.1"
tar-fs: "npm:^3.0.4"
tunnel-agent: "npm:^0.6.0"
checksum: 10c0/f6a756fec5051ef2f9341e0543cde1da4e822982dd5398010baad92e2262bd177e08b753eb19b2fbee30f2fcb0e8756f24088fafc48293a364e9a8f8dc65a300
languageName: node
linkType: hard
"shebang-command@npm:^2.0.0":
version: 2.0.0
resolution: "shebang-command@npm:2.0.0"
@@ -14404,6 +14669,24 @@ __metadata:
languageName: node
linkType: hard
"simple-concat@npm:^1.0.0":
version: 1.0.1
resolution: "simple-concat@npm:1.0.1"
checksum: 10c0/62f7508e674414008910b5397c1811941d457dfa0db4fd5aa7fa0409eb02c3609608dfcd7508cace75b3a0bf67a2a77990711e32cd213d2c76f4fd12ee86d776
languageName: node
linkType: hard
"simple-get@npm:^4.0.0, simple-get@npm:^4.0.1":
version: 4.0.1
resolution: "simple-get@npm:4.0.1"
dependencies:
decompress-response: "npm:^6.0.0"
once: "npm:^1.3.1"
simple-concat: "npm:^1.0.0"
checksum: 10c0/b0649a581dbca741babb960423248899203165769747142033479a7dc5e77d7b0fced0253c731cd57cf21e31e4d77c9157c3069f4448d558ebc96cf9e1eebcf0
languageName: node
linkType: hard
"simple-git@npm:^3.27.0":
version: 3.27.0
resolution: "simple-git@npm:3.27.0"
@@ -14668,19 +14951,21 @@ __metadata:
"@graphql-codegen/client-preset": "npm:^4.3.0"
"@headlessui/vue": "npm:^1.7.13"
"@heroicons/vue": "npm:^2.0.12"
"@intercom/messenger-js-sdk": "npm:^0.0.14"
"@jsonforms/core": "npm:3.1.0"
"@jsonforms/vue": "npm:3.1.0"
"@jsonforms/vue-vanilla": "npm:3.1.0"
"@nuxt/eslint": "npm:^1.3.1"
"@nuxt/image": "npm:^1.10.0"
"@nuxtjs/tailwindcss": "npm:^6.14.0"
"@parcel/watcher": "npm:^2.5.1"
"@pinia/nuxt": "npm:^0.4.11"
"@speckle/objectloader": "npm:^2.24.0"
"@speckle/objectsender": "npm:^2.24.0"
"@speckle/shared": "npm:^2.24.0"
"@speckle/tailwind-theme": "npm:2.24.1-alpha.0"
"@speckle/ui-components": "npm:^2.24.0"
"@speckle/ui-components-nuxt": "npm:^2.24.0"
"@speckle/objectloader": "npm:^2.25.0"
"@speckle/objectsender": "npm:^2.25.0"
"@speckle/shared": "npm:^2.25.0"
"@speckle/tailwind-theme": "npm:2.25.0"
"@speckle/ui-components": "npm:^2.25.0"
"@speckle/ui-components-nuxt": "npm:^2.25.0"
"@types/apollo-upload-client": "npm:^17.0.1"
"@types/eslint": "npm:^9.6.1"
"@types/lodash-es": "npm:^4.17.6"
@@ -14807,6 +15092,20 @@ __metadata:
languageName: node
linkType: hard
"streamx@npm:^2.21.0":
version: 2.22.1
resolution: "streamx@npm:2.22.1"
dependencies:
bare-events: "npm:^2.2.0"
fast-fifo: "npm:^1.3.2"
text-decoder: "npm:^1.1.0"
dependenciesMeta:
bare-events:
optional: true
checksum: 10c0/b5e489cca78ff23b910e7d58c3e0059e692f93ec401a5974689f2c50c33c9d94f64246a305566ad52cdb818ee583e02e4257b9066fd654cb9f576a9692fdb976
languageName: node
linkType: hard
"string-env-interpolation@npm:^1.0.1":
version: 1.0.1
resolution: "string-env-interpolation@npm:1.0.1"
@@ -14904,6 +15203,13 @@ __metadata:
languageName: node
linkType: hard
"strip-json-comments@npm:~2.0.1":
version: 2.0.1
resolution: "strip-json-comments@npm:2.0.1"
checksum: 10c0/b509231cbdee45064ff4f9fd73609e2bcc4e84a4d508e9dd0f31f70356473fde18abfb5838c17d56fb236f5a06b102ef115438de0600b749e818a35fbbc48c43
languageName: node
linkType: hard
"strip-literal@npm:^3.0.0":
version: 3.0.0
resolution: "strip-literal@npm:3.0.0"
@@ -15124,7 +15430,7 @@ __metadata:
languageName: node
linkType: hard
"svgo@npm:^3.3.2":
"svgo@npm:^3.2.0, svgo@npm:^3.3.2":
version: 3.3.2
resolution: "svgo@npm:3.3.2"
dependencies:
@@ -15263,7 +15569,36 @@ __metadata:
languageName: node
linkType: hard
"tar-stream@npm:^2.2.0":
"tar-fs@npm:^2.0.0":
version: 2.1.3
resolution: "tar-fs@npm:2.1.3"
dependencies:
chownr: "npm:^1.1.1"
mkdirp-classic: "npm:^0.5.2"
pump: "npm:^3.0.0"
tar-stream: "npm:^2.1.4"
checksum: 10c0/472ee0c3c862605165163113ab6924f411c07506a1fb24c51a1a80085f0d4d381d86d2fd6b189236c8d932d1cd97b69cce35016767ceb658a35f7584fe77f305
languageName: node
linkType: hard
"tar-fs@npm:^3.0.4":
version: 3.1.0
resolution: "tar-fs@npm:3.1.0"
dependencies:
bare-fs: "npm:^4.0.1"
bare-path: "npm:^3.0.0"
pump: "npm:^3.0.0"
tar-stream: "npm:^3.1.5"
dependenciesMeta:
bare-fs:
optional: true
bare-path:
optional: true
checksum: 10c0/760309677543c03fbc253b5ef1ab4bb2ceafb554471b6cbe4930d1633f35662ec26a1414c66fa6754f5aa7e8c65003f73849242f624c322d3dcba7a8888a6915
languageName: node
linkType: hard
"tar-stream@npm:^2.1.4, tar-stream@npm:^2.2.0":
version: 2.2.0
resolution: "tar-stream@npm:2.2.0"
dependencies:
@@ -15276,7 +15611,7 @@ __metadata:
languageName: node
linkType: hard
"tar-stream@npm:^3.0.0":
"tar-stream@npm:^3.0.0, tar-stream@npm:^3.1.5":
version: 3.1.7
resolution: "tar-stream@npm:3.1.7"
dependencies:
@@ -15622,6 +15957,15 @@ __metadata:
languageName: node
linkType: hard
"tunnel-agent@npm:^0.6.0":
version: 0.6.0
resolution: "tunnel-agent@npm:0.6.0"
dependencies:
safe-buffer: "npm:^5.0.1"
checksum: 10c0/4c7a1b813e7beae66fdbf567a65ec6d46313643753d0beefb3c7973d66fcec3a1e7f39759f0a0b4465883499c6dc8b0750ab8b287399af2e583823e40410a17a
languageName: node
linkType: hard
"tunnel@npm:^0.0.6":
version: 0.0.6
resolution: "tunnel@npm:0.0.6"
@@ -15744,7 +16088,7 @@ __metadata:
languageName: node
linkType: hard
"ufo@npm:^1.1.2, ufo@npm:^1.5.4, ufo@npm:^1.6.1":
"ufo@npm:^1.1.2, ufo@npm:^1.3.2, ufo@npm:^1.5.4, ufo@npm:^1.6.1":
version: 1.6.1
resolution: "ufo@npm:1.6.1"
checksum: 10c0/5a9f041e5945fba7c189d5410508cbcbefef80b253ed29aa2e1f8a2b86f4bd51af44ee18d4485e6d3468c92be9bf4a42e3a2b72dcaf27ce39ce947ec994f1e6b
@@ -16066,6 +16410,78 @@ __metadata:
languageName: node
linkType: hard
"unstorage@npm:^1.10.1":
version: 1.16.1
resolution: "unstorage@npm:1.16.1"
dependencies:
anymatch: "npm:^3.1.3"
chokidar: "npm:^4.0.3"
destr: "npm:^2.0.5"
h3: "npm:^1.15.3"
lru-cache: "npm:^10.4.3"
node-fetch-native: "npm:^1.6.6"
ofetch: "npm:^1.4.1"
ufo: "npm:^1.6.1"
peerDependencies:
"@azure/app-configuration": ^1.8.0
"@azure/cosmos": ^4.2.0
"@azure/data-tables": ^13.3.0
"@azure/identity": ^4.6.0
"@azure/keyvault-secrets": ^4.9.0
"@azure/storage-blob": ^12.26.0
"@capacitor/preferences": ^6.0.3 || ^7.0.0
"@deno/kv": ">=0.9.0"
"@netlify/blobs": ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0
"@planetscale/database": ^1.19.0
"@upstash/redis": ^1.34.3
"@vercel/blob": ">=0.27.1"
"@vercel/kv": ^1.0.1
aws4fetch: ^1.0.20
db0: ">=0.2.1"
idb-keyval: ^6.2.1
ioredis: ^5.4.2
uploadthing: ^7.4.4
peerDependenciesMeta:
"@azure/app-configuration":
optional: true
"@azure/cosmos":
optional: true
"@azure/data-tables":
optional: true
"@azure/identity":
optional: true
"@azure/keyvault-secrets":
optional: true
"@azure/storage-blob":
optional: true
"@capacitor/preferences":
optional: true
"@deno/kv":
optional: true
"@netlify/blobs":
optional: true
"@planetscale/database":
optional: true
"@upstash/redis":
optional: true
"@vercel/blob":
optional: true
"@vercel/kv":
optional: true
aws4fetch:
optional: true
db0:
optional: true
idb-keyval:
optional: true
ioredis:
optional: true
uploadthing:
optional: true
checksum: 10c0/753ed7a5bb0e6b6a4803912428762b5393e78262e94489239c4bd7f718bf95e0f71bb2cf3d3f178f45abcfaf69160fa868b8a82e1b34c1b7dc4609344b1cadad
languageName: node
linkType: hard
"unstorage@npm:^1.16.0":
version: 1.16.0
resolution: "unstorage@npm:1.16.0"
@@ -16880,6 +17296,18 @@ __metadata:
languageName: node
linkType: hard
"xss@npm:^1.0.14":
version: 1.0.15
resolution: "xss@npm:1.0.15"
dependencies:
commander: "npm:^2.20.3"
cssfilter: "npm:0.0.10"
bin:
xss: bin/xss
checksum: 10c0/9b31bee62a208f78e2b7bc8154e3ee87d980f4661dc4ab850ce6f4de7bc50eb152f0bdc13fa759ff8ab6d9bfdf8c0d79cf9f6f86249872b92181912309bccd08
languageName: node
linkType: hard
"y18n@npm:^5.0.5":
version: 5.0.8
resolution: "y18n@npm:5.0.8"