FE2 - Embedding (#1979)

* Add Dialog

* Add options to embed dialog

* Min Height of Clipboard Input multiline to 3 lines

* Check for visibility

* Link to change access of project

* Rename to guided mode

* Change icon when user clicks copy button

* Update Menu styles based on agi feedback

* Update graphql.ts

* Embed Options as hashState

* Auto grow Clipboard Input

* embed state and more options

* Tidyups

* Footer only shows when !embedOptions.isTransparent

* Add auto/manual Load

* Add Pre setup component

* WIP Button Group mobile

* Updates around manual load

* Viewer Share nav

* Add embed dialog to project page

* Minor fixes

* Check for federated

* Responsive Tidyups

* Responsive Fixes. Fix console issues

* Add Alert to Version Embed

* Disable Zoom

* GQL updates

* Comment Slideshow

* GraphQl changes

* Fix visibility

* Build fix

* Revert "Build fix"

This reverts commit 0e706cbd9fde78204032bb1ec4421b1742d023ac.

* remove unneeded change, revert yarn.lock

* Test Commit

* Remove commit test

* Fix build

* Update Tailwind. Add base url env

* fix for portal scope issue

* useLogger

* useLogger

* chore(fe2): include NUXT_PUBLIC_BASE_URL in deployment manifests

* lazy load optimization

* lint fixes

* Updates

* Re-add guided open Dialog sections

* Prevent login popup on embed

* Tidy up mobile combined button group

* Tidy up embed Dialogs

* Small styling issues

* Update scrolling in embed dialog

* Move selection info when embed

* Testing fixes

* Discuss in Speckle

* Responsive Dialog Changes

* Fix bug

* WIP Manual Load

* Fix nuxt errors

* Fix nuxt logger issue

* Fix embed dialog overflows

* New Dialog layout

* Responsive Breakpoint change

* Preview Image

* Fix bug with dialogSection

* Hide selection info on mobile when thread is open

* Footer Model Name

* Overflow on ClipboardInput

* Style fixes

* Tidy ups

* Responsive updates

* Responsive fixes

* Update button

* Changes from testing

* Fix embed height with footer

* Fix Dialog Section

* Fixes from testing

* Move "reset filters" on embed

* Small fixes

* Updates from CR 1

* CR Comments 2

* Updates from CR

* Add deserializeEmbedOptions helper

* DialogSection changes

* Revert changes in TextArea

* Updates from CR

* Only check for noscroll in watch

* Update useRoute

* Comment Slideshow mode

* Changes from testing

* Fix mobile share button

* onMounted warn fixes

* Updates from testing

* Remove nesting of ManualLoad

* Keep Speckle text on mobile

* minor cleanup & bugfixes

* Add target prop to Logo

* navbar flash fix + more cleanup

* Fix urls

* Footer Logo changes

* Remove viewer-transparent from layout

* Add Reply in Speckle

* Remove Anchored Points from embed

* Final changes pre CR

* Fix Anchored Points

* Update packages/frontend-2/components/project/model-page/dialog/embed/Embed.vue

Co-authored-by: Kristaps Fabians Geikins <fabians@speckle.systems>

* Fixes from CR

* Updates from cr

* Changes WIP

* Fix for dialog opening

* Changes from PR

* Updates to check embed in activity

* fix(fe2): project settings dialog error

* Make Team open section on click of "Manage"

* Fixes from merge

* Changes from cr

* Compare old to new in watch

* Fix logo in footer of embed

* Fixes from merge

* Fix build. Fix lazy load

* Updates from Benjamin

* Fix transparent bg

---------

Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>
Co-authored-by: Iain Sproat <68657+iainsproat@users.noreply.github.com>
Co-authored-by: Kristaps Fabians Geikins <fabians@speckle.systems>
This commit is contained in:
andrewwallacespeckle
2024-02-06 10:38:22 +00:00
committed by GitHub
parent ec95ebdfb3
commit ff6433128a
94 changed files with 1663 additions and 458 deletions
+1
View File
@@ -23,6 +23,7 @@ services:
environment:
NUXT_PUBLIC_SERVER_NAME: 'local'
NUXT_PUBLIC_API_ORIGIN: 'http://127.0.0.1'
NUXT_PUBLIC_BASE_URL: 'http://127.0.0.1'
NUXT_PUBLIC_BACKEND_API_ORIGIN: 'http://speckle-server:3000'
NUXT_REDIS_URL: 'redis://redis'
+2
View File
@@ -6,6 +6,8 @@ NUXT_PUBLIC_LOG_PRETTY=true
NUXT_PUBLIC_API_ORIGIN=http://127.0.0.1:3000
NUXT_PUBLIC_BASE_URL=http://127.0.0.1:8081
NUXT_PUBLIC_MIXPANEL_TOKEN_ID=acd87c5a50b56df91a795e999812a3a4
NUXT_PUBLIC_MIXPANEL_API_HOST=https://analytics.speckle.systems
+1 -1
View File
@@ -39,7 +39,7 @@ const config = {
],
'no-alert': 'error',
eqeqeq: ['error', 'always', { null: 'always' }],
'no-console': 'off',
'no-console': 'error',
'no-var': 'error'
},
overrides: [
+6 -2
View File
@@ -1,5 +1,8 @@
<template>
<div id="speckle" class="bg-foundation-page text-foreground">
<div
id="speckle"
class="bg-foundation-page text-foreground has-[.viewer-transparent]:!bg-transparent"
>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
@@ -20,7 +23,8 @@ useHead({
lang: 'en'
},
bodyAttrs: {
class: 'simple-scrollbar bg-foundation-page text-foreground'
class:
'simple-scrollbar bg-foundation-page text-foreground has-[.viewer-transparent]:!bg-transparent'
}
})
@@ -1,7 +1,7 @@
<template>
<div>
<nav class="fixed z-20 top-0 h-14 bg-foundation shadow hover:shadow-md transition">
<div class="flex items-center justify-between h-full w-screen px-4">
<div class="flex gap-4 items-center justify-between h-full w-screen px-4">
<div class="flex items-center truncate">
<HeaderLogoBlock :active="false" to="/" />
<HeaderNavLink
@@ -12,7 +12,7 @@
/>
<PortalTarget name="navigation"></PortalTarget>
</div>
<div class="flex items-center">
<div class="flex items-center gap-1.5 sm:gap-2">
<PortalTarget name="secondary-actions"></PortalTarget>
<PortalTarget name="primary-actions"></PortalTarget>
<!-- Notifications dropdown -->
@@ -13,10 +13,10 @@
</div>
<UserAvatar v-if="!menuOpen" no-bg size="lg" hover-effect>
<BellIcon class="text-foreground w-5 h-5" />
<BellIcon class="text-primary sm:text-foreground w-5 h-5" />
</UserAvatar>
<UserAvatar v-else size="lg" hover-effect no-bg>
<XMarkIcon class="text-foreground w-5 h-5" />
<XMarkIcon class="text-primary sm:text-foreground w-5 h-5" />
</UserAvatar>
</div>
</div>
@@ -46,7 +46,7 @@
</template>
<script setup lang="ts">
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
import { XMarkIcon, BellIcon } from '@heroicons/vue/24/solid'
import { XMarkIcon, BellIcon } from '@heroicons/vue/24/outline'
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
const { activeUser } = useActiveUser()
@@ -0,0 +1,133 @@
<template>
<Menu
as="div"
class="flex items-center relative sm:border-r border-outline-1 sm:pr-4"
>
<MenuButton as="div">
<FormButton class="hidden sm:flex" outlined :icon-right="ChevronDownIcon">
Share
</FormButton>
<button class="sm:hidden mt-1.5">
<ShareIcon class="h-5 w-5 text-primary" />
</button>
</MenuButton>
<Transition
enter-active-class="transition ease-out duration-200"
enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95"
>
<MenuItems
class="absolute z-50 flex flex-col gap-1 right-0 sm:right-4 top-12 min-w-max w-full sm:w-44 p-1 origin-top-right bg-foundation-2 outline outline-2 outline-primary-muted rounded-md shadow-lg overflow-hidden text-sm"
>
<MenuItem v-slot="{ active }">
<div
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-2 items-center px-2 py-1.5 text-sm text-foreground cursor-pointer transition rounded'
]"
@click="handleCopyLink"
@keypress="keyboardClick(handleCopyLink)"
>
<LinkIcon class="w-5 h-5" />
Copy Link
</div>
</MenuItem>
<MenuItem v-if="!isFederated" v-slot="{ active }">
<div
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-2 items-center px-2 py-1.5 text-sm text-foreground cursor-pointer transition rounded'
]"
@click="handleCopyId"
@keypress="keyboardClick(handleCopyId)"
>
<FingerPrintIcon class="w-5 h-5" />
Copy ID
</div>
</MenuItem>
<MenuItem v-slot="{ active }">
<div
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-2 items-center px-2 py-1.5 text-sm text-foreground cursor-pointer transition rounded'
]"
@click="handleEmbed"
@keypress="keyboardClick(handleEmbed)"
>
<CodeBracketIcon class="w-5 h-5" />
Embed Model
</div>
</MenuItem>
</MenuItems>
</Transition>
<ProjectModelPageDialogEmbed
v-model:open="embedDialogOpen"
:project-id="projectId"
:visibility="visibility"
/>
</Menu>
</template>
<script setup lang="ts">
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
import {
ChevronDownIcon,
LinkIcon,
FingerPrintIcon,
CodeBracketIcon,
ShareIcon
} from '@heroicons/vue/24/outline'
import { SpeckleViewer } from '@speckle/shared'
import { keyboardClick } from '@speckle/ui-components'
import type { ProjectVisibility } from '~/lib/common/generated/gql/graphql'
import { useCopyModelLink } from '~~/lib/projects/composables/modelManagement'
const props = defineProps<{
projectId: string
resourceIdString: string
visibility: ProjectVisibility
}>()
const { copy } = useClipboard()
const copyModelLink = useCopyModelLink()
const embedDialogOpen = ref(false)
const parsedResourceIds = computed(() =>
SpeckleViewer.ViewerRoute.parseUrlParameters(props.resourceIdString)
)
const firstResource = computed(() => parsedResourceIds.value[0] || {})
const versionId = computed(() => {
if (SpeckleViewer.ViewerRoute.isModelResource(firstResource.value)) {
return firstResource.value.versionId
}
return ''
})
const modelId = computed(() => {
if (SpeckleViewer.ViewerRoute.isModelResource(firstResource.value)) {
return firstResource.value.modelId // Assuming your firstResource object has a modelId property
}
return ''
})
const isFederated = computed(() => parsedResourceIds.value.length > 1)
const handleCopyId = () => {
copy(props.resourceIdString, { successMessage: 'ID copied to clipboard' })
}
const handleCopyLink = () => {
const modelIdValue = modelId.value
const versionIdValue = versionId.value ? versionId.value : undefined
copyModelLink(props.projectId, modelIdValue, versionIdValue)
}
const handleEmbed = () => {
embedDialogOpen.value = true
}
</script>
@@ -1,6 +1,6 @@
<template>
<div>
<Menu as="div" class="ml-2 flex items-center">
<Menu as="div" class="flex items-center">
<MenuButton v-slot="{ open: userOpen }">
<span class="sr-only">Open user menu</span>
<UserAvatar v-if="!userOpen" size="lg" :user="activeUser" hover-effect />
@@ -23,7 +23,7 @@
<NuxtLink
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-3 border-b border-primary items-center px-3 py-3 text-sm text-primary cursor-pointer transition'
'flex gap-3 border-b border-primary items-center px-3 py-3 text-sm text-primary cursor-pointer transition mb-1'
]"
@click="goToConnectors()"
>
@@ -35,7 +35,7 @@
<NuxtLink
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-2.5 items-center px-3 py-2.5 text-sm text-foreground cursor-pointer transition'
'flex gap-2.5 items-center px-3 py-2.5 text-sm text-foreground cursor-pointer transition mx-1 rounded'
]"
@click="() => (showProfileEditDialog = true)"
>
@@ -47,7 +47,7 @@
<NuxtLink
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-foreground cursor-pointer transition'
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-foreground cursor-pointer transition mx-1 rounded'
]"
@click="goToServerManagement()"
>
@@ -59,7 +59,7 @@
<NuxtLink
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-foreground cursor-pointer transition'
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-foreground cursor-pointer transition mx-1 rounded'
]"
@click="onThemeClick"
>
@@ -71,7 +71,7 @@
<NuxtLink
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-foreground cursor-pointer transition'
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-foreground cursor-pointer transition mx-1 rounded'
]"
@click="toggleInviteDialog"
>
@@ -83,7 +83,7 @@
<NuxtLink
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-foreground cursor-pointer transition'
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-foreground cursor-pointer transition mx-1 rounded'
]"
target="_blank"
to="https://docs.google.com/forms/d/e/1FAIpQLSeTOU8i0KwpgBG7ONimsh4YMqvLKZfSRhWEOz4W0MyjQ1lfAQ/viewform"
@@ -97,7 +97,7 @@
<NuxtLink
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-danger cursor-pointer transition'
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-danger cursor-pointer transition mx-1 rounded'
]"
@click="logout"
>
@@ -109,7 +109,7 @@
<NuxtLink
:class="[
active ? 'bg-foundation-focus' : '',
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-primary cursor-pointer transition'
'flex gap-3.5 items-center px-3 py-2.5 text-sm text-primary cursor-pointer transition mx-1 rounded'
]"
:to="loginUrl"
>
@@ -1,21 +1,26 @@
<template>
<Portal to="navigation">
<HeaderNavLink :to="projectRoute(project.id)" :name="project.name"></HeaderNavLink>
<HeaderNavLink
v-if="props.project.model"
:to="modelVersionsRoute(project.id, props.project.model.id)"
:name="props.project.model.name"
></HeaderNavLink>
</Portal>
<div>
<Portal to="navigation">
<HeaderNavLink
:to="projectRoute(project.id)"
:name="project.name"
></HeaderNavLink>
<HeaderNavLink
v-if="props.project.model"
:to="modelVersionsRoute(project.id, props.project.model.id)"
:name="props.project.model.name"
></HeaderNavLink>
</Portal>
<CommonEditableTitleDescription
:title="titleState"
:description="descriptionState"
:can-edit="canEdit"
:is-disabled="anyMutationsLoading"
@update:title="handleUpdateTitle"
@update:description="handleUpdateDescription"
/>
<CommonEditableTitleDescription
:title="titleState"
:description="descriptionState"
:can-edit="canEdit"
:is-disabled="anyMutationsLoading"
@update:title="handleUpdateTitle"
@update:description="handleUpdateDescription"
/>
</div>
</template>
<script setup lang="ts">
@@ -25,7 +25,7 @@
</div>
<div
v-if="items?.length && project.model"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3 mt-4 relative z-0"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3 mt-4 relative z-10"
>
<!-- Decrementing z-index necessary for the actions menu to render correctly. Each card has its own stacking context because of the scale property -->
<template v-for="(item, i) in items" :key="item.id">
@@ -40,6 +40,7 @@
:selection-disabled="disabledSelections[item.id]"
@select="onSelect(item)"
@chosen="onSingleActionChosen($event, item)"
@embed="handleEmbed(item.id)"
/>
<ProjectModelPageVersionsCard
v-else
@@ -79,6 +80,13 @@
:version="editMessageDialogVersion"
@fully-closed="dialogState = null"
/>
<ProjectModelPageDialogEmbed
v-model:open="embedDialogOpen"
:visibility="project.visibility"
:project-id="project.id"
:version-id="currentVersionId"
:model-id="project.model.id"
/>
<div class="py-12">
<!-- Some padding to deal with a card menu potentially opening at the bottom of the page -->
</div>
@@ -102,6 +110,7 @@ type SingleVersion = NonNullable<Get<typeof versions.value, 'items[0]'>>
graphql(`
fragment ProjectModelPageVersionsPagination on Project {
id
visibility
model(id: $modelId) {
id
versions(limit: 16, cursor: $versionsCursor) {
@@ -167,6 +176,9 @@ const importArea = ref(
}>
)
const currentVersionId = ref<string | undefined>(undefined)
const embedDialogOpen = ref(false)
const selectedItems = computed({
get: () =>
(realVersionItems.value || []).filter((i) => !!itemsSelectedState.value[i.id]),
@@ -273,4 +285,9 @@ const onBatchDelete = () => {
items: selectedItems.value.slice()
}
}
const handleEmbed = (versionId: string) => {
currentVersionId.value = versionId
embedDialogOpen.value = true
}
</script>
@@ -0,0 +1,260 @@
<template>
<LayoutDialog
v-model:open="isOpen"
:max-width="visibility == ProjectVisibility.Private ? 'sm' : 'md'"
:buttons="
visibility == ProjectVisibility.Private
? nonDiscoverableButtons
: discoverableButtons
"
>
<template #header>Embed Model</template>
<div v-if="visibility === ProjectVisibility.Private">
<p>
<strong>Model embedding only works if the project is Discoverable.</strong>
</p>
<p class="mt-5">
To change this setting you must be logged in as a user with the
<strong>Owner</strong>
project permission.
</p>
<p>
Go to
<strong>Project Dashboard > Manage > Access</strong>
and choose
<strong>Discoverable</strong>
from the drop-down list.
</p>
</div>
<div v-else>
<CommonAlert v-if="multipleVersionedResources" class="mb-4 sm:-mt-4" color="info">
<template #title>You are about embedding a specific version</template>
<template #description>
<p>
This means that any changes you made after this version will not be included
in the embedded model.
</p>
<p>
<strong>Tip:</strong>
If you want to share the latest version of your model, go back to the
project dashboard and start the embedding process from there.
</p>
</template>
</CommonAlert>
<div class="flex flex-col lg:flex-row gap-8 mb-6">
<div class="flex-1 order-1 lg:order-2">
<h4 class="font-bold text-sm text-foreground-2 mb-2 ml-0.5">Embed Code</h4>
<FormClipboardInput :value="iframeCode" is-multiline />
<p class="text-sm sm:text-base text-foreground-2 mt-2 mb-5 ml-0.5">
Copy this code to embed your model in a webpage or document.
</p>
<LayoutDialogSection border-b border-t title="Options">
<template #icon>
<Cog6ToothIcon class="h-full w-full" />
</template>
<div
class="flex flex-col gap-1.5 sm:gap-2 ml-5 sm:ml-7 text-sm cursor-default"
>
<div v-for="option in embedDialogOptions" :key="option.id">
<label
:for="`option-${option.id}`"
class="flex items-center gap-1 cursor-pointer max-w-max"
>
<FormCheckbox
:id="`option-${option.id}`"
:model-value="option.value.value"
:name="option.label"
hide-label
class="cursor-pointer"
@update:model-value="
(newValue) => updateOption(option.value, newValue)
"
/>
<span>{{ option.label }}</span>
</label>
</div>
</div>
</LayoutDialogSection>
<LayoutDialogSection
v-if="!isSmallerOrEqualSm"
lazy-load
border-b
title="Preview"
>
<template #icon>
<EyeIcon class="h-full w-full" />
</template>
<ProjectModelPageDialogEmbedIframe
v-if="!isSmallerOrEqualSm"
:src="updatedUrl"
title="Preview"
width="600"
height="400"
class="shrink-0 w-[600px] h-[400px] mx-auto"
/>
</LayoutDialogSection>
</div>
</div>
</div>
</LayoutDialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { Cog6ToothIcon, EyeIcon } from '@heroicons/vue/24/outline'
import { ProjectVisibility } from '~~/lib/common/generated/gql/graphql'
import { useClipboard } from '~~/composables/browser'
import { SpeckleViewer } from '@speckle/shared'
import { projectRoute } from '~~/lib/common/helpers/route'
const props = defineProps<{
visibility?: ProjectVisibility
projectId: string
modelId?: string
versionId?: string
}>()
const isOpen = defineModel<boolean>('open', { required: true })
const router = useRouter()
const route = useRoute()
const { copy } = useClipboard()
const {
public: { baseUrl }
} = useRuntimeConfig()
const { isSmallerOrEqualSm } = useIsSmallerOrEqualThanBreakpoint()
const transparentBackground = ref(false)
const hideViewerControls = ref(false)
const hideSelectionInfo = ref(false)
const preventScrolling = ref(false)
const manuallyLoadModel = ref(false)
const routeModelId = computed(() => route.params.modelId as string)
const parsedResources = computed(() =>
SpeckleViewer.ViewerRoute.parseUrlParameters(routeModelId.value)
)
const multipleVersionedResources = computed(() => {
return (
parsedResources.value.filter(
(resource) =>
SpeckleViewer.ViewerRoute.isModelResource(resource) &&
resource.versionId !== undefined
).length > 1
)
})
const updatedUrl = computed(() => {
const url = new URL(`/projects/${encodeURIComponent(props.projectId)}`, baseUrl)
url.pathname += '/models/'
// Use props.modelId and props.versionId if provided
if (props.modelId) {
let modelPath = encodeURIComponent(props.modelId)
if (props.versionId) {
modelPath += `@${encodeURIComponent(props.versionId)}`
}
url.pathname += modelPath
} else {
// Otherwise, use routeModelId directly
url.pathname += routeModelId.value
}
// Construct the embed options as a hash fragment
const embedOptions: Record<string, boolean> = { isEnabled: true }
embedDialogOptions.forEach((option) => {
if (option.value.value) {
embedOptions[option.id] = true
}
})
// Serialize the embedOptions into a hash fragment
const hashFragment = encodeURIComponent(JSON.stringify(embedOptions))
url.hash = `embed=${hashFragment}`
return url.toString()
})
const iframeCode = computed(() => {
return `<iframe title="Speckle" src="${updatedUrl.value}" width="600" height="400" frameborder="0"></iframe>`
})
const discoverableButtons = computed(() => [
{
text: 'Cancel',
props: { color: 'invert', fullWidth: true, outline: true },
onClick: () => {
isOpen.value = false
}
},
{
text: 'Copy Embed Code',
props: { color: 'primary', fullWidth: true },
onClick: () => {
handleEmbedCodeCopy(iframeCode.value)
}
}
])
const nonDiscoverableButtons = computed(() => [
{
text: 'Close',
props: { color: 'invert', fullWidth: true, outline: true },
onClick: () => {
isOpen.value = false
}
},
{
text: 'Change Access',
props: { color: 'primary', fullWidth: true },
onClick: () => {
isOpen.value = false
router.push(`${projectRoute(props.projectId)}?settings=access`)
}
}
])
const handleEmbedCodeCopy = async (value: string) => {
await copy(value, {
successMessage: 'Embed code copied to clipboard',
failureMessage: 'Failed to copy embed code to clipboard'
})
}
const updateOption = (optionRef: Ref<boolean>, newValue: unknown) => {
optionRef.value = newValue === undefined ? false : !!newValue
}
const embedDialogOptions = [
{
id: 'isTransparent',
label: 'Transparent background',
value: transparentBackground
},
{
id: 'hideControls',
label: 'Hide viewer controls',
value: hideViewerControls
},
{
id: 'hideSelectionInfo',
label: 'Hide the Selection Info panel',
value: hideSelectionInfo
},
{
id: 'noScroll',
label: 'Prevent scrolling (zooming)',
value: preventScrolling
},
{
id: 'manualLoad',
label: 'Load model manually',
value: manuallyLoadModel
}
]
</script>
@@ -0,0 +1,19 @@
<template>
<iframe
:src="src"
:title="title"
:width="width"
:height="height"
frameborder="0"
scrolling="no"
></iframe>
</template>
<script setup lang="ts">
defineProps<{
src: string
title: string
width?: string | number
height?: string | number
}>()
</script>
@@ -79,6 +79,7 @@
:selection-disabled="selectionDisabled"
@select="onSelect"
@chosen="$emit('chosen', $event)"
@embed="$emit('embed')"
/>
</div>
</div>
@@ -122,6 +123,7 @@ const emit = defineEmits<{
(e: 'select'): void
(e: 'update:selected', val: boolean): void
(e: 'chosen', val: VersionActionTypes): void
(e: 'embed'): void
}>()
const props = defineProps<{
@@ -21,7 +21,8 @@ import {
LinkIcon,
FingerPrintIcon,
ArrowRightOnRectangleIcon,
CursorArrowRaysIcon
CursorArrowRaysIcon,
CodeBracketIcon
} from '@heroicons/vue/24/outline'
import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
import { useCopyModelLink } from '~~/lib/projects/composables/modelManagement'
@@ -31,6 +32,7 @@ const emit = defineEmits<{
(e: 'update:open', v: boolean): void
(e: 'select'): void
(e: 'chosen', v: VersionActionTypes): void
(e: 'embed'): void
}>()
const props = defineProps<{
@@ -81,7 +83,8 @@ const actionsItems = computed<LayoutMenuItem<VersionActionTypes>[][]>(() => [
],
[
{ title: 'Copy Link', id: VersionActionTypes.Share, icon: LinkIcon },
{ title: 'Copy ID', id: VersionActionTypes.CopyId, icon: FingerPrintIcon }
{ title: 'Copy ID', id: VersionActionTypes.CopyId, icon: FingerPrintIcon },
{ title: 'Embed Model', id: VersionActionTypes.EmbedModel, icon: CodeBracketIcon }
],
[
{
@@ -108,6 +111,9 @@ const onActionChosen = (params: { item: LayoutMenuItem<VersionActionTypes> }) =>
case VersionActionTypes.CopyId:
copy(props.versionId, { successMessage: 'Version ID copied to clipboard' })
break
case VersionActionTypes.EmbedModel:
emit('embed')
break
}
emit('chosen', item.id)
@@ -1,16 +1,21 @@
<template>
<Portal to="navigation">
<HeaderNavLink :to="projectRoute(project.id)" :name="project.name"></HeaderNavLink>
</Portal>
<div>
<Portal to="navigation">
<HeaderNavLink
:to="projectRoute(project.id)"
:name="project.name"
></HeaderNavLink>
</Portal>
<CommonEditableTitleDescription
:title="titleState"
:description="descriptionState"
:can-edit="canEdit"
:is-disabled="anyMutationsLoading"
@update:title="handleUpdateTitle"
@update:description="handleUpdateDescription"
/>
<CommonEditableTitleDescription
:title="titleState"
:description="descriptionState"
:can-edit="canEdit"
:is-disabled="anyMutationsLoading"
@update:title="handleUpdateTitle"
@update:description="handleUpdateDescription"
/>
</div>
</template>
<script setup lang="ts">
@@ -95,6 +95,7 @@ graphql(`
fragment ProjectPageLatestItemsModels on Project {
id
role
visibility
modelCount: models(limit: 0) {
totalCount
}
@@ -1,19 +1,19 @@
<template>
<div :class="['p-4', 'flex flex-col justify-center items-center space-y-8']">
<div :class="['p-4', 'flex flex-col justify-center items-center gap-2 sm:gap-6']">
<div
:class="`w-42 h-42 group transition-[margin-right] mr-0 hover:mr-12 ${
:class="`hidden sm:block w-42 h-42 group transition-[margin-right] mr-0 hover:sm:mr-12 ${
small ? 'scale-75' : ''
}`"
>
<template v-if="!isDarkTheme">
<img
src="~~/assets/images/discussions/d-w-1.png"
class="opacity-80 w-36 h-auto shadow-md relative transition grayscale blur-[1px] group-hover:blur-[2px] group-hover:grayscale-0 group-hover:-translate-x-10 group-hover:-translate-y-3 group-hover:scale-105"
class="opacity-80 w-36 h-auto shadow-md relative transition grayscale blur-[1px] group-hover:blur-[2px] group-hover:sm:grayscale-0 group-hover:sm:-translate-x-10 group-hover:sm:-translate-y-3 group-hover:sm:scale-105"
alt="discussions image"
/>
<img
src="~~/assets/images/discussions/d-w-2.png"
class="w-36 shadow-md relative ml-10 -mt-20 transition grayscale group-hover:grayscale-0 group-hover:translate-x-5 group-hover:scale-150 group-hover:shadow-xl"
class="w-36 shadow-md relative ml-10 -mt-20 transition grayscale group-hover:sm:grayscale-0 group-hover:sm:translate-x-5 group-hover:sm:scale-150 group-hover:sm:shadow-xl"
alt="discussions image"
/>
</template>
@@ -30,13 +30,17 @@
/>
</template>
</div>
<div class="text-foreground text-center">
<div class="text-foreground text-center text-xs sm:text-sm">
<div>Speckle allows for real time discussions straight in your 3D model.</div>
<div v-if="!small" class="text-xs text-foreground-2">
Head over to a model and start coordinating right away!
</div>
<div v-else class="mt-2">
<FormButton :icon-left="PlusIcon" @click="() => $emit('new-discussion')">
<div v-else class="mt-3">
<FormButton
size="sm"
:icon-left="PlusIcon"
@click="() => $emit('new-discussion')"
>
New discussion
</FormButton>
</div>
@@ -23,11 +23,20 @@
:project-id="projectId"
@deleted="$emit('model-updated')"
/>
<ProjectModelPageDialogEmbed
v-model:open="embedDialogOpen"
:project-id="projectId"
:visibility="visibility"
:model-id="model.id"
/>
</div>
</template>
<script setup lang="ts">
import type { Nullable } from '@speckle/shared'
import type { ProjectPageModelsActionsFragment } from '~~/lib/common/generated/gql/graphql'
import type {
ProjectPageModelsActionsFragment,
ProjectVisibility
} from '~~/lib/common/generated/gql/graphql'
import type { LayoutMenuItem } from '~~/lib/layout/helpers/components'
import { useCopyModelLink } from '~~/lib/projects/composables/modelManagement'
import { EllipsisVerticalIcon } from '@heroicons/vue/24/solid'
@@ -36,7 +45,8 @@ import {
PencilIcon,
LinkIcon,
FingerPrintIcon,
ArrowUpTrayIcon
ArrowUpTrayIcon,
CodeBracketIcon
} from '@heroicons/vue/24/outline'
import { graphql } from '~~/lib/common/generated/gql'
import { useMixpanel } from '~~/lib/core/composables/mp'
@@ -53,13 +63,15 @@ enum ActionTypes {
Delete = 'delete',
Share = 'share',
UploadVersion = 'upload-version',
CopyId = 'copy-id'
CopyId = 'copy-id',
Embed = 'embed'
}
const emit = defineEmits<{
(e: 'update:open', v: boolean): void
(e: 'model-updated'): void
(e: 'upload-version'): void
(e: 'embed'): void
}>()
const props = defineProps<{
@@ -67,6 +79,7 @@ const props = defineProps<{
model: ProjectPageModelsActionsFragment
projectId: string
canEdit?: boolean
visibility?: ProjectVisibility
}>()
const copyModelLink = useCopyModelLink()
@@ -74,6 +87,7 @@ const { copy } = useClipboard()
const showActionsMenu = ref(false)
const openDialog = ref(null as Nullable<ActionTypes>)
const embedDialogOpen = ref(false)
const isMain = computed(() => props.model.name === 'main')
const actionsItems = computed<LayoutMenuItem[][]>(() => [
@@ -93,7 +107,8 @@ const actionsItems = computed<LayoutMenuItem[][]>(() => [
],
[
{ title: 'Copy Link', id: ActionTypes.Share, icon: LinkIcon },
{ title: 'Copy ID', id: ActionTypes.CopyId, icon: FingerPrintIcon }
{ title: 'Copy ID', id: ActionTypes.CopyId, icon: FingerPrintIcon },
{ title: 'Embed Model', id: ActionTypes.Embed, icon: CodeBracketIcon }
],
[
{
@@ -135,6 +150,9 @@ const onActionChosen = (params: { item: LayoutMenuItem; event: MouseEvent }) =>
case ActionTypes.CopyId:
copy(props.model.id, { successMessage: 'Copied model ID to clipboard' })
break
case ActionTypes.Embed:
embedDialogOpen.value = true
break
}
}
@@ -40,22 +40,26 @@
/>
</div>
</div>
<div class="h-12 flex items-center px-2 py-1 space-x-1">
<NuxtLink class="min-w-0 cursor-pointer" :href="finalModelUrl">
<div
class="h-auto sm:h-12 flex flex-col sm:flex-row sm:items-center px-2 py-1 gap-x-1"
>
<NuxtLink class="min-w-0 max-w-full cursor-pointer" :href="finalModelUrl">
<div
v-if="nameParts[0]"
class="text-xs text-foreground-2 relative -mb-1 truncate"
>
{{ nameParts[0] }}
</div>
<div class="font-bold truncate text-foreground flex-shrink min-w-0">
<div
class="font-bold text-sm sm:text-base truncate text-foreground flex-shrink min-w-0"
>
{{ nameParts[1] }}
</div>
</NuxtLink>
<div class="grow" />
<div class="hidden sm:flex grow" />
<div class="flex items-center">
<div
:class="`text-xs w-full text-foreground-2 mr-1 truncate transition ${
:class="`text-xs w-full text-foreground-2 sm:mr-1 truncate transition ${
hovered ? 'sm:w-auto' : 'sm:w-0'
}`"
>
@@ -81,6 +85,7 @@
v-model:open="showActionsMenu"
:model="model"
:project-id="projectId"
:visibility="project?.visibility"
:can-edit="canEdit"
@click.stop.prevent
@upload-version="triggerVersionUpload"
@@ -136,6 +141,7 @@ graphql(`
fragment ProjectPageModelsCardProject on Project {
id
role
visibility
}
`)
@@ -1,6 +1,13 @@
<template>
<template v-if="itemsCount">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
<div
class="relative z-10 grid gap-3"
:class="
smallView
? 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4'
: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4'
"
>
<!-- Decrementing z-index necessary for the actions menu to render correctly. Each card has its own stacking context because of the scale property -->
<ProjectPageModelsCard
v-for="(item, i) in items"
@@ -10,6 +17,7 @@
:project="project"
:show-actions="showActions"
:show-versions="showVersions"
height="h-32 sm:h-64"
:disable-default-link="disableDefaultLinks"
:style="`z-index: ${items.length - i};`"
@click="($event) => $emit('model-clicked', { id: item.id, e: $event })"
@@ -70,6 +78,7 @@ const props = withDefaults(
disablePagination?: boolean
sourceApps?: SourceAppDefinition[]
contributors?: FormUsersSelectItemFragment[]
smallView?: boolean
}>(),
{
showActions: true,
@@ -24,19 +24,25 @@
<div class="flex items-center justify-between mt-3">
<UserAvatarGroup :users="teamUsers" class="max-w-[104px]" />
<div v-if="activeUser">
<FormButton class="ml-2" @click="dialogOpen = true">
<FormButton class="ml-2" @click="onButtonClick">
{{ project.role === 'stream:owner' ? 'Manage' : 'View' }}
</FormButton>
</div>
</div>
</template>
<template #default>
<ProjectPageTeamDialog v-model:open="dialogOpen" :project="project" />
<ProjectPageTeamDialog
v-model:open="dialogOpen"
:project="project"
:open-section="openSection"
/>
</template>
</ProjectPageStatsBlock>
</template>
<script setup lang="ts">
import { Cog6ToothIcon, UsersIcon } from '@heroicons/vue/24/outline'
import type { Optional } from '@speckle/shared'
import { OpenSectionType } from '~/lib/projects/helpers/components'
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
import { graphql } from '~~/lib/common/generated/gql'
import type { ProjectPageStatsBlockTeamFragment } from '~~/lib/common/generated/gql/graphql'
@@ -61,6 +67,51 @@ const props = defineProps<{
}>()
const dialogOpen = ref(false)
const openSection = ref<OpenSectionType | undefined>()
const route = useRoute()
const router = useRouter()
const teamUsers = computed(() => props.project.team.map((t) => t.user))
const readDialogStateFromQuery = async () => {
const newSettings = route.query.settings as Optional<string | true>
let shouldShow = false
if (!newSettings) {
shouldShow = false
} else if (newSettings === 'invite') {
shouldShow = true
openSection.value = OpenSectionType.Invite
} else if (newSettings === 'access') {
shouldShow = true
openSection.value = OpenSectionType.Access
} else {
shouldShow = true
openSection.value = OpenSectionType.Team
}
if (shouldShow) {
dialogOpen.value = true
await router.replace({ query: { ...route.query, settings: undefined } })
}
}
const onButtonClick = () => {
openSection.value = OpenSectionType.Team
dialogOpen.value = true
}
onMounted(() => {
readDialogStateFromQuery()
})
watch(
() => route.query.settings,
(newSettings, oldSettings) => {
if (newSettings !== oldSettings) {
readDialogStateFromQuery()
}
}
)
</script>
@@ -2,12 +2,19 @@
<LayoutDialog v-model:open="isOpen" max-width="sm">
<template #header>Manage Project</template>
<div class="flex flex-col text-foreground">
<ProjectPageTeamDialogManageUsers always-open :project="project" />
<ProjectPageTeamDialogManageUsers
:always-open="openSection === OpenSectionType.Team"
:project="project"
/>
<ProjectPageTeamDialogInviteUser
v-if="isOwner && !isServerGuest"
:project="project"
:default-open="openSection === OpenSectionType.Invite"
/>
<ProjectPageTeamDialogManagePermissions
:project="project"
:default-open="openSection === OpenSectionType.Access"
/>
<ProjectPageTeamDialogManagePermissions :project="project" />
<ProjectPageTeamDialogWebhooks v-if="isOwner" :project="project" />
<ProjectPageTeamDialogDangerZones
v-if="isOwner || canLeaveProject"
@@ -20,6 +27,7 @@
import type { ProjectPageTeamDialogFragment } from '~~/lib/common/generated/gql/graphql'
import { graphql } from '~~/lib/common/generated/gql'
import { useTeamDialogInternals } from '~~/lib/projects/composables/team'
import { OpenSectionType } from '~~/lib/projects/helpers/components'
graphql(`
fragment ProjectPageTeamDialog on Project {
@@ -55,6 +63,7 @@ const emit = defineEmits<{
const props = defineProps<{
open: boolean
project: ProjectPageTeamDialogFragment
openSection?: OpenSectionType
}>()
const { isOwner, isServerGuest, canLeaveProject } = useTeamDialogInternals({
@@ -1,5 +1,11 @@
<template>
<LayoutDialogSection allow-overflow border-b border-t title="Invite">
<LayoutDialogSection
allow-overflow
border-b
border-t
title="Invite"
:always-open="defaultOpen"
>
<template #icon>
<UserPlusIcon class="h-full w-full" />
</template>
@@ -67,6 +73,7 @@ type InvitableUser = UserSearchItem | string
const props = defineProps<{
project: ProjectPageTeamDialogFragment
defaultOpen: boolean
}>()
const loading = ref(false)
@@ -1,5 +1,10 @@
<template>
<LayoutDialogSection allow-overflow border-b title="Access">
<LayoutDialogSection
allow-overflow
border-b
title="Access"
:always-open="defaultOpen"
>
<template #icon>
<LockOpenIcon
v-if="project.visibility === ProjectVisibility.Public"
@@ -45,6 +50,7 @@ import { useMixpanel } from '~~/lib/core/composables/mp'
const props = defineProps<{
project: ProjectPageTeamDialogFragment
defaultOpen: boolean
}>()
const { isOwner, isServerGuest } = useTeamDialogInternals({
@@ -2,7 +2,7 @@
<div
class="relative max-w-4xl w-screen h-[100dvh] flex items-center justify-center z-50"
>
<TourSegmentation v-if="tourState.showSegmentation && step === 0" @next="step++" />
<TourSegmentation v-if="showSegmentation && step === 0" @next="step++" />
<TourSlideshow v-if="step === 1" @next="step++" />
<!-- <OnboardingDialogManager v-if="step === 2" allow-escape @cancel="step++" /> -->
<div
@@ -27,6 +27,7 @@
</div>
</template>
<script setup lang="ts">
import { useViewerTour } from '~/lib/viewer/composables/tour'
import { useSynchronizedCookie } from '~~/lib/common/composables/reactiveCookie'
import { useMixpanel } from '~~/lib/core/composables/mp'
@@ -38,7 +39,7 @@ const hasCompletedChecklistV1 = useSynchronizedCookie<boolean>(
{ default: () => false }
)
const tourState = useTourStageState()
const { showSegmentation } = useViewerTour()
const mp = useMixpanel()
watch(step, (val) => {
@@ -73,6 +73,7 @@ import {
import type { OnboardingState } from '~~/lib/auth/helpers/onboarding'
import { useProcessOnboarding } from '~~/lib/auth/composables/onboarding'
import { useCameraUtilities } from '~~/lib/viewer/composables/ui'
import { useViewerTour } from '~/lib/viewer/composables/tour'
const { setMixpanelSegments } = useProcessOnboarding()
const {
@@ -84,7 +85,7 @@ const {
const onboardingState = ref<OnboardingState>({ industry: undefined, role: undefined })
const { activeUser } = useActiveUser()
const tourState = useTourStageState()
const tourState = useViewerTour()
const emit = defineEmits(['next'])
@@ -102,7 +103,7 @@ function setRole(val: OnboardingRole) {
nextView()
// NOTE: workaround for being able to view this in storybook
if (activeUser.value?.id) setMixpanelSegments(onboardingState.value)
tourState.value.showSegmentation = false
tourState.showSegmentation.value = false
emit('next')
}
@@ -60,10 +60,11 @@ import BasicViewerNavigation from '~~/components/tour/content/BasicViewerNavigat
import OverlayModel from '~~/components/tour/content/OverlayModel.vue'
import { useCameraUtilities } from '~~/lib/viewer/composables/ui'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useViewerTour } from '~/lib/viewer/composables/tour'
const emit = defineEmits(['next'])
const tourStage = useTourStageState()
const tourStage = useViewerTour()
const { zoom, setView } = useCameraUtilities()
// Drives the amount of slideshow items
@@ -148,8 +149,8 @@ useViewerAnchoredPoints({
const finishSlideshow = () => {
zoom()
setView('left')
tourStage.value.showNavbar = true
tourStage.value.showViewerControls = true
tourStage.showNavbar.value = true
tourStage.showControls.value = true
emit('next')
}
@@ -4,9 +4,10 @@
</div>
</template>
<script setup lang="ts">
const state = useTourStageState()
import { useViewerTour } from '~/lib/viewer/composables/tour'
state.value.showNavbar = true
const state = useViewerTour()
state.value.showViewerControls = true
state.showNavbar.value = true
state.showControls.value = true
</script>
@@ -5,6 +5,7 @@
>
<!-- Add new thread bubble -->
<ViewerAnchoredPointNewThread
v-if="canPostComment && !isEmbedEnabled"
v-model="buttonState"
:can-post-comment="canPostComment"
class="z-[13]"
@@ -26,13 +27,15 @@
@login="showLoginDialog = true"
/>
<!-- Active users -->
<ViewerAnchoredPointUser
v-for="user in Object.values(users)"
:key="user.state.sessionId"
:user="user"
class="z-[10]"
/>
<div v-if="!isEmbedEnabled">
<!-- Active users -->
<ViewerAnchoredPointUser
v-for="user in Object.values(users)"
:key="user.state.sessionId"
:user="user"
class="z-[10]"
/>
</div>
<AuthLoginPanel
v-model:open="showLoginDialog"
@@ -46,7 +49,7 @@
<ViewerScope :state="state">
<div
v-if="usersWithAvatars.length > 0"
class="px-1 py-1 flex space-x-1 items-center"
class="scale-90 flex space-x-1 items-center"
>
<!-- <UserAvatarGroup :users="activeUserAvatars" :overlap="false" hover-effect /> -->
<template v-for="user in usersWithAvatars" :key="user.id">
@@ -71,8 +74,16 @@
<!-- Active user tracking cancel & Follower count display -->
<div
v-if="(spotlightUserSessionId && spotlightUser) || followers.length !== 0"
class="absolute w-screen mt-[3.5rem] h-[calc(100dvh-3.5rem)] z-10 p-1"
v-if="
(!isEmbedEnabled && spotlightUserSessionId && spotlightUser) ||
followers.length !== 0
"
class="absolute w-screen z-10 p-1"
:class="
isEmbedEnabled
? 'h-[calc(100dvh-3.5rem)]'
: 'h-[calc(100dvh-3.5rem)] mt-[3.5rem]'
"
>
<div
class="w-full h-full outline -outline-offset-0 outline-8 rounded-md outline-blue-500/40"
@@ -104,6 +115,7 @@
</template>
<script setup lang="ts">
import type { Nullable } from '@speckle/shared'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import { useActiveUser } from '~~/lib/auth/composables/activeUser'
import type { LimitedUser } from '~~/lib/common/generated/gql/graphql'
import type { SetFullyRequired } from '~~/lib/common/helpers/type'
@@ -129,6 +141,8 @@ const { isOpenThread, open } = useThreadUtilities()
const canPostComment = useCheckViewerCommentingAccess()
const { isEnabled: isEmbedEnabled } = useEmbed()
const followers = computed(() => {
if (!isLoggedIn.value) return []
const res = [] as LimitedUser[]
@@ -1,24 +1,31 @@
<template>
<div>
<div v-if="showControls">
<div
class="absolute z-20 flex h-[100dvh] flex-col space-y-2 bg-green-300/0 px-2 pt-[4.2rem]"
class="absolute z-20 flex max-h-screen simple-scrollbar flex-col space-y-1 md:space-y-2 bg-green-300/0 px-2"
:class="
showNavbar && !isEmbedEnabled
? 'pt-[4.2rem]'
: isTransparent
? 'pt-2'
: 'pt-2 pb-16'
"
>
<!-- Models -->
<ViewerControlsButtonToggle
v-tippy="modelsShortcut"
v-tippy="isSmallerOrEqualSm ? undefined : modelsShortcut"
:active="activeControl === 'models'"
@click="toggleActiveControl('models')"
>
<CubeIcon class="h-5 w-5" />
<CubeIcon class="h-4 w-4 md:h-5 md:w-5" />
</ViewerControlsButtonToggle>
<!-- Explorer -->
<ViewerControlsButtonToggle
v-tippy="explorerShortcut"
v-tippy="isSmallerOrEqualSm ? undefined : explorerShortcut"
:active="activeControl === 'explorer'"
@click="toggleActiveControl('explorer')"
>
<IconFileExplorer class="h-5 w-5" />
<IconFileExplorer class="h-4 w-4 md:h-5 md:w-5" />
</ViewerControlsButtonToggle>
<!-- TODO -->
@@ -31,17 +38,17 @@
<!-- Comment threads -->
<ViewerControlsButtonToggle
v-tippy="discussionsShortcut"
v-tippy="isSmallerOrEqualSm ? undefined : discussionsShortcut"
:active="activeControl === 'discussions'"
@click="toggleActiveControl('discussions')"
>
<ChatBubbleLeftRightIcon class="h-5 w-5" />
<ChatBubbleLeftRightIcon class="h-4 w-4 md:h-5 md:w-5" />
</ViewerControlsButtonToggle>
<!-- Automateeeeeeee FTW -->
<ViewerControlsButtonToggle
v-if="allAutomationRuns.length !== 0"
v-tippy="summary.longSummary"
v-tippy="isSmallerOrEqualSm ? undefined : summary.longSummary"
:active="activeControl === 'automate'"
class="p-2"
@click="toggleActiveControl('automate')"
@@ -56,68 +63,89 @@
<!-- Measurements -->
<ViewerControlsButtonToggle
v-tippy="measureShortcut"
v-tippy="isSmallerOrEqualSm ? undefined : measureShortcut"
:active="activeControl === 'measurements'"
@click="toggleMeasurements"
>
<IconMeasurements class="h-5 w-5" />
<IconMeasurements class="h-4 w-4 md:h-5 md:w-5" />
</ViewerControlsButtonToggle>
<!-- Standard viewer controls -->
<ViewerControlsButtonGroup>
<!-- Views -->
<ViewerViewsMenu v-tippy="'Views'" />
<!-- Zoom extents -->
<ViewerControlsButtonToggle
v-tippy="zoomExtentsShortcut"
flat
@click="trackAndzoomExtentsOrSelection()"
<div class="w-8 flex gap-2">
<div class="md:hidden">
<ViewerControlsButtonToggle
:active="activeControl === 'mobileOverflow'"
@click="toggleActiveControl('mobileOverflow')"
>
<ChevronDoubleRightIcon
class="h-4 w-4 md:h-5 md:w-5 transition"
:class="activeControl === 'mobileOverflow' ? 'rotate-180' : ''"
/>
</ViewerControlsButtonToggle>
</div>
<div
class="-mt-28 md:mt-0 bg-foundation md:bg-transparent md:gap-2 shadow-md md:shadow-none flex flex-col rounded-lg transition-all *:shadow-none *:py-0 *:md:shadow-md *:md:py-2"
:class="[
activeControl === 'mobileOverflow' ? '' : '-translate-x-24 md:translate-x-0'
]"
>
<ArrowsPointingOutIcon class="h-5 w-5" />
</ViewerControlsButtonToggle>
<ViewerControlsButtonGroup>
<!-- Views -->
<ViewerViewsMenu v-tippy="isSmallerOrEqualSm ? undefined : 'Views'" />
<!-- Zoom extents -->
<ViewerControlsButtonToggle
v-tippy="isSmallerOrEqualSm ? undefined : zoomExtentsShortcut"
flat
@click="trackAndzoomExtentsOrSelection()"
>
<ArrowsPointingOutIcon class="h-4 w-4 md:h-5 md:w-5" />
</ViewerControlsButtonToggle>
<!-- Sun and lights -->
<ViewerSunMenu v-tippy="'Light Controls'" />
</ViewerControlsButtonGroup>
<ViewerControlsButtonGroup>
<!-- Projection type -->
<!-- TODO (Question for fabs): How to persist state between page navigation? e.g., swap to iso mode, move out, move back, iso mode is still on in viewer but not in ui -->
<ViewerControlsButtonToggle
v-tippy="projectionShortcut"
flat
secondary
:active="isOrthoProjection"
@click="trackAndtoggleProjection()"
>
<IconPerspective v-if="isOrthoProjection" class="h-4 w-4" />
<IconPerspectiveMore v-else class="h-4 w-4" />
</ViewerControlsButtonToggle>
<!-- Sun and lights -->
<ViewerSunMenu
v-tippy="isSmallerOrEqualSm ? undefined : 'Light Controls'"
/>
</ViewerControlsButtonGroup>
<ViewerControlsButtonGroup>
<!-- Projection type -->
<!-- TODO (Question for fabs): How to persist state between page navigation? e.g., swap to iso mode, move out, move back, iso mode is still on in viewer but not in ui -->
<ViewerControlsButtonToggle
v-tippy="isSmallerOrEqualSm ? undefined : projectionShortcut"
flat
secondary
:active="isOrthoProjection"
@click="trackAndtoggleProjection()"
>
<IconPerspective v-if="isOrthoProjection" class="h-3.5 md:h-4 w-4" />
<IconPerspectiveMore v-else class="h-3.5 md:h-4 w-4" />
</ViewerControlsButtonToggle>
<!-- Section Box -->
<ViewerControlsButtonToggle
v-tippy="sectionBoxShortcut"
flat
secondary
:active="isSectionBoxEnabled"
@click="toggleSectionBox()"
>
<ScissorsIcon class="h-5 w-5" />
</ViewerControlsButtonToggle>
<!-- Section Box -->
<ViewerControlsButtonToggle
v-tippy="isSmallerOrEqualSm ? undefined : sectionBoxShortcut"
flat
secondary
:active="isSectionBoxEnabled"
@click="toggleSectionBox()"
>
<ScissorsIcon class="h-4 w-4 md:h-5 md:w-5" />
</ViewerControlsButtonToggle>
<!-- Explosion -->
<ViewerExplodeMenu v-tippy="'Explode'" />
<!-- Explosion -->
<ViewerExplodeMenu v-tippy="isSmallerOrEqualSm ? undefined : 'Explode'" />
<!-- Settings -->
<ViewerSettingsMenu />
</ViewerControlsButtonGroup>
<!-- Settings -->
<ViewerSettingsMenu />
</ViewerControlsButtonGroup>
</div>
<!-- Standard viewer controls -->
</div>
</div>
<div
ref="scrollableControlsContainer"
:class="`simple-scrollbar absolute z-10 mx-14 mt-[4rem] mb-4 max-h-[calc(100dvh-5.5rem)] w-64 sm:w-72 overflow-y-auto px-[2px] py-[2px] transition ${
:class="`simple-scrollbar absolute z-10 ml-12 md:ml-14 mb-4 max-h-[calc(100dvh-4.5rem)] w-56 md:w-72 overflow-y-auto px-[2px] py-[2px] transition ${
activeControl !== 'none'
? 'translate-x-0 opacity-100'
: '-translate-x-[100%] opacity-0'
}`"
} ${isEmbedEnabled ? 'mt-1.5' : 'mt-[4rem]'}`"
>
<div v-show="activeControl.length !== 0 && activeControl === 'measurements'">
<KeepAlive>
@@ -185,7 +213,8 @@ import {
ChatBubbleLeftRightIcon,
ArrowsPointingOutIcon,
ScissorsIcon,
PlusIcon
PlusIcon,
ChevronDoubleRightIcon
} from '@heroicons/vue/24/outline'
import type { Nullable } from '@speckle/shared'
import {
@@ -213,6 +242,8 @@ const {
import { AutomationRunStatus } from '~~/lib/common/generated/gql/graphql'
import type { AutomationRun } from '~~/lib/common/generated/gql/graphql'
import { useIsSmallerOrEqualThanBreakpoint } from '~~/composables/browser'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import { useViewerTour } from '~/lib/viewer/composables/tour'
const { resourceItems, modelsAndVersionIds } = useInjectedViewerLoadedResources()
@@ -220,6 +251,10 @@ const { toggleSectionBox, isSectionBoxEnabled } = useSectionBoxUtilities()
const { enableMeasurements } = useMeasurementUtilities()
const { showNavbar, showControls } = useViewerTour()
const { isTransparent, isEnabled: isEmbedEnabled } = useEmbed()
const allAutomationRuns = computed(() => {
const allAutomationStatuses = modelsAndVersionIds.value
.map((model) => model.model.loadedVersion.items[0].automationStatus)
@@ -290,6 +325,7 @@ type ActiveControl =
| 'discussions'
| 'automate'
| 'measurements'
| 'mobileOverflow'
const openAddModel = ref(false)
@@ -1,7 +1,8 @@
<template>
<div
v-show="hasAnyFiltersApplied"
class="absolute bottom-4 left-0 w-screen p-2 bg-pink-300/0 flex justify-center pointer-events-none"
class="absolute left-0 w-screen p-2 bg-pink-300/0 flex justify-center pointer-events-none"
:class="isEmbedEnabled ? 'bottom-16 mb-2' : 'bottom-4'"
>
<Transition
enter-active-class="transform ease-out duration-300 transition"
@@ -11,7 +12,11 @@
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<FormButton size="sm" class="pointer-events-auto" @click="trackAndResetFilters">
<FormButton
:size="isEmbedEnabled ? 'xs' : 'sm'"
class="pointer-events-auto"
@click="trackAndResetFilters"
>
Reset Filters
</FormButton>
</Transition>
@@ -20,11 +25,14 @@
<script setup lang="ts">
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useFilterUtilities } from '~~/lib/viewer/composables/ui'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
const {
resetFilters,
filters: { hasAnyFiltersApplied }
} = useFilterUtilities()
const { isEnabled: isEmbedEnabled } = useEmbed()
const mp = useMixpanel()
const trackAndResetFilters = () => {
resetFilters()
@@ -3,7 +3,7 @@
<div
v-show="viewerBusy"
:class="`absolute w-full max-w-screen h-1 bg-blue-500/20 overflow-hidden ${
showNavbar ? 'mt-14' : 'mt-0'
showNavbar && !isEmbedEnabled ? 'mt-14' : 'mt-0'
} text-xs text-foreground-on-primary z-50`"
>
<div class="swoosher absolute top-0 bg-blue-500/50 rounded-md"></div>
@@ -11,9 +11,13 @@
</div>
</template>
<script setup lang="ts">
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import { useViewerTour } from '~/lib/viewer/composables/tour'
import { useInjectedViewerInterfaceState } from '~~/lib/viewer/composables/setup'
const { isEnabled: isEmbedEnabled } = useEmbed()
const { viewerBusy } = useInjectedViewerInterfaceState()
const { showNavbar } = useTourStageState().value
const { showNavbar } = useViewerTour()
</script>
<style scoped>
.swoosher {
@@ -0,0 +1,149 @@
<template>
<div>
<ViewerPostSetupWrapper>
<div class="flex-1">
<!-- Nav -->
<Portal to="navigation">
<ViewerScope :state="state">
<HeaderNavLink
:to="`/projects/${project?.id}`"
:name="project?.name"
></HeaderNavLink>
<ViewerExplorerNavbarLink />
</ViewerScope>
</Portal>
<ClientOnly>
<!-- Tour host -->
<div
v-if="showTour"
class="fixed w-full h-[100dvh] flex justify-center items-center pointer-events-none z-[100]"
>
<TourOnboarding />
</div>
<!-- Viewer host -->
<div
class="special-gradient absolute z-10 overflow-hidden w-screen"
:class="
isEmbedEnabled
? isTransparent
? 'viewer-transparent h-[100dvh]'
: 'h-[calc(100dvh-3.5rem)]'
: 'h-[100dvh]'
"
>
<ViewerBase />
<Transition
enter-from-class="opacity-0"
enter-active-class="transition duration-1000"
>
<ViewerAnchoredPoints v-show="showControls" />
</Transition>
</div>
<!-- Global loading bar -->
<ViewerLoadingBar class="z-20" />
<!-- Sidebar controls -->
<Transition
enter-from-class="opacity-0"
enter-active-class="transition duration-1000"
>
<ViewerControls v-show="showControls" class="z-20" />
</Transition>
<!-- Viewer Object Selection Info Display -->
<Transition
v-if="!hideSelectionInfo"
enter-from-class="opacity-0"
enter-active-class="transition duration-1000"
>
<div v-show="showControls">
<ViewerSelectionSidebar class="z-20" />
</div>
</Transition>
<!-- Shows up when filters are applied for an easy return to normality -->
<ViewerGlobalFilterReset class="z-20" :embed="!!isEmbedEnabled" />
</ClientOnly>
</div>
</ViewerPostSetupWrapper>
<ViewerEmbedFooter
:name="modelName || 'Loading...'"
:date="lastUpdate"
:url="route.path"
/>
<Portal to="primary-actions">
<HeaderNavShare
v-if="project"
:resource-id-string="modelId"
:project-id="project.id"
:visibility="project.visibility"
/>
</Portal>
</div>
</template>
<script setup lang="ts">
import {
useSetupViewer,
type InjectableViewerState
} from '~~/lib/viewer/composables/setup'
import dayjs from 'dayjs'
import { graphql } from '~~/lib/common/generated/gql'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import { useViewerTour } from '~/lib/viewer/composables/tour'
const emit = defineEmits<{
setup: [InjectableViewerState]
}>()
const route = useRoute()
const { showTour, showControls } = useViewerTour()
const modelId = computed(() => route.params.modelId as string)
const projectId = computed(() => route.params.id as string)
const state = useSetupViewer({
projectId
})
const { isEnabled: isEmbedEnabled, hideSelectionInfo, isTransparent } = useEmbed()
emit('setup', state)
const {
resources: {
response: { project }
}
} = state
graphql(`
fragment ModelPageProject on Project {
id
createdAt
name
visibility
}
`)
const title = computed(() =>
project.value?.name.length ? `Viewer - ${project.value.name}` : ''
)
const modelName = computed(() => {
if (project.value?.models?.items && project.value.models.items.length > 0) {
return project.value.models.items[0].name
} else {
return project.value?.name
}
})
const lastUpdate = computed(() => {
if (project.value?.models?.items[0] && project.value.models.items[0].updatedAt) {
return 'Updated ' + dayjs(project.value.models.items[0].updatedAt).fromNow()
} else if (project.value) {
return 'Created ' + dayjs(project.value.createdAt).fromNow()
} else return undefined
})
useHead({ title })
</script>
@@ -1,12 +1,18 @@
<template>
<slot />
<slot v-if="!wrapper" />
<div v-else>
<slot />
</div>
</template>
<script setup lang="ts">
import { useSetupViewerScope } from '~~/lib/viewer/composables/setup'
import type { InjectableViewerState } from '~~/lib/viewer/composables/setup'
import {
useSetupViewerScope,
type InjectableViewerState
} from '~/lib/viewer/composables/setup/core'
const props = defineProps<{
state: InjectableViewerState
wrapper?: boolean
}>()
useSetupViewerScope(props.state)
@@ -24,7 +24,7 @@
</button>
<ViewerCommentsPortalOrDiv to="mobileComments">
<div
v-if="modelValue.isExpanded"
v-if="modelValue.isExpanded && !isEmbedEnabled"
class="bg-foundation px-2 py-2 text-sm text-primary sm:hidden font-medium flex justify-between items-center"
>
Add Comment
@@ -87,6 +87,9 @@ import {
} from '~~/lib/viewer/helpers/comments'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useThreadUtilities } from '~~/lib/viewer/composables/ui'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
const { isEnabled: isEmbedEnabled } = useEmbed()
const emit = defineEmits<{
(e: 'update:modelValue', v: ViewerNewThreadBubbleModel): void
@@ -48,12 +48,9 @@
]"
>
<div
class="relative w-full sm:w-80 flex py-2 pl-3 pr-2 sm:px-2 bg-foundation-2"
class="relative w-full sm:w-80 flex justify-between items-center py-2 pl-3 pr-2 sm:px-2 bg-foundation-2"
>
<div class="flex-grow flex items-center">
<span class="sm:hidden text-primary text-sm font-medium">
Discussions
</span>
<FormButton
v-tippy="'Previous'"
size="sm"
@@ -115,7 +112,7 @@
<div class="relative w-full sm:w-80 flex flex-col flex-1 justify-between">
<div
ref="commentsContainer"
class="max-h-[calc(50vh)] sm:max-h-[300px] 2xl:max-h-[500px] pb-20 overflow-y-auto simple-scrollbar flex flex-col space-y-1 pr-1"
class="max-h-[40vh] sm:max-h-[300px] 2xl:max-h-[500px] overflow-y-auto simple-scrollbar flex flex-col space-y-1 pr-1"
>
<div
v-if="!isThreadResourceLoaded"
@@ -147,12 +144,26 @@
</div>
</div>
<ViewerAnchoredPointThreadNewReply
v-if="!modelValue.archived && canReply"
v-if="showNewReplyComponent"
:model-value="modelValue"
@submit="onNewReply"
/>
<div
v-if="!canReply"
v-if="isEmbedEnabled"
class="flex justify-between w-full gap-2 p-2 mt-2"
>
<FormButton
:icon-right="ArrowTopRightOnSquareIcon"
full-width
:to="getLinkToThread(projectId, props.modelValue)"
external
target="_blank"
>
Reply in Speckle
</FormButton>
</div>
<div
v-if="!canReply && !isEmbedEnabled"
class="p-3 flex flex-col items-center justify-center bg-foundation-2"
>
<FormButton full-width @click="$emit('login')">Reply</FormButton>
@@ -201,6 +212,7 @@ import {
import { useDisableGlobalTextSelection } from '~~/lib/common/composables/window'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useThreadUtilities } from '~~/lib/viewer/composables/ui'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
const emit = defineEmits<{
(e: 'update:modelValue', v: CommentBubbleModel): void
@@ -214,9 +226,12 @@ const props = defineProps<{
modelValue: CommentBubbleModel
}>()
const { isEmbedEnabled } = useEmbed()
const threadId = computed(() => props.modelValue.id)
const { copy } = useClipboard()
const { activeUser } = useActiveUser()
const { isSmallerOrEqualSm } = useIsSmallerOrEqualThanBreakpoint()
const archiveComment = useArchiveComment()
const { triggerNotification } = useGlobalToast()
@@ -248,6 +263,15 @@ const comments = computed(() => [
...props.modelValue.replies.items.slice().reverse()
])
const showNewReplyComponent = computed(() => {
return (
!props.modelValue.archived &&
canReply.value &&
!isSmallerOrEqualSm.value &&
!isEmbedEnabled.value
)
})
// Note: conflicted with dragging styles, so took it out temporarily
// const { style } = useExpandedThreadResponsiveLocation({
// threadContainer,
@@ -1,7 +1,7 @@
<!-- eslint-disable vuejs-accessibility/no-autofocus -->
<template>
<div
class="absolute bottom-0 left-0 bg-foundation pl-4 pr-3 py-2 sm:py-1.5 rounded-b flex items-center w-full"
class="hidden sm:flex bg-foundation pl-4 pr-3 py-2 sm:py-1.5 rounded-b items-center w-full"
>
<FormButton
:icon-left="PaperClipIcon"
@@ -44,6 +44,7 @@
size="xs"
:icon-left="loadedVersionsOnly ? CheckCircleIcon : CheckCircleIconOutlined"
text
class="!text-left"
@click="
loadedVersionsOnly = loadedVersionsOnly ? undefined : 'loadedVersionsOnly'
"
@@ -1,7 +1,7 @@
<template>
<!-- eslint-disable-next-line vuejs-accessibility/click-events-have-key-events -->
<div
:class="`py-2 my-2 px-2 flex flex-col space-y-1 bg-foundation border-l-4 hover:shadow-lg hover:bg-primary-muted rounded transition cursor-pointer
:class="`py-1 sm:py-2 my-1 sm:my-2 px-2 flex flex-col space-y-1 bg-foundation border-l-4 hover:shadow-lg hover:bg-primary-muted rounded transition cursor-pointer
${isOpenInViewer ? 'border-primary' : 'border-transparent'}
`"
@click="open(thread.id)"
@@ -27,7 +27,7 @@
</span>
</span>
</div>
<div class="truncate text-sm mb-1">
<div class="truncate text-xs sm:text-sm mb-1">
{{ thread.rawText }}
</div>
<div
@@ -18,6 +18,5 @@ const props = defineProps({
})
const breakpoints = useBreakpoints(TailwindBreakpoints)
const isMobile = computed(() => breakpoints.smallerOrEqual('sm').value)
const isMobile = breakpoints.smallerOrEqual('sm')
</script>
@@ -1,8 +1,8 @@
<template>
<button
:class="`bg-foundation text-foreground shadow-md rounded-lg w-10 flex flex-col justify-center space-y-1 py-1`"
<div
:class="`bg-foundation text-foreground shadow-md rounded-lg w-8 md:w-10 flex flex-col justify-center md:gap-1 py-1`"
>
<slot></slot>
</button>
</div>
</template>
<script setup lang="ts"></script>
@@ -1,6 +1,6 @@
<template>
<button
:class="`transition rounded-lg w-10 h-10 flex items-center justify-center ${shadowClasses} ${colorClasses} active:scale-[0.9] outline-none`"
:class="`transition rounded-lg w-8 md:w-10 h-8 md:h-10 shrink-0 flex items-center justify-center ${shadowClasses} ${colorClasses} active:scale-[0.9] outline-none`"
>
<slot></slot>
</button>
@@ -0,0 +1,44 @@
<template>
<ClientOnly>
<div
v-if="isEmbedEnabled"
class="select-none fixed bottom-0 left-0 w-full z-20 flex gap-3 px-4 h-14 items-center"
:class="isTransparent ? 'bg-transparent' : 'bg-foundation'"
>
<HeaderLogoBlock
large-icon
to="https://speckle.systems/"
target="_blank"
show-text-on-mobile
:active="false"
/>
<div class="h-6 w-px bg-outline-3"></div>
<div class="flex flex-col">
<NuxtLink :to="url" target="_blank" class="leading-3">
<div class="flex items-center gap-1 w-full">
<h2 class="font-bold text-base text-sm truncate text-foreground">
{{ name }}
</h2>
<ArrowTopRightOnSquareIcon class="h-3 w-3" />
</div>
<span v-if="date" class="text-xs text-foreground-2">
{{ date }}
</span>
</NuxtLink>
</div>
</div>
</ClientOnly>
</template>
<script setup lang="ts">
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/20/solid'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
defineProps<{
date?: string
name?: string
url?: string
}>()
const { isEmbedEnabled, isTransparent } = useEmbed()
</script>
@@ -0,0 +1,46 @@
<template>
<div>
<button
class="group flex items-center justify-center absolute inset-0"
@click="$emit('play')"
>
<div v-if="previewUrl" class="absolute inset-0">
<PreviewImage :preview-url="previewUrl" />
</div>
<div
class="relative z-10 pointer-events-none group-hover:scale-110 group-hover:shadow-xl shadow h-14 w-14 rounded-full border border-foundation bg-primary flex items-center justify-center transition -mt-10"
>
<PlayIcon class="h-6 w-6 ml-[3px] text-foundation" />
</div>
</button>
<ViewerEmbedFooter :url="projectUrl" name="View in Speckle" />
</div>
</template>
<script setup lang="ts">
import { PlayIcon } from '@heroicons/vue/20/solid'
const route = useRoute()
const {
public: { apiOrigin }
} = useRuntimeConfig()
const projectUrl = route.path
const projectId = route.params.id as string
const modelId = route.params.modelId as string
const previewUrl = computed(() => {
if (modelId) {
const url = new URL(`/preview/${projectId}/commits/${modelId}`, apiOrigin)
return url.toString()
} else if (projectId) {
const url = new URL(`/preview/${projectId}`, apiOrigin)
return url.toString()
} else return null
})
defineEmits<{
(e: 'play'): void
}>()
</script>
@@ -3,7 +3,7 @@
<PopoverButton v-slot="{ open }" as="template">
<ViewerControlsButtonToggle flat secondary :active="open || isActive">
<!-- <ChevronUpDownIcon class="w-5 h-5 rotate-45" /> -->
<IconExplode class="h-5 w-5" />
<IconExplode class="h-4 w-4 sm:h-5 sm:w-5" />
</ViewerControlsButtonToggle>
</PopoverButton>
<Transition
@@ -15,20 +15,22 @@
leave-to-class="opacity-0"
>
<PopoverPanel
class="absolute translate-x-0 left-12 top-2 p-2 bg-foundation max-h-64 simple-scrollbar overflow-y-auto outline outline-2 outline-primary-muted rounded-lg shadow-lg overflow-hidden flex flex-col space-y-2"
class="absolute translate-x-0 left-12 top-0 p-2 bg-foundation max-h-64 simple-scrollbar overflow-y-auto outline outline-2 outline-primary-muted rounded-lg shadow-lg overflow-hidden flex flex-col space-y-2"
>
<div class="flex items-center space-x-1">
<input
id="intensity"
v-model="explodeFactor"
class="h-2 mr-2"
class="w-24 sm:w-32 h-2 mr-2"
type="range"
name="intensity"
min="0"
max="1"
step="0.01"
/>
<label class="text-sm text-foreground-2" for="intensity">Intensity</label>
<label class="text-xs sm:text-sm text-foreground-2" for="intensity">
Intensity
</label>
</div>
</PopoverPanel>
</Transition>
@@ -40,8 +40,8 @@
</div>
</template>
<div
:class="`relative flex flex-col space-y-2 py-2 px-2 simple-scrollbar overflow-y-scroll overflow-x-hidden shadow-inner ${
showAllFilters ? 'h-44 visible' : 'h-0 invisible'
:class="`relative flex flex-col space-y-2 px-2 simple-scrollbar overflow-y-scroll overflow-x-hidden shadow-inner ${
showAllFilters ? 'h-44 visible py-2' : 'h-0 invisible py-1'
} transition-[height] border-b-2 border-primary-muted`"
>
<div class="sticky top-0">
@@ -9,7 +9,7 @@
} ${isSelected ? 'border-primary bg-primary-muted' : 'border-transparent'}`"
@click="setSelection()"
>
<div class="flex space-x-2 items-center flex-shrink truncate text-sm">
<div class="flex space-x-2 items-center flex-shrink truncate text-xs sm:text-sm">
<span
v-if="color"
class="w-3 h-3 rounded"
@@ -1,14 +1,17 @@
<template>
<div class="bg-foundation sm:rounded-lg overflow-hidden shadow flex flex-col">
<div class="bg-foundation rounded-lg overflow-hidden shadow flex flex-col">
<div class="sticky top-0 z-50 flex flex-col bg-foundation">
<div v-if="!hideClose" class="absolute top-2 right-2 sm:right-0 z-10">
<div
v-if="!hideClose"
class="absolute top-1.5 sm:top-2 right-0.5 sm:right-0 z-10"
>
<FormButton size="sm" color="secondary" text @click="$emit('close')">
<XMarkIcon class="h-4 w-4 sm:h-3 sm:w-3 text-primary sm:text-foreground" />
</FormButton>
</div>
<div
v-if="$slots.title"
class="flex items-center h-10 px-3 border-b border-outline-3 dark:border-foundation-2 bg-foundation rounded-t"
class="flex items-center py-2 px-3 border-b border-outline-3 dark:border-foundation-2 bg-foundation"
>
<div
class="flex items-center h-full w-full pr-8 font-semibold sm:font-bold text-sm text-primary"
@@ -21,7 +24,7 @@
</div>
<div
v-if="$slots.actions"
class="flex items-center h-8 sm:h-10 gap-2 px-2"
class="flex items-center py-1 sm:py-2 gap-2 px-2"
:class="
moveActionsToBottom
? 'order-3 border-t border-outline-3 mt-2'
@@ -2,9 +2,9 @@
<ViewerLayoutPanel move-actions-to-bottom @close="$emit('close')">
<template #title>Measure Mode</template>
<div
class="flex items-center gap-2 text-sm px-3 py-2 border-b border-outline-3 text-foreground-2"
class="flex items-center gap-2 text-xs sm:text-sm px-3 py-1.5 sm:py-2 border-b border-outline-3 text-foreground-2"
>
<InformationCircleIcon class="h-6 h-6 shrink-0" />
<InformationCircleIcon class="h-5 w-5 sm:h-6 sm:h-6 shrink-0" />
<span class="max-w-[210px]">
Reloading the model will delete all measurements.
</span>
@@ -21,9 +21,9 @@
Delete Selected
</FormButton>
</template>
<div class="p-3 flex flex-col gap-3 border-b border-outline-3">
<div class="px-3 py-2 sm:p-3 flex flex-col gap-3 border-b border-outline-3">
<div>
<h6 class="font-semibold text-sm mb-2">Measurement Type</h6>
<h6 class="font-semibold text-xs sm:text-sm mb-2">Measurement Type</h6>
<FormRadio
v-for="option in measurementTypeOptions"
:key="option.value"
@@ -36,26 +36,28 @@
/>
</div>
</div>
<div class="p-3 flex items-center border-b border-outline-3">
<div class="py-2 px-3 sm:p-3 flex items-center border-b border-outline-3">
<FormCheckbox
name="Snap to Objects"
hide-label
:model-value="measurementParams.vertexSnap"
@update:model-value="() => toggleMeasurementsSnap()"
/>
<span class="font-normal text-sm">Snap to Vertices</span>
<span class="font-normal text-xs sm:text-sm">Snap to Vertices</span>
</div>
<div class="p-3 flex flex-col gap-3">
<div class="flex flex-col gap-2">
<h6 class="font-semibold text-sm">Units</h6>
<h6 class="font-semibold text-xs sm:text-sm">Units</h6>
<ViewerMeasurementsUnitSelect
v-model="selectedUnit"
mount-menu-on-body
@update:model-value="onChangeMeasurementUnits"
/>
</div>
<div class="flex flex-col gap-3">
<label class="font-semibold text-sm" for="precision">Precision</label>
<div class="flex flex-col gap-2 sm:gap-3">
<label class="font-semibold text-xs sm:text-sm" for="precision">
Precision
</label>
<div class="flex gap-2 items-center">
<input
id="precision"
@@ -21,7 +21,7 @@
{{ showRemove ? 'Done' : 'Remove' }}
</FormButton>
</template>
<div class="flex flex-col space-y-2 px-1 py-2">
<div class="flex flex-col space-y-2 px-1 py-3">
<template v-if="resourceItems.length">
<div
v-for="({ model, versionId }, index) in modelsAndVersionIds"
@@ -12,7 +12,7 @@
<div
:class="`${
showVersions ? 'bg-primary' : 'bg-foundation hover:bg-primary-muted'
} group sticky cursor-pointer top-0 z-20 flex h-20 min-w-0 max-w-full items-center justify-between space-x-2 p-2 transition select-none`"
} group sticky cursor-pointer top-0 z-20 flex h-10 sm:h-20 min-w-0 max-w-full items-center justify-between space-x-2 p-2 transition select-none`"
@click="showVersions = !showVersions"
>
<div>
@@ -23,7 +23,7 @@
v-tippy="modelName.subheader ? model.name : null"
:class="`${
showVersions ? 'text-foundation' : ''
} font-bold truncate min-w-0`"
} text-sm sm:text-base font-bold truncate min-w-0`"
>
{{ modelName.header }}
</div>
@@ -1,7 +1,7 @@
<template>
<LayoutDialog v-model:open="open" max-width="lg">
<template #header>Add Model</template>
<div class="flex flex-col space-y-4">
<div class="flex flex-col gap-y-4">
<LayoutTabs v-slot="{ activeItem }" :items="tabItems">
<ViewerResourcesAddModelDialogModelTab
v-if="activeItem.id === 'model'"
@@ -1,13 +1,14 @@
<template>
<div class="flex flex-col space-y-2">
<div class="flex flex-col gap-y-2">
<div class="flex justify-end">
<FormTextInput
v-model="search"
name="modelsearch"
:show-label="false"
:size="isSmallerOrEqualSm ? 'sm' : 'base'"
placeholder="Search"
color="foundation"
class="w-60"
class="w-48 sm:w-60"
:show-clear="search !== ''"
auto-focus
@change="updateSearchImmediately"
@@ -21,6 +22,7 @@
:project="project"
:project-id="project.id"
:excluded-ids="alreadyLoadedModelIds"
:small-view="true"
:show-actions="false"
:show-versions="false"
disable-default-links
@@ -34,12 +36,14 @@
<script setup lang="ts">
import { debounce } from 'lodash-es'
import { useInjectedViewerLoadedResources } from '~~/lib/viewer/composables/setup'
import { useIsSmallerOrEqualThanBreakpoint } from '~~/composables/browser'
const emit = defineEmits<{
(e: 'chosen', val: { modelId: string }): void
}>()
const { project, resourceItems } = useInjectedViewerLoadedResources()
const { isSmallerOrEqualSm } = useIsSmallerOrEqualThanBreakpoint()
const search = ref('')
const debouncedSearch = ref('')
@@ -1,23 +1,29 @@
<template>
<div class="flex flex-col space-y-4">
<div class="text-foreground normal">
<div class="flex flex-col gap-y-4">
<div class="text-foreground normal text-sm sm:text-base">
Add objects from the current project by their IDs or an Object URL.
</div>
<form
class="flex flex-col space-y-4 sm:space-y-0 sm:flex-row sm:space-x-4 w-full"
class="flex flex-col gap-y-4 sm:space-y-0 sm:flex-row sm:space-x-4 w-full"
@submit="onSubmit"
>
<FormTextInput
name="objectIdsOrUrl"
label="Value"
full-width
size="lg"
:size="isSmallerOrEqualSm ? 'base' : 'lg'"
:custom-icon="LinkIcon"
:rules="[isRequired, isValidValue]"
placeholder="Comma-delimited object IDs/URLs"
auto-focus
/>
<FormButton :icon-left="PlusIcon" size="lg" submit>Add</FormButton>
<FormButton
:icon-left="PlusIcon"
:size="isSmallerOrEqualSm ? 'base' : 'lg'"
submit
>
Add
</FormButton>
</form>
</div>
</template>
@@ -29,6 +35,7 @@ import { isRequired } from '~~/lib/common/helpers/validation'
import { isObjectId } from '~~/lib/common/helpers/resources'
import { useInjectedViewerLoadedResources } from '~~/lib/viewer/composables/setup'
import { difference } from 'lodash-es'
import { useIsSmallerOrEqualThanBreakpoint } from '~~/composables/browser'
const emit = defineEmits<{
(e: 'chosen', val: { objectIds: string[] }): void
@@ -39,6 +46,7 @@ const urlRegexp = /\/models\/([a-zA-Z0-_9,@$]+)$/i
const { handleSubmit } = useForm<FormPayload>()
const { resourceItems } = useInjectedViewerLoadedResources()
const { isSmallerOrEqualSm } = useIsSmallerOrEqualThanBreakpoint()
const explodeValidatedObjectIds = (commaDelimitedIdList: string) => {
const idParts = commaDelimitedIdList.split(',')
@@ -1,10 +1,12 @@
<template>
<ViewerCommentsPortalOrDiv v-if="objects.length !== 0" to="bottomPanel">
<div
:class="`sm:bg-foundation simple-scrollbar z-10 relative sm:fixed sm:top-16 sm:right-4 sm:top-[4rem] sm:right-4 sm:mb-4 sm:max-w-64 min-h-[4.75rem] max-h-[50vh] sm:max-h-[calc(100dvh-5.5rem)] w-full sm:w-64 overflow-y-auto sm:rounded-md sm:shadow transition ${
:class="`sm:bg-foundation simple-scrollbar z-10 relative sm:fixed sm:right-4 sm:right-4 sm:mb-4 sm:max-w-64 min-h-[3rem] sm:min-h-[4.75rem] max-h-[50vh] sm:max-h-[calc(100vh-5.5rem)] w-full sm:w-64 overflow-y-auto sm:rounded-md sm:shadow transition ${
objects.length !== 0
? 'translate-x-0 opacity-100'
: 'translate-x-[120%] opacity-0'
} ${isEmbedEnabled ? 'sm:top-2' : 'sm:top-[4rem]'} ${
focusedThreadId && isSmallerOrEqualSm ? 'hidden' : ''
}`"
>
<ViewerLayoutPanel @close="trackAndClearSelection()">
@@ -73,16 +75,22 @@ import { containsAll } from '~~/lib/common/helpers/utils'
import { useFilterUtilities, useSelectionUtilities } from '~~/lib/viewer/composables/ui'
import { uniqWith } from 'lodash-es'
import { useMixpanel } from '~~/lib/core/composables/mp'
import { useIsSmallerOrEqualThanBreakpoint } from '~~/composables/browser'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
const {
viewer: {
metadata: { filteringState }
},
urlHashState: { focusedThreadId },
ui: { diff }
} = useInjectedViewerState()
const { objects, clearSelection } = useSelectionUtilities()
const { hideObjects, showObjects, isolateObjects, unIsolateObjects } =
useFilterUtilities()
const { isEmbedEnabled } = useEmbed()
const { isSmallerOrEqualSm } = useIsSmallerOrEqualThanBreakpoint()
const itemCount = ref(42)
@@ -14,59 +14,67 @@
leave-to-class="opacity-0"
>
<PopoverPanel
class="absolute translate-x-0 left-12 top-2 p-2 bg-foundation max-h-64 simple-scrollbar overflow-y-auto outline outline-2 outline-primary-muted rounded-lg shadow-lg overflow-hidden flex flex-col space-y-2"
class="absolute translate-x-0 left-10 sm:left-12 top-2 p-2 bg-foundation max-h-64 simple-scrollbar overflow-y-auto outline outline-2 outline-primary-muted rounded-lg shadow-lg overflow-hidden flex flex-col space-y-2"
>
<div class="flex items-center space-x-1">
<input
id="intensity"
v-model="intensity"
class="h-2 mr-2"
class="w-24 sm:w-32 h-2 mr-2"
type="range"
name="intensity"
min="1"
max="10"
step="0.05"
/>
<label class="text-sm text-foreground-2" for="intensity">Intensity</label>
<label class="text-xs sm:text-sm text-foreground-2" for="intensity">
Intensity
</label>
</div>
<div class="flex items-center space-x-1">
<input
id="elevation"
v-model="elevation"
class="h-2 mr-2"
class="w-24 sm:w-32 h-2 mr-2"
type="range"
name="elevation"
min="0"
:max="Math.PI"
step="0.05"
/>
<label class="text-sm text-foreground-2" for="elevation">Elevation</label>
<label class="text-xs sm:text-sm text-foreground-2" for="elevation">
Elevation
</label>
</div>
<div class="flex items-center space-x-1">
<input
id="azimuth"
v-model="azimuth"
class="h-2 mr-2"
class="w-24 sm:w-32 h-2 mr-2"
type="range"
name="azimuth"
:min="-Math.PI * 0.5"
:max="Math.PI * 0.5"
step="0.05"
/>
<label class="text-sm text-foreground-2" for="azimuth">Azimuth</label>
<label class="text-xs sm:text-sm text-foreground-2" for="azimuth">
Azimuth
</label>
</div>
<div class="flex items-center space-x-1">
<input
id="indirect"
v-model="indirectLightIntensity"
class="h-2 mr-2"
class="w-24 sm:w-32 h-2 mr-2"
type="range"
name="indirect"
min="0"
max="5"
step="0.05"
/>
<label class="text-sm text-foreground-2" for="indirect">Indirect</label>
<label class="text-xs sm:text-sm text-foreground-2" for="indirect">
Indirect
</label>
</div>
</PopoverPanel>
</Transition>
@@ -14,7 +14,7 @@
leave-to-class="opacity-0"
>
<MenuItems
class="absolute translate-x-0 w-32 left-12 top-2 bg-foundation max-h-64 simple-scrollbar overflow-y-auto outline outline-2 outline-primary-muted rounded-lg shadow-lg overflow-hidden flex flex-col"
class="absolute translate-x-0 w-24 sm:w-32 left-10 sm:left-12 -top-1 sm:top-2 bg-foundation max-h-64 simple-scrollbar overflow-y-auto outline outline-2 outline-primary-muted rounded-lg shadow-lg overflow-hidden flex flex-col"
>
<!-- Canonical views first -->
<MenuItem
@@ -27,7 +27,7 @@
:class="{
'bg-primary text-foreground-on-primary': active,
'text-foreground': !active,
'text-sm py-2 transition': true
'text-xs sm:text-sm py-2 transition': true
}"
@click="setView(view.name.toLowerCase() as CanonicalView)"
>
+5 -5
View File
@@ -43,10 +43,10 @@ export const useClipboard = () => {
export const useIsSmallerOrEqualThanBreakpoint = () => {
const breakpoints = useBreakpoints(TailwindBreakpoints)
return {
isSmallerOrEqualSm: computed(() => breakpoints.smallerOrEqual('sm').value),
isSmallerOrEqualMd: computed(() => breakpoints.smallerOrEqual('md').value),
isSmallerOrEqualLg: computed(() => breakpoints.smallerOrEqual('lg').value),
isSmallerOrEqualXl: computed(() => breakpoints.smallerOrEqual('xl').value),
isSmallerOrEqual2xl: computed(() => breakpoints.smallerOrEqual('2xl').value)
isSmallerOrEqualSm: breakpoints.smallerOrEqual('sm'),
isSmallerOrEqualMd: breakpoints.smallerOrEqual('md'),
isSmallerOrEqualLg: breakpoints.smallerOrEqual('lg'),
isSmallerOrEqualXl: breakpoints.smallerOrEqual('xl'),
isSmallerOrEqual2xl: breakpoints.smallerOrEqual('2xl')
}
}
-10
View File
@@ -1,10 +0,0 @@
export const useTextInputGlobalFocus = () =>
useState<boolean>('text-input-focus', () => false)
export const useTourStageState = () =>
useState('global-ui-element-state', () => ({
showNavbar: true,
showViewerControls: true,
showTour: false,
showSegmentation: true
}))
+26 -23
View File
@@ -1,45 +1,48 @@
<template>
<div :class="`relative min-h-full`">
<div class="relative min-h-full">
<div
v-if="debug"
class="pointer-events-none fixed bottom-0 z-40 flex w-full space-x-2 p-3 text-xs"
>
<FormButton
class="pointer-events-auto"
size="xs"
@click="tourState.showNavbar = !tourState.showNavbar"
>
<FormButton class="pointer-events-auto" size="xs" @click="toggleNavbar">
nav
</FormButton>
<FormButton
class="pointer-events-auto"
size="xs"
@click="tourState.showViewerControls = !tourState.showViewerControls"
>
<FormButton class="pointer-events-auto" size="xs" @click="toggleViewerControls">
viewer ctrls
</FormButton>
<FormButton
class="pointer-events-auto"
size="xs"
@click="tourState.showTour = !tourState.showTour"
>
<FormButton class="pointer-events-auto" size="xs" @click="toggleTour">
tour ctrls
</FormButton>
<!-- <span>{{ tourState }}</span> -->
</div>
<Transition
enter-from-class="opacity-0"
enter-active-class="transition duration-1000"
>
<HeaderNavBar v-show="tourState.showNavbar" class="relative z-20 mb-6" />
</Transition>
<ClientOnly>
<Transition
enter-from-class="opacity-0"
enter-active-class="transition duration-1000"
>
<HeaderNavBar v-show="showNavbar" class="relative z-20 mb-6" />
</Transition>
</ClientOnly>
<main class="absolute top-0 left-0 z-10 h-[100dvh] w-screen">
<slot />
</main>
</div>
</template>
<script setup lang="ts">
const tourState = useTourStageState()
import { useViewerTour } from '~/lib/viewer/composables/tour'
const { showNavbar, showTour, showControls } = useViewerTour()
const debug = ref(false)
const toggleNavbar = () => {
showNavbar.value = !showNavbar.value
}
const toggleTour = () => {
showTour.value = !showTour.value
}
const toggleViewerControls = () => {
showControls.value = !showControls.value
}
</script>
@@ -1,14 +1,20 @@
import type { CookieOptions } from '#app'
import dayjs from 'dayjs'
import { useScopedState } from '~~/lib/common/composables/scopedState'
/**
* Makes useCookie() synchronized across the app so that a change to it from one place
* will also update other references elsewhere
* will also update other references elsewhere.
*
* Defaults to an expiration date of 1 year
*/
export const useSynchronizedCookie = <CookieValue = string>(
name: string,
opts?: CookieOptions<CookieValue>
) =>
useScopedState(`synchronizedCookiesState-${name}`, () =>
useCookie<CookieValue>(name, opts)
useCookie<CookieValue>(name, {
expires: dayjs().add(1, 'year').toDate(),
...(opts || {})
})
)
@@ -25,7 +25,7 @@ const documents = {
"\n fragment ProjectDiscussionsPageHeader_Project on Project {\n id\n name\n }\n": types.ProjectDiscussionsPageHeader_ProjectFragmentDoc,
"\n fragment ProjectDiscussionsPageResults_Project on Project {\n id\n }\n": types.ProjectDiscussionsPageResults_ProjectFragmentDoc,
"\n fragment ProjectModelPageHeaderProject on Project {\n id\n name\n role\n model(id: $modelId) {\n id\n name\n description\n }\n }\n": types.ProjectModelPageHeaderProjectFragmentDoc,
"\n fragment ProjectModelPageVersionsPagination on Project {\n id\n model(id: $modelId) {\n id\n versions(limit: 16, cursor: $versionsCursor) {\n cursor\n totalCount\n items {\n ...ProjectModelPageVersionsCardVersion\n }\n }\n }\n }\n": types.ProjectModelPageVersionsPaginationFragmentDoc,
"\n fragment ProjectModelPageVersionsPagination on Project {\n id\n visibility\n model(id: $modelId) {\n id\n versions(limit: 16, cursor: $versionsCursor) {\n cursor\n totalCount\n items {\n ...ProjectModelPageVersionsCardVersion\n }\n }\n }\n }\n": types.ProjectModelPageVersionsPaginationFragmentDoc,
"\n fragment ProjectModelPageVersionsProject on Project {\n ...ProjectPageProjectHeader\n model(id: $modelId) {\n id\n name\n pendingImportedVersions {\n ...PendingFileUpload\n }\n }\n ...ProjectModelPageVersionsPagination\n }\n": types.ProjectModelPageVersionsProjectFragmentDoc,
"\n fragment ProjectModelPageDialogDeleteVersion on Version {\n id\n message\n }\n": types.ProjectModelPageDialogDeleteVersionFragmentDoc,
"\n fragment ProjectModelPageDialogEditMessageVersion on Version {\n id\n message\n }\n": types.ProjectModelPageDialogEditMessageVersionFragmentDoc,
@@ -36,9 +36,9 @@ const documents = {
"\n fragment ProjectPageProjectHeader on Project {\n id\n role\n name\n description\n visibility\n allowPublicComments\n }\n": types.ProjectPageProjectHeaderFragmentDoc,
"\n fragment ProjectPageLatestItemsComments on Project {\n id\n commentThreadCount: commentThreads(limit: 0) {\n totalCount\n }\n }\n": types.ProjectPageLatestItemsCommentsFragmentDoc,
"\n fragment ProjectPageLatestItemsCommentItem on Comment {\n id\n author {\n ...FormUsersSelectItem\n }\n screenshot\n rawText\n createdAt\n updatedAt\n archived\n repliesCount: replies(limit: 0) {\n totalCount\n }\n replyAuthors(limit: 4) {\n totalCount\n items {\n ...FormUsersSelectItem\n }\n }\n }\n": types.ProjectPageLatestItemsCommentItemFragmentDoc,
"\n fragment ProjectPageLatestItemsModels on Project {\n id\n role\n modelCount: models(limit: 0) {\n totalCount\n }\n }\n": types.ProjectPageLatestItemsModelsFragmentDoc,
"\n fragment ProjectPageLatestItemsModels on Project {\n id\n role\n visibility\n modelCount: models(limit: 0) {\n totalCount\n }\n }\n": types.ProjectPageLatestItemsModelsFragmentDoc,
"\n fragment ProjectPageModelsActions on Model {\n id\n name\n }\n": types.ProjectPageModelsActionsFragmentDoc,
"\n fragment ProjectPageModelsCardProject on Project {\n id\n role\n }\n": types.ProjectPageModelsCardProjectFragmentDoc,
"\n fragment ProjectPageModelsCardProject on Project {\n id\n role\n visibility\n }\n": types.ProjectPageModelsCardProjectFragmentDoc,
"\n fragment ModelPreview on Model {\n previewUrl\n }\n": types.ModelPreviewFragmentDoc,
"\n fragment SingleLevelModelTreeItem on ModelsTreeItem {\n id\n name\n fullName\n model {\n ...ProjectPageLatestItemsModelItem\n }\n hasChildren\n updatedAt\n }\n": types.SingleLevelModelTreeItemFragmentDoc,
"\n fragment ModelCardAutomationStatus_AutomationsStatus on AutomationsStatus {\n id\n status\n statusMessage\n automationRuns {\n id\n automationId\n automationName\n createdAt\n status\n functionRuns {\n id\n functionId\n functionName\n functionLogo\n elapsed\n status\n statusMessage\n contextView\n results\n resultVersions {\n id\n model {\n id\n name\n }\n }\n }\n }\n }\n": types.ModelCardAutomationStatus_AutomationsStatusFragmentDoc,
@@ -59,6 +59,7 @@ const documents = {
"\n fragment UserProfileEditDialogBio_User on User {\n id\n name\n company\n bio\n ...UserProfileEditDialogAvatar_User\n }\n": types.UserProfileEditDialogBio_UserFragmentDoc,
"\n fragment UserProfileEditDialogDeleteAccount_User on User {\n id\n email\n }\n": types.UserProfileEditDialogDeleteAccount_UserFragmentDoc,
"\n fragment UserProfileEditDialogNotificationPreferences_User on User {\n id\n notificationPreferences\n }\n": types.UserProfileEditDialogNotificationPreferences_UserFragmentDoc,
"\n fragment ModelPageProject on Project {\n id\n createdAt\n name\n visibility\n }\n": types.ModelPageProjectFragmentDoc,
"\n fragment ThreadCommentAttachment on Comment {\n text {\n attachments {\n id\n fileName\n fileType\n fileSize\n }\n }\n }\n": types.ThreadCommentAttachmentFragmentDoc,
"\n fragment ViewerCommentsListItem on Comment {\n id\n rawText\n archived\n author {\n ...LimitedUserAvatar\n }\n createdAt\n viewedAt\n replies {\n totalCount\n cursor\n items {\n ...ViewerCommentsReplyItem\n }\n }\n replyAuthors(limit: 4) {\n totalCount\n items {\n ...FormUsersSelectItem\n }\n }\n resources {\n resourceId\n resourceType\n }\n }\n": types.ViewerCommentsListItemFragmentDoc,
"\n fragment ViewerModelVersionCardItem on Version {\n id\n message\n referencedObject\n sourceApplication\n createdAt\n previewUrl\n authorUser {\n ...LimitedUserAvatar\n }\n }\n": types.ViewerModelVersionCardItemFragmentDoc,
@@ -167,7 +168,6 @@ const documents = {
"\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n stream(id: $streamId) {\n commit(id: $commitId) {\n id\n branch {\n id\n }\n }\n }\n }\n": types.LegacyViewerCommitRedirectMetadataDocument,
"\n query ResolveCommentLink($commentId: String!, $projectId: String!) {\n comment(id: $commentId, streamId: $projectId) {\n ...LinkableComment\n }\n }\n": types.ResolveCommentLinkDocument,
"\n fragment ProjectPageProject on Project {\n id\n createdAt\n ...ProjectPageProjectHeader\n ...ProjectPageStatsBlockTeam\n ...ProjectPageTeamDialog\n ...ProjectPageStatsBlockVersions\n ...ProjectPageStatsBlockModels\n ...ProjectPageStatsBlockComments\n ...ProjectPageLatestItemsModels\n ...ProjectPageLatestItemsComments\n }\n": types.ProjectPageProjectFragmentDoc,
"\n fragment ModelPageProject on Project {\n id\n createdAt\n name\n }\n": types.ModelPageProjectFragmentDoc,
};
/**
@@ -235,7 +235,7 @@ export function graphql(source: "\n fragment ProjectModelPageHeaderProject on P
/**
* 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 ProjectModelPageVersionsPagination on Project {\n id\n model(id: $modelId) {\n id\n versions(limit: 16, cursor: $versionsCursor) {\n cursor\n totalCount\n items {\n ...ProjectModelPageVersionsCardVersion\n }\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectModelPageVersionsPagination on Project {\n id\n model(id: $modelId) {\n id\n versions(limit: 16, cursor: $versionsCursor) {\n cursor\n totalCount\n items {\n ...ProjectModelPageVersionsCardVersion\n }\n }\n }\n }\n"];
export function graphql(source: "\n fragment ProjectModelPageVersionsPagination on Project {\n id\n visibility\n model(id: $modelId) {\n id\n versions(limit: 16, cursor: $versionsCursor) {\n cursor\n totalCount\n items {\n ...ProjectModelPageVersionsCardVersion\n }\n }\n }\n }\n"): (typeof documents)["\n fragment ProjectModelPageVersionsPagination on Project {\n id\n visibility\n model(id: $modelId) {\n id\n versions(limit: 16, cursor: $versionsCursor) {\n cursor\n totalCount\n items {\n ...ProjectModelPageVersionsCardVersion\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -279,7 +279,7 @@ export function graphql(source: "\n fragment ProjectPageLatestItemsCommentItem
/**
* 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 ProjectPageLatestItemsModels on Project {\n id\n role\n modelCount: models(limit: 0) {\n totalCount\n }\n }\n"): (typeof documents)["\n fragment ProjectPageLatestItemsModels on Project {\n id\n role\n modelCount: models(limit: 0) {\n totalCount\n }\n }\n"];
export function graphql(source: "\n fragment ProjectPageLatestItemsModels on Project {\n id\n role\n visibility\n modelCount: models(limit: 0) {\n totalCount\n }\n }\n"): (typeof documents)["\n fragment ProjectPageLatestItemsModels on Project {\n id\n role\n visibility\n modelCount: models(limit: 0) {\n totalCount\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -287,7 +287,7 @@ export function graphql(source: "\n fragment ProjectPageModelsActions on Model
/**
* 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 ProjectPageModelsCardProject on Project {\n id\n role\n }\n"): (typeof documents)["\n fragment ProjectPageModelsCardProject on Project {\n id\n role\n }\n"];
export function graphql(source: "\n fragment ProjectPageModelsCardProject on Project {\n id\n role\n visibility\n }\n"): (typeof documents)["\n fragment ProjectPageModelsCardProject on Project {\n id\n role\n visibility\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -368,6 +368,10 @@ export function graphql(source: "\n fragment UserProfileEditDialogDeleteAccount
* 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 UserProfileEditDialogNotificationPreferences_User on User {\n id\n notificationPreferences\n }\n"): (typeof documents)["\n fragment UserProfileEditDialogNotificationPreferences_User on User {\n id\n notificationPreferences\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 fragment ModelPageProject on Project {\n id\n createdAt\n name\n visibility\n }\n"): (typeof documents)["\n fragment ModelPageProject on Project {\n id\n createdAt\n name\n visibility\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -800,10 +804,6 @@ export function graphql(source: "\n query ResolveCommentLink($commentId: 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 fragment ProjectPageProject on Project {\n id\n createdAt\n ...ProjectPageProjectHeader\n ...ProjectPageStatsBlockTeam\n ...ProjectPageTeamDialog\n ...ProjectPageStatsBlockVersions\n ...ProjectPageStatsBlockModels\n ...ProjectPageStatsBlockComments\n ...ProjectPageLatestItemsModels\n ...ProjectPageLatestItemsComments\n }\n"): (typeof documents)["\n fragment ProjectPageProject on Project {\n id\n createdAt\n ...ProjectPageProjectHeader\n ...ProjectPageStatsBlockTeam\n ...ProjectPageTeamDialog\n ...ProjectPageStatsBlockVersions\n ...ProjectPageStatsBlockModels\n ...ProjectPageStatsBlockComments\n ...ProjectPageLatestItemsModels\n ...ProjectPageLatestItemsComments\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 fragment ModelPageProject on Project {\n id\n createdAt\n name\n }\n"): (typeof documents)["\n fragment ModelPageProject on Project {\n id\n createdAt\n name\n }\n"];
export function graphql(source: string) {
return (documents as any)[source] ?? {};
@@ -2948,7 +2948,7 @@ export type ProjectDiscussionsPageResults_ProjectFragment = { __typename?: 'Proj
export type ProjectModelPageHeaderProjectFragment = { __typename?: 'Project', id: string, name: string, role?: string | null, model: { __typename?: 'Model', id: string, name: string, description?: string | null } };
export type ProjectModelPageVersionsPaginationFragment = { __typename?: 'Project', id: string, model: { __typename?: 'Model', id: string, versions: { __typename?: 'VersionCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Version', id: string, message?: string | null, createdAt: string, previewUrl: string, sourceApplication?: string | null, authorUser?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> } } };
export type ProjectModelPageVersionsPaginationFragment = { __typename?: 'Project', id: string, visibility: ProjectVisibility, model: { __typename?: 'Model', id: string, versions: { __typename?: 'VersionCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Version', id: string, message?: string | null, createdAt: string, previewUrl: string, sourceApplication?: string | null, authorUser?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> } } };
export type ProjectModelPageVersionsProjectFragment = { __typename?: 'Project', id: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, model: { __typename?: 'Model', id: string, name: string, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, versions: { __typename?: 'VersionCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Version', id: string, message?: string | null, createdAt: string, previewUrl: string, sourceApplication?: string | null, authorUser?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> } } };
@@ -2962,7 +2962,7 @@ export type ProjectModelPageVersionsCardVersionFragment = { __typename?: 'Versio
export type ProjectModelsPageHeader_ProjectFragment = { __typename?: 'Project', id: string, name: string, sourceApps: Array<string>, role?: string | null, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> };
export type ProjectModelsPageResults_ProjectFragment = { __typename?: 'Project', id: string, role?: string | null, modelCount: { __typename?: 'ModelCollection', totalCount: number } };
export type ProjectModelsPageResults_ProjectFragment = { __typename?: 'Project', id: string, role?: string | null, visibility: ProjectVisibility, modelCount: { __typename?: 'ModelCollection', totalCount: number } };
export type ProjectPageProjectHeaderFragment = { __typename?: 'Project', id: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean };
@@ -2970,11 +2970,11 @@ export type ProjectPageLatestItemsCommentsFragment = { __typename?: 'Project', i
export type ProjectPageLatestItemsCommentItemFragment = { __typename?: 'Comment', id: string, screenshot?: string | null, rawText: string, createdAt: string, updatedAt: string, archived: boolean, author: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null }, repliesCount: { __typename?: 'CommentCollection', totalCount: number }, replyAuthors: { __typename?: 'CommentReplyAuthorCollection', totalCount: number, items: Array<{ __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null }> } };
export type ProjectPageLatestItemsModelsFragment = { __typename?: 'Project', id: string, role?: string | null, modelCount: { __typename?: 'ModelCollection', totalCount: number } };
export type ProjectPageLatestItemsModelsFragment = { __typename?: 'Project', id: string, role?: string | null, visibility: ProjectVisibility, modelCount: { __typename?: 'ModelCollection', totalCount: number } };
export type ProjectPageModelsActionsFragment = { __typename?: 'Model', id: string, name: string };
export type ProjectPageModelsCardProjectFragment = { __typename?: 'Project', id: string, role?: string | null };
export type ProjectPageModelsCardProjectFragment = { __typename?: 'Project', id: string, role?: string | null, visibility: ProjectVisibility };
export type ModelPreviewFragment = { __typename?: 'Model', previewUrl?: string | null };
@@ -3003,9 +3003,9 @@ export type ProjectPageTeamDialogFragment = { __typename?: 'Project', id: string
export type OnUserProjectsUpdateSubscriptionVariables = Exact<{ [key: string]: never; }>;
export type OnUserProjectsUpdateSubscription = { __typename?: 'Subscription', userProjectsUpdated: { __typename?: 'UserProjectsUpdatedMessage', type: UserProjectsUpdatedMessageType, id: string, project?: { __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> } | null } };
export type OnUserProjectsUpdateSubscription = { __typename?: 'Subscription', userProjectsUpdated: { __typename?: 'UserProjectsUpdatedMessage', type: UserProjectsUpdatedMessageType, id: string, project?: { __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> } | null } };
export type ProjectsDashboardFilledFragment = { __typename?: 'ProjectCollection', items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> };
export type ProjectsDashboardFilledFragment = { __typename?: 'ProjectCollection', items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> };
export type ProjectsInviteBannerFragment = { __typename?: 'PendingStreamCollaborator', id: string, projectId: string, projectName: string, token?: string | null, invitedBy: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } };
@@ -3019,6 +3019,8 @@ export type UserProfileEditDialogDeleteAccount_UserFragment = { __typename?: 'Us
export type UserProfileEditDialogNotificationPreferences_UserFragment = { __typename?: 'User', id: string, notificationPreferences: {} };
export type ModelPageProjectFragment = { __typename?: 'Project', id: string, createdAt: string, name: string, visibility: ProjectVisibility };
export type ThreadCommentAttachmentFragment = { __typename?: 'Comment', text: { __typename?: 'SmartTextEditorValue', attachments?: Array<{ __typename?: 'BlobMetadata', id: string, fileName: string, fileType: string, fileSize?: number | null }> | null } };
export type ViewerCommentsListItemFragment = { __typename?: 'Comment', id: string, rawText: string, archived: boolean, createdAt: string, viewedAt?: string | null, author: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null }, replies: { __typename?: 'CommentCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Comment', id: string, archived: boolean, rawText: string, createdAt: string, text: { __typename?: 'SmartTextEditorValue', doc?: {} | null, attachments?: Array<{ __typename?: 'BlobMetadata', id: string, fileName: string, fileType: string, fileSize?: number | null }> | null }, author: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }, replyAuthors: { __typename?: 'CommentReplyAuthorCollection', totalCount: number, items: Array<{ __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null }> }, resources: Array<{ __typename?: 'ResourceIdentifier', resourceId: string, resourceType: ResourceType }> };
@@ -3160,9 +3162,9 @@ export type SearchProjectsQueryVariables = Exact<{
export type SearchProjectsQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', projects: { __typename?: 'ProjectCollection', totalCount: number, items: Array<{ __typename?: 'Project', id: string, name: string }> } } | null };
export type ProjectDashboardItemNoModelsFragment = { __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> };
export type ProjectDashboardItemNoModelsFragment = { __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> };
export type ProjectDashboardItemFragment = { __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> };
export type ProjectDashboardItemFragment = { __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> };
export type PendingFileUploadFragment = { __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string };
@@ -3304,7 +3306,7 @@ export type ProjectsDashboardQueryQueryVariables = Exact<{
}>;
export type ProjectsDashboardQueryQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, projects: { __typename?: 'ProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> }, projectInvites: Array<{ __typename?: 'PendingStreamCollaborator', id: string, projectId: string, projectName: string, token?: string | null, invitedBy: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> } | null };
export type ProjectsDashboardQueryQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, projects: { __typename?: 'ProjectCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Project', id: string, name: string, createdAt: string, updatedAt: string, role?: string | null, visibility: ProjectVisibility, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, displayName: string, previewUrl?: string | null, createdAt: string, updatedAt: string, description?: string | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, pendingImportedVersions: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> }, pendingImportedModels: Array<{ __typename?: 'FileUpload', id: string, projectId: string, modelName: string, convertedStatus: number, convertedMessage?: string | null, uploadDate: string, convertedLastUpdate: string, fileType: string, fileName: string }>, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> }> }, projectInvites: Array<{ __typename?: 'PendingStreamCollaborator', id: string, projectId: string, projectName: string, token?: string | null, invitedBy: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }> } | null };
export type ProjectPageQueryQueryVariables = Exact<{
id: Scalars['String'];
@@ -3397,14 +3399,14 @@ export type ProjectModelVersionsQueryVariables = Exact<{
}>;
export type ProjectModelVersionsQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, model: { __typename?: 'Model', id: string, versions: { __typename?: 'VersionCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Version', id: string, message?: string | null, createdAt: string, previewUrl: string, sourceApplication?: string | null, authorUser?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> } } } };
export type ProjectModelVersionsQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, visibility: ProjectVisibility, model: { __typename?: 'Model', id: string, versions: { __typename?: 'VersionCollection', cursor?: string | null, totalCount: number, items: Array<{ __typename?: 'Version', id: string, message?: string | null, createdAt: string, previewUrl: string, sourceApplication?: string | null, authorUser?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null, commentThreadCount: { __typename?: 'CommentCollection', totalCount: number }, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, createdAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, statusMessage?: string | null, contextView?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null }> } } } };
export type ProjectModelsPageQueryVariables = Exact<{
projectId: Scalars['String'];
}>;
export type ProjectModelsPageQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, name: string, sourceApps: Array<string>, role?: string | null, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, modelCount: { __typename?: 'ModelCollection', totalCount: number } } };
export type ProjectModelsPageQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, name: string, sourceApps: Array<string>, role?: string | null, visibility: ProjectVisibility, team: Array<{ __typename?: 'ProjectCollaborator', user: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } }>, modelCount: { __typename?: 'ModelCollection', totalCount: number } } };
export type ProjectDiscussionsPageQueryVariables = Exact<{
projectId: Scalars['String'];
@@ -3649,7 +3651,7 @@ export type ViewerLoadedResourcesQueryVariables = Exact<{
}>;
export type ViewerLoadedResourcesQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, role?: string | null, createdAt: string, name: string, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, updatedAt: string, loadedVersion: { __typename?: 'VersionCollection', items: Array<{ __typename?: 'Version', id: string, message?: string | null, referencedObject: string, sourceApplication?: string | null, createdAt: string, previewUrl: string, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, versionId: string, createdAt: string, updatedAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, contextView?: string | null, statusMessage?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, referencedObject: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null, authorUser?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> }, versions: { __typename?: 'VersionCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Version', id: string, message?: string | null, referencedObject: string, sourceApplication?: string | null, createdAt: string, previewUrl: string, authorUser?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> } }> }, modelCount: { __typename?: 'ModelCollection', totalCount: number } } };
export type ViewerLoadedResourcesQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, role?: string | null, visibility: ProjectVisibility, createdAt: string, name: string, models: { __typename?: 'ModelCollection', totalCount: number, items: Array<{ __typename?: 'Model', id: string, name: string, updatedAt: string, loadedVersion: { __typename?: 'VersionCollection', items: Array<{ __typename?: 'Version', id: string, message?: string | null, referencedObject: string, sourceApplication?: string | null, createdAt: string, previewUrl: string, automationStatus?: { __typename?: 'AutomationsStatus', id: string, status: AutomationRunStatus, statusMessage?: string | null, automationRuns: Array<{ __typename?: 'AutomationRun', id: string, automationId: string, automationName: string, versionId: string, createdAt: string, updatedAt: string, status: AutomationRunStatus, functionRuns: Array<{ __typename?: 'AutomationFunctionRun', id: string, functionId: string, functionName: string, functionLogo?: string | null, elapsed: number, status: AutomationRunStatus, contextView?: string | null, statusMessage?: string | null, results?: {} | null, resultVersions: Array<{ __typename?: 'Version', id: string, referencedObject: string, model: { __typename?: 'Model', id: string, name: string } }> }> }> } | null, authorUser?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> }, versions: { __typename?: 'VersionCollection', totalCount: number, cursor?: string | null, items: Array<{ __typename?: 'Version', id: string, message?: string | null, referencedObject: string, sourceApplication?: string | null, createdAt: string, previewUrl: string, authorUser?: { __typename?: 'LimitedUser', id: string, name: string, avatar?: string | null } | null }> } }> }, modelCount: { __typename?: 'ModelCollection', totalCount: number } } };
export type ViewerModelVersionsQueryVariables = Exact<{
projectId: Scalars['String'];
@@ -3723,8 +3725,6 @@ export type ResolveCommentLinkQuery = { __typename?: 'Query', comment?: { __type
export type ProjectPageProjectFragment = { __typename?: 'Project', id: string, createdAt: string, role?: string | null, name: string, description?: string | null, visibility: ProjectVisibility, allowPublicComments: boolean, team: Array<{ __typename?: 'ProjectCollaborator', role: string, user: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } }>, invitedTeam?: Array<{ __typename?: 'PendingStreamCollaborator', id: string, title: string, inviteId: string, role: string, user?: { __typename?: 'LimitedUser', role?: string | null, id: string, name: string, avatar?: string | null } | null }> | null, versionCount: { __typename?: 'VersionCollection', totalCount: number }, modelCount: { __typename?: 'ModelCollection', totalCount: number }, commentThreadCount: { __typename?: 'ProjectCommentCollection', totalCount: number } };
export type ModelPageProjectFragment = { __typename?: 'Project', id: string, createdAt: string, name: string };
export const AuthRegisterPanelServerInfoFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AuthRegisterPanelServerInfo"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"inviteOnly"}}]}}]} as unknown as DocumentNode<AuthRegisterPanelServerInfoFragment, unknown>;
export const ServerTermsOfServicePrivacyPolicyFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ServerTermsOfServicePrivacyPolicyFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"termsOfService"}}]}}]} as unknown as DocumentNode<ServerTermsOfServicePrivacyPolicyFragmentFragment, unknown>;
export const AuthStategiesServerInfoFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AuthStategiesServerInfoFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ServerInfo"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"authStrategies"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode<AuthStategiesServerInfoFragmentFragment, unknown>;
@@ -3741,12 +3741,12 @@ export const ProjectModelPageDialogMoveToVersionFragmentDoc = {"kind":"Document"
export const ModelCardAutomationStatus_AutomationsStatusFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ModelCardAutomationStatus_AutomationsStatus"},"typeCondition":{"kind":"NamedType","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":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationId"}},{"kind":"Field","name":{"kind":"Name","value":"automationName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionId"}},{"kind":"Field","name":{"kind":"Name","value":"functionName"}},{"kind":"Field","name":{"kind":"Name","value":"functionLogo"}},{"kind":"Field","name":{"kind":"Name","value":"elapsed"}},{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"statusMessage"}},{"kind":"Field","name":{"kind":"Name","value":"contextView"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"kind":"Field","name":{"kind":"Name","value":"resultVersions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode<ModelCardAutomationStatus_AutomationsStatusFragment, unknown>;
export const ModelCardAutomationStatus_VersionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ModelCardAutomationStatus_Version"},"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":"automationStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ModelCardAutomationStatus_AutomationsStatus"}}]}}]}}]} as unknown as DocumentNode<ModelCardAutomationStatus_VersionFragment, unknown>;
export const ProjectModelPageVersionsCardVersionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageVersionsCardVersion"},"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":"message"}},{"kind":"Field","name":{"kind":"Name","value":"authorUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageDialogDeleteVersion"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageDialogMoveToVersion"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ModelCardAutomationStatus_Version"}}]}}]} as unknown as DocumentNode<ProjectModelPageVersionsCardVersionFragment, unknown>;
export const ProjectModelPageVersionsPaginationFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageVersionsPagination"},"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":"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":"IntValue","value":"16"}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionsCursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageVersionsCardVersion"}}]}}]}}]}}]}}]} as unknown as DocumentNode<ProjectModelPageVersionsPaginationFragment, unknown>;
export const ProjectModelPageVersionsPaginationFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageVersionsPagination"},"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":"visibility"}},{"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":"IntValue","value":"16"}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"versionsCursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageVersionsCardVersion"}}]}}]}}]}}]}}]} as unknown as DocumentNode<ProjectModelPageVersionsPaginationFragment, unknown>;
export const ProjectModelPageVersionsProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageVersionsProject"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageProjectHeader"}},{"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":"name"}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectModelPageVersionsPagination"}}]}}]} as unknown as DocumentNode<ProjectModelPageVersionsProjectFragment, unknown>;
export const ProjectModelPageDialogEditMessageVersionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelPageDialogEditMessageVersion"},"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":"message"}}]}}]} as unknown as DocumentNode<ProjectModelPageDialogEditMessageVersionFragment, unknown>;
export const FormUsersSelectItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"FormUsersSelectItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LimitedUser"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]} as unknown as DocumentNode<FormUsersSelectItemFragment, unknown>;
export const ProjectModelsPageHeader_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelsPageHeader_Project"},"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":"sourceApps"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FormUsersSelectItem"}}]}}]}}]}}]} as unknown as DocumentNode<ProjectModelsPageHeader_ProjectFragment, unknown>;
export const ProjectPageLatestItemsModelsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModels"},"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":"role"}},{"kind":"Field","alias":{"kind":"Name","value":"modelCount"},"name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode<ProjectPageLatestItemsModelsFragment, unknown>;
export const ProjectPageLatestItemsModelsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModels"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","alias":{"kind":"Name","value":"modelCount"},"name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode<ProjectPageLatestItemsModelsFragment, unknown>;
export const ProjectModelsPageResults_ProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectModelsPageResults_Project"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModels"}}]}}]} as unknown as DocumentNode<ProjectModelsPageResults_ProjectFragment, unknown>;
export const ProjectPageLatestItemsCommentItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsCommentItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Comment"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"FormUsersSelectItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"screenshot"}},{"kind":"Field","name":{"kind":"Name","value":"rawText"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"archived"}},{"kind":"Field","alias":{"kind":"Name","value":"repliesCount"},"name":{"kind":"Name","value":"replies"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"replyAuthors"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"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":"FormUsersSelectItem"}}]}}]}}]}}]} as unknown as DocumentNode<ProjectPageLatestItemsCommentItemFragment, unknown>;
export const ModelPreviewFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ModelPreview"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}}]}}]} as unknown as DocumentNode<ModelPreviewFragment, unknown>;
@@ -3756,7 +3756,7 @@ export const ProjectPageModelsActionsFragmentDoc = {"kind":"Document","definitio
export const ModelCardAutomationStatus_ModelFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ModelCardAutomationStatus_Model"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","name":{"kind":"Name","value":"automationStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ModelCardAutomationStatus_AutomationsStatus"}}]}}]}}]} as unknown as DocumentNode<ModelCardAutomationStatus_ModelFragment, unknown>;
export const ProjectPageLatestItemsModelItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Model"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"displayName"}},{"kind":"Field","alias":{"kind":"Name","value":"versionCount"},"name":{"kind":"Name","value":"versions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedVersions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ModelCardAutomationStatus_Model"}}]}}]} as unknown as DocumentNode<ProjectPageLatestItemsModelItemFragment, unknown>;
export const SingleLevelModelTreeItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SingleLevelModelTreeItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ModelsTreeItem"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"fullName"}},{"kind":"Field","name":{"kind":"Name","value":"model"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"hasChildren"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<SingleLevelModelTreeItemFragment, unknown>;
export const ProjectPageModelsCardProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"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":"role"}}]}}]} as unknown as DocumentNode<ProjectPageModelsCardProjectFragment, unknown>;
export const ProjectPageModelsCardProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardProject"},"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":"role"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}}]}}]} as unknown as DocumentNode<ProjectPageModelsCardProjectFragment, unknown>;
export const ProjectDashboardItemNoModelsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"},"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":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"team"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsCardProject"}}]}}]} as unknown as DocumentNode<ProjectDashboardItemNoModelsFragment, unknown>;
export const ProjectDashboardItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectDashboardItem"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItemNoModels"}},{"kind":"Field","name":{"kind":"Name","value":"models"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"onlyWithVersions"},"value":{"kind":"BooleanValue","value":true}}]}}],"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":"ProjectPageLatestItemsModelItem"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"pendingImportedModels"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"4"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PendingFileUpload"}}]}}]}}]} as unknown as DocumentNode<ProjectDashboardItemFragment, unknown>;
export const ProjectsDashboardFilledFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsDashboardFilled"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}}]}}]} as unknown as DocumentNode<ProjectsDashboardFilledFragment, unknown>;
@@ -3767,6 +3767,7 @@ export const UserProfileEditDialogAvatar_UserFragmentDoc = {"kind":"Document","d
export const UserProfileEditDialogBio_UserFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserProfileEditDialogBio_User"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"bio"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UserProfileEditDialogAvatar_User"}}]}}]} as unknown as DocumentNode<UserProfileEditDialogBio_UserFragment, unknown>;
export const UserProfileEditDialogDeleteAccount_UserFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserProfileEditDialogDeleteAccount_User"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]} as unknown as DocumentNode<UserProfileEditDialogDeleteAccount_UserFragment, unknown>;
export const UserProfileEditDialogNotificationPreferences_UserFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UserProfileEditDialogNotificationPreferences_User"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"notificationPreferences"}}]}}]} as unknown as DocumentNode<UserProfileEditDialogNotificationPreferences_UserFragment, unknown>;
export const ModelPageProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ModelPageProject"},"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":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}}]}}]} as unknown as DocumentNode<ModelPageProjectFragment, unknown>;
export const ViewerModelVersionCardItemFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ViewerModelVersionCardItem"},"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":"message"}},{"kind":"Field","name":{"kind":"Name","value":"referencedObject"}},{"kind":"Field","name":{"kind":"Name","value":"sourceApplication"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"previewUrl"}},{"kind":"Field","name":{"kind":"Name","value":"authorUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}}]}}]} as unknown as DocumentNode<ViewerModelVersionCardItemFragment, unknown>;
export const ProjectUpdatableMetadataFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectUpdatableMetadata"},"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":"description"}},{"kind":"Field","name":{"kind":"Name","value":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"allowPublicComments"}}]}}]} as unknown as DocumentNode<ProjectUpdatableMetadataFragment, unknown>;
export const AppAuthorAvatarFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AppAuthorAvatar"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AppAuthor"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]} as unknown as DocumentNode<AppAuthorAvatarFragment, unknown>;
@@ -3783,7 +3784,6 @@ export const ProjectPageStatsBlockModelsFragmentDoc = {"kind":"Document","defini
export const ProjectPageStatsBlockCommentsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageStatsBlockComments"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode<ProjectPageStatsBlockCommentsFragment, unknown>;
export const ProjectPageLatestItemsCommentsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageLatestItemsComments"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Project"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","alias":{"kind":"Name","value":"commentThreadCount"},"name":{"kind":"Name","value":"commentThreads"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"0"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}}]}}]}}]} as unknown as DocumentNode<ProjectPageLatestItemsCommentsFragment, unknown>;
export const ProjectPageProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageProject"},"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":"createdAt"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageProjectHeader"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageStatsBlockTeam"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageTeamDialog"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageStatsBlockVersions"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageStatsBlockModels"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageStatsBlockComments"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModels"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsComments"}}]}}]} as unknown as DocumentNode<ProjectPageProjectFragment, unknown>;
export const ModelPageProjectFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ModelPageProject"},"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":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]} as unknown as DocumentNode<ModelPageProjectFragment, unknown>;
export const RegisterPanelServerInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"RegisterPanelServerInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"serverInviteByToken"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}}]} as unknown as DocumentNode<RegisterPanelServerInviteQuery, RegisterPanelServerInviteQueryVariables>;
export const EmailVerificationBannerStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EmailVerificationBannerState"},"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":"email"}},{"kind":"Field","name":{"kind":"Name","value":"verified"}},{"kind":"Field","name":{"kind":"Name","value":"hasPendingVerification"}}]}}]}}]} as unknown as DocumentNode<EmailVerificationBannerStateQuery, EmailVerificationBannerStateQueryVariables>;
export const RequestVerificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RequestVerification"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"requestVerification"}}]}}]} as unknown as DocumentNode<RequestVerificationMutation, RequestVerificationMutationVariables>;
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
@@ -60,5 +60,12 @@ export enum VersionActionTypes {
EditMessage = 'edit-message',
Select = 'select',
Share = 'share',
CopyId = 'copy-id'
CopyId = 'copy-id',
EmbedModel = 'embed-model'
}
export enum OpenSectionType {
Invite = 'invite',
Access = 'access',
Team = 'team'
}
@@ -27,6 +27,7 @@ import { useViewerAnchoredPoints } from '~~/lib/viewer/composables/anchorPoints'
import { useOnBeforeWindowUnload } from '~~/lib/common/composables/window'
import { ToastNotificationType, useGlobalToast } from '~~/lib/common/composables/toast'
import { onViewerUserActivityBroadcastedSubscription } from '~~/lib/viewer/graphql/subscriptions'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
import {
StateApplyMode,
@@ -56,7 +57,6 @@ function useCollectMainMetadata() {
const { sessionId } = useInjectedViewerState()
const { activeUser } = useActiveUser()
const { serialize } = useStateSerialization()
return (): Omit<ViewerUserActivityMessageInput, 'status' | 'selection'> => ({
userId: activeUser.value?.id || null,
userName: activeUser.value?.name || 'Anonymous Viewer',
@@ -79,9 +79,10 @@ export function useViewerUserActivityBroadcasting(
const { isLoggedIn } = useActiveUser()
const getMainMetadata = useCollectMainMetadata()
const apollo = useApolloClient().client
const { isEnabled: isEmbedEnabled } = useEmbed()
const invokeMutation = async (message: ViewerUserActivityMessageInput) => {
if (!isLoggedIn.value) return false
if (!isLoggedIn.value || isEmbedEnabled) return false
const result = await apollo
.mutate({
mutation: broadcastViewerUserActivityMutation,
@@ -143,6 +144,7 @@ export function useViewerUserActivityTracking(params: {
const { isLoggedIn } = useActiveUser()
const { triggerNotification } = useGlobalToast()
const sendUpdate = useViewerUserActivityBroadcasting()
const { isEnabled: isEmbedEnabled } = useEmbed()
// TODO: For some reason subscription is set up twice? Vue Apollo bug?
const { onResult: onUserActivity } = useSubscription(
@@ -174,7 +176,7 @@ export function useViewerUserActivityTracking(params: {
const incomingSessionId = event.sessionId
if (sessionId.value === incomingSessionId) return
if (status === ViewerUserActivityStatus.Disconnected) {
if (!isEmbedEnabled && status === ViewerUserActivityStatus.Disconnected) {
triggerNotification({
description: `${users.value[incomingSessionId]?.userName || 'A user'} left.`,
type: ToastNotificationType.Info
@@ -206,7 +208,7 @@ export function useViewerUserActivityTracking(params: {
lastUpdate: dayjs()
}
if (!Object.keys(users.value).includes(incomingSessionId)) {
if (!isEmbedEnabled && !Object.keys(users.value).includes(incomingSessionId)) {
triggerNotification({
description: `${userData.userName} joined.`,
type: ToastNotificationType.Info
@@ -19,14 +19,7 @@ import type {
} from '@speckle/viewer'
import type { MaybeRef } from '@vueuse/shared'
import { inject, ref, provide } from 'vue'
import type {
InjectionKey,
ComputedRef,
WritableComputedRef,
Raw,
Ref,
ShallowRef
} from 'vue'
import type { ComputedRef, WritableComputedRef, Raw, Ref, ShallowRef } from 'vue'
import { useScopedState } from '~~/lib/common/composables/scopedState'
import type { MaybeNullOrUndefined, Nullable, Optional } from '@speckle/shared'
import { SpeckleViewer, isNonNullable } from '@speckle/shared'
@@ -68,6 +61,10 @@ import { useDiffUtilities, useFilterUtilities } from '~~/lib/viewer/composables/
import { flatten, reduce } from 'lodash-es'
import { setupViewerCommentBubbles } from '~~/lib/viewer/composables/setup/comments'
import { FilteringExtension } from '@speckle/viewer'
import {
InjectableViewerStateKey,
useSetupViewerScope
} from '~/lib/viewer/composables/setup/core'
export type LoadedModel = NonNullable<
Get<ViewerLoadedResourcesQuery, 'project.models.items[0]'>
@@ -302,13 +299,6 @@ export type InitialStateWithInterface = InitialStateWithUrlHashState &
*/
const GlobalViewerDataKey = Symbol('GlobalViewerData')
/**
* Vue injection key for the Injectable Viewer State
*/
const InjectableViewerStateKey: InjectionKey<InjectableViewerState> = Symbol(
'INJECTABLE_VIEWER_STATE'
)
function createViewerDataBuilder(params: { viewerDebug: boolean }) {
return () => {
if (process.server)
@@ -971,17 +961,6 @@ export function useInjectedViewerInterfaceState(): InjectableViewerState['ui'] {
return ui
}
/**
* Use this when you want to use the viewer state outside the viewer, ie in a component that's inside a portal!
* @param state
*/
export function useSetupViewerScope(
state: InjectableViewerState
): InjectableViewerState {
provide(InjectableViewerStateKey, state)
return state
}
export function useResetUiState() {
const {
ui: { camera, sectionBox, highlightedObjectIds, lightConfig }
@@ -998,3 +977,5 @@ export function useResetUiState() {
endDiff()
}
}
export { InjectableViewerStateKey, useSetupViewerScope }
@@ -0,0 +1,27 @@
import type { InjectableViewerState } from '~/lib/viewer/composables/setup'
/**
* Keeping some core Viewer state code here so that we can import it without
* importing the entire Viewer state related codebase. Useful in embed mode where
* we don't want to load all of the Viewer JS before the Play button is pressed.
*/
/**
* Vue injection key for the Injectable Viewer State
*/
export const InjectableViewerStateKey: InjectionKey<InjectableViewerState> = Symbol(
'INJECTABLE_VIEWER_STATE'
)
/**
* Use this when you want to use the viewer state outside the viewer, ie in a component that's inside a portal!
* @param state
*/
export function useSetupViewerScope(
state: InjectableViewerState
): InjectableViewerState {
provide(InjectableViewerStateKey, state)
return state
}
export type { InjectableViewerState }
@@ -0,0 +1,129 @@
import { writableAsyncComputed } from '~/lib/common/composables/async'
import { useScopedState } from '~/lib/common/composables/scopedState'
import { ViewerHashStateKeys } from '~/lib/viewer/composables/setup/urlHashState'
import { useConditionalViewerRendering } from '~/lib/viewer/composables/ui'
import { useRouteHashState } from '~~/lib/common/composables/url'
export type EmbedOptions = {
isEnabled?: boolean
isTransparent?: boolean
hideControls?: boolean
hideSelectionInfo?: boolean
noScroll?: boolean
manualLoad?: boolean
}
export function isEmbedOptions(obj: unknown): obj is EmbedOptions {
if (typeof obj === 'object' && obj !== null) {
const possibleOptions = obj as Partial<EmbedOptions>
return Object.keys(possibleOptions).every(
(key) =>
[
'isEnabled',
'isTransparent',
'hideControls',
'hideSelectionInfo',
'noScroll',
'manualLoad'
].includes(key) &&
typeof possibleOptions[key as keyof EmbedOptions] === 'boolean'
)
}
return false
}
export function deserializeEmbedOptions(embedString: string | null): EmbedOptions {
const logger = useLogger()
if (!embedString) {
return { isEnabled: false }
}
try {
const parsed: unknown = JSON.parse(embedString)
if (isEmbedOptions(parsed)) {
return { ...parsed, isEnabled: true }
}
logger.error('Parsed object is not of type EmbedOptions')
} catch (error) {
logger.error(error)
}
return { isEnabled: false }
}
export function useEmbedState() {
const { hashState } = useRouteHashState()
const embedOptions = writableAsyncComputed({
get: () => {
const embedString = hashState.value[ViewerHashStateKeys.EmbedOptions]
return deserializeEmbedOptions(embedString)
},
set: async (newOptions) => {
const embedString = newOptions ? JSON.stringify(newOptions) : null
await hashState.update({
...hashState.value,
[ViewerHashStateKeys.EmbedOptions]: embedString
})
},
initialState: null,
asyncRead: false
})
return { embedOptions }
}
const embedStateScopedKey = Symbol('EmbedStateScopedKey')
export function useEmbed() {
const { embedOptions } = useEmbedState()
const { showControls } = useConditionalViewerRendering()
// useScopedState so that we don't keep creating new computeds
return useScopedState(embedStateScopedKey, () => {
const createComputed = <K extends keyof EmbedOptions>(key: K) =>
writableAsyncComputed({
get: () => embedOptions.value?.[key],
set: async (newVal) => {
await embedOptions.update({
...(embedOptions.value ?? {}),
...{
[key]: newVal
}
})
},
initialState: null,
asyncRead: false
})
const isEnabled = createComputed('isEnabled')
const isTransparent = createComputed('isTransparent')
const hideSelectionInfo = createComputed('hideSelectionInfo')
const noScroll = createComputed('noScroll')
const manualLoad = createComputed('manualLoad')
const showControlsNew = writableAsyncComputed({
get: () => showControls.value,
set: async (newVal) =>
await embedOptions.update({
...(embedOptions.value ?? {}),
...{
hideControls: !(newVal || undefined)
}
}),
initialState: null,
asyncRead: false
})
return {
isEnabled,
isEmbedEnabled: isEnabled,
isTransparent,
showControls: showControlsNew,
hideSelectionInfo,
noScroll,
manualLoad
}
})
}
@@ -50,6 +50,7 @@ import { setupDebugMode } from '~~/lib/viewer/composables/setup/dev'
import { CameraController } from '@speckle/viewer'
import type { Reference } from '@apollo/client'
import type { Modifier } from '@apollo/client/cache'
import { useEmbed } from '~/lib/viewer/composables/setup/embed'
function useViewerIsBusyEventHandler() {
const state = useInjectedViewerState()
@@ -744,6 +745,24 @@ function useViewerMeasurementIntegration() {
)
}
function useDisableZoomOnEmbed() {
const { viewer } = useInjectedViewerState()
const embedOptions = useEmbed()
watch(
() => embedOptions.noScroll.value,
(newNoScrollValue) => {
const cameraController = viewer.instance.getExtension(CameraController)
if (newNoScrollValue) {
cameraController.controls.mouseButtons.wheel = 0
} else {
cameraController.controls.mouseButtons.wheel = 4
}
},
{ immediate: true }
)
}
export function useViewerPostSetup() {
if (process.server) return
useViewerObjectAutoLoading()
@@ -759,5 +778,6 @@ export function useViewerPostSetup() {
useExplodeFactorIntegration()
useDiffingIntegration()
useViewerMeasurementIntegration()
useDisableZoomOnEmbed()
setupDebugMode()
}
@@ -5,7 +5,8 @@ import { useDiffBuilderUtilities } from '~~/lib/viewer/composables/setup/diff'
export enum ViewerHashStateKeys {
FocusedThreadId = 'threadId',
Diff = 'diff'
Diff = 'diff',
EmbedOptions = 'embed'
}
export function setupUrlHashState(): InjectableViewerState['urlHashState'] {
@@ -0,0 +1,41 @@
import { useConditionalViewerRendering } from '~/lib/viewer/composables/ui'
export const useTourStageState = () =>
useState('viewer-tour-state', () => ({
showNavbar: true,
showViewerControls: true,
showTour: false,
showSegmentation: true
}))
export function useViewerTour() {
const state = useTourStageState()
const conditionalRendering = useConditionalViewerRendering()
const showNavbar = computed({
get: () => conditionalRendering.showNavbar.value,
set: (newVal) => (state.value.showNavbar = newVal)
})
const showControls = computed({
get: () => conditionalRendering.showControls.value,
set: (newVal) => (state.value.showViewerControls = newVal)
})
const showTour = computed({
get: () => state.value.showTour,
set: (newVal) => (state.value.showTour = newVal)
})
const showSegmentation = computed({
get: () => state.value.showSegmentation,
set: (newVal) => (state.value.showSegmentation = newVal)
})
return {
showNavbar,
showControls,
showTour,
showSegmentation
}
}
@@ -4,6 +4,7 @@ import { CameraController } from '@speckle/viewer'
import type { MeasurementOptions, PropertyInfo } from '@speckle/viewer'
import { until } from '@vueuse/shared'
import { difference, isString, uniq } from 'lodash-es'
import { useEmbedState } from '~/lib/viewer/composables/setup/embed'
import type { SpeckleObject } from '~~/lib/common/helpers/sceneExplorer'
import { isNonNullable } from '~~/lib/common/helpers/utils'
import {
@@ -12,6 +13,7 @@ import {
useInjectedViewerState
} from '~~/lib/viewer/composables/setup'
import { useDiffBuilderUtilities } from '~~/lib/viewer/composables/setup/diff'
import { useTourStageState } from '~~/lib/viewer/composables/tour'
export function useSectionBoxUtilities() {
const { instance } = useInjectedViewer()
@@ -361,3 +363,35 @@ export function useMeasurementUtilities() {
removeMeasurement
}
}
/**
* Some conditional rendering values depend on multiple & overlapping states. This utility reconciles that.
*/
export function useConditionalViewerRendering() {
const tourState = useTourStageState()
const embedMode = useEmbedState()
const showControls = computed(() => {
if (tourState.value.showTour && !tourState.value.showViewerControls) return false
if (
embedMode.embedOptions.value?.isEnabled &&
embedMode.embedOptions.value.hideControls
) {
return false
}
return true
})
const showNavbar = computed(() => {
if (!showControls.value) return false
if (tourState.value.showTour && !tourState.value.showNavbar) return false
if (embedMode.embedOptions.value?.isEnabled) return false
return true
})
return {
showNavbar,
showControls
}
}
+1
View File
@@ -43,6 +43,7 @@ export default defineNuxtConfig({
public: {
apiOrigin: 'UNDEFINED',
backendApiOrigin: '',
baseUrl: '',
mixpanelApiHost: 'UNDEFINED',
mixpanelTokenId: 'UNDEFINED',
logLevel: NUXT_PUBLIC_LOG_LEVEL,
+1 -1
View File
@@ -113,7 +113,7 @@
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^26.0.0",
"tailwindcss": "^3.3.2",
"tailwindcss": "^3.4.1",
"type-fest": "^3.5.1",
"typescript": "^4.8.3",
"vue-tsc": "1.8.22",
+5 -4
View File
@@ -17,6 +17,7 @@
</div>
</template>
<script setup lang="ts">
import { useViewerTour } from '~/lib/viewer/composables/tour'
import {
useProcessOnboarding,
FIRST_MODEL_NAME
@@ -34,7 +35,7 @@ definePageMeta({
const router = useRouter()
const { createOnboardingProject, setUserOnboardingComplete } = useProcessOnboarding()
const tourStage = useTourStageState()
const tourStage = useViewerTour()
const status = ref('Setting up your account')
@@ -48,9 +49,9 @@ onMounted(async () => {
await setUserOnboardingComplete()
status.value = 'Almost done!'
tourStage.value.showNavbar = false
tourStage.value.showViewerControls = false
tourStage.value.showTour = true
tourStage.showNavbar.value = false
tourStage.showControls.value = false
tourStage.showTour.value = true
const firstModelToLoad = project?.models.items.find(
(model) => model.name === FIRST_MODEL_NAME
@@ -21,9 +21,13 @@
<!-- No v-if=project to ensure internal queries trigger ASAP -->
<div v-show="project" class="flex flex-col space-y-8 sm:space-y-14">
<!-- Latest models -->
<ProjectPageLatestItemsModels :project="project" :project-id="projectId" />
<div class="relative z-10">
<ProjectPageLatestItemsModels :project="project" :project-id="projectId" />
</div>
<!-- Latest comments -->
<ProjectPageLatestItemsComments :project="project" :project-id="projectId" />
<div class="relative z-0">
<ProjectPageLatestItemsComments :project="project" :project-id="projectId" />
</div>
<!-- More actions -->
<!-- <ProjectPageMoreActions /> -->
</div>
@@ -1,80 +1,25 @@
<template>
<ViewerPostSetupWrapper>
<div class="absolute top-0 left-0 w-screen h-[100dvh]">
<!-- Nav -->
<Portal to="navigation">
<ViewerScope :state="state">
<HeaderNavLink
:to="`/projects/${project?.id}`"
:name="project?.name"
></HeaderNavLink>
<ViewerExplorerNavbarLink />
</ViewerScope>
</Portal>
<!-- Note: commented out until we scope it properly. -->
<!-- <Portal to="primary-actions">
<div class="flex space-x-4">
<FormButton :icon-left="ShareIcon">Share</FormButton>
</div>
</Portal> -->
<ClientOnly>
<!-- Tour host -->
<div
v-if="tourState.showTour"
class="fixed w-full h-[100dvh] flex justify-center items-center pointer-events-none z-[100]"
>
<TourOnboarding />
</div>
<!-- Viewer host -->
<div class="special-gradient absolute w-screen h-[100dvh] z-10 overflow-hidden">
<ViewerBase />
<Transition
enter-from-class="opacity-0"
enter-active-class="transition duration-1000"
>
<ViewerAnchoredPoints v-show="tourState.showViewerControls" />
</Transition>
</div>
<!-- Global loading bar -->
<ViewerLoadingBar class="z-20" />
<!-- Sidebar sketches -->
<Transition
enter-from-class="opacity-0"
enter-active-class="transition duration-1000"
>
<ViewerControls v-show="tourState.showViewerControls" class="z-20" />
</Transition>
<!-- Viewer Object Selection Info Display -->
<Transition
enter-from-class="opacity-0"
enter-active-class="transition duration-1000"
>
<div v-show="tourState.showViewerControls">
<ViewerSelectionSidebar class="z-20 hidden sm:block" />
</div>
</Transition>
<!-- Shows up when filters are applied for an easy return to normality -->
<ViewerGlobalFilterReset class="z-20" />
</ClientOnly>
</div>
</ViewerPostSetupWrapper>
<div
v-if="tourState.showViewerControls"
class="sm:hidden shadow-t fixed bottom-0 left-0 max-h-[65vh] overflow-hidden w-screen z-50 transition-all duration-300 empty:-bottom-[65vh]"
>
<PortalTarget name="bottomPanel"></PortalTarget>
<PortalTarget name="mobileComments"></PortalTarget>
<div :class="isTransparent ? 'viewer-transparent' : ''">
<ViewerEmbedManualLoad v-if="isManualLoad" @play="isManualLoad = false" />
<LazyViewerPreSetupWrapper v-else @setup="state = $event" />
<ClientOnly>
<Component
:is="state ? ViewerScope : 'div'"
:state="state"
wrapper
class="fixed shadow-t bottom-0 left-0 max-h-[65vh] overflow-hidden w-screen z-50 transition-all duration-300 empty:-bottom-[65vh]"
>
<PortalTarget name="bottomPanel"></PortalTarget>
<PortalTarget name="mobileComments"></PortalTarget>
</Component>
</ClientOnly>
</div>
</template>
<script setup lang="ts">
import { graphql } from '~~/lib/common/generated/gql'
import { useSetupViewer } from '~~/lib/viewer/composables/setup'
const tourState = useTourStageState()
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { deserializeEmbedOptions } from '~~/lib/viewer/composables/setup/embed'
import type { InjectableViewerState } from '~~/lib/viewer/composables/setup/core'
definePageMeta({
layout: 'viewer',
@@ -84,29 +29,29 @@ definePageMeta({
key: '/projects/:id/models/resources' // To prevent controls flickering on resource url param changes
})
const ViewerScope = resolveComponent('ViewerScope')
const isManualLoad = ref(false)
const isTransparent = ref(false)
const route = useRoute()
const projectId = computed(() => route.params.id as string)
const state = ref<InjectableViewerState>()
const state = useSetupViewer({
projectId
})
const checkUrlForEmbedManualLoadSettings = () => {
if (process.server) return
const {
resources: {
response: { project }
}
} = state
const hashParams = new URLSearchParams(route.hash.substring(1))
const embedParam = hashParams.get('embed')
graphql(`
fragment ModelPageProject on Project {
id
createdAt
name
}
`)
const embedOptions = deserializeEmbedOptions(embedParam)
isManualLoad.value = embedOptions.manualLoad === true
isTransparent.value = embedOptions.isTransparent === true
}
const title = computed(() =>
project.value?.name.length ? `Viewer - ${project.value.name}` : ''
watch(
() => route.fullPath,
() => {
checkUrlForEmbedManualLoadSettings()
},
{ immediate: true }
)
useHead({ title })
</script>
@@ -48,6 +48,7 @@ export default defineNuxtPlugin(async (nuxtApp) => {
const seqLogger = new seq.Logger({
serverUrl: logClientApiEndpoint,
apiKey: logClientApiToken,
// eslint-disable-next-line no-console
onError: console.error
})
@@ -1,6 +1,6 @@
<template>
<div
v-if="!$vuetify.breakpoint.xs || (isEmbed && commentSlideShow)"
v-if="!$vuetify.breakpoint.xs"
class="no-mouse py-2"
:style="`max-width: 350px; padding-right:30px;
${
@@ -450,7 +450,6 @@ import { SMART_EDITOR_SCHEMA } from '@/main/lib/viewer/comments/commentsHelper'
import { isSuccessfullyUploaded } from '@/main/lib/common/file-upload/fileUploadHelper'
import { COMMENT_FULL_INFO_FRAGMENT } from '@/graphql/comments'
import { useCommitObjectViewerParams } from '@/main/lib/viewer/commit-object-viewer/stateManager'
import { useEmbedViewerQuery } from '@/main/lib/viewer/commit-object-viewer/composables/embed'
// TODO: The template is a WET mess, need to refactor it
export default {
@@ -587,12 +586,10 @@ export default {
},
setup() {
const { streamId, resourceId, isEmbed } = useCommitObjectViewerParams()
const { commentSlideShow } = useEmbedViewerQuery()
return {
streamId,
resourceId,
isEmbed,
commentSlideShow
isEmbed
}
},
data() {
@@ -111,7 +111,7 @@ const containerClasses = computed(() => {
break
case 'default':
default:
classParts.push(hasDescription.value ? 'p-4' : 'p-2')
classParts.push(hasDescription.value ? 'p-3 sm:p-4' : 'p-2')
break
}
@@ -185,7 +185,7 @@ const descriptionWrapperClasses = computed(() => {
break
case 'default':
default:
classParts.push('mt-2 text-sm')
classParts.push('mt-1 sm:mt-2 text-xs sm:text-sm')
break
}
@@ -16,10 +16,6 @@
:class="`${iconClasses} ${hideText ? '' : 'mr-2'}`"
/>
<slot v-if="!hideText">Button</slot>
<div v-else style="margin: 0 !important; width: 0.01px">
&nbsp;
<!-- The point of this is to ensure text & no-text buttons have the same height -->
</div>
<Component
:is="iconRight"
v-if="iconRight || !loading"
@@ -433,7 +429,7 @@ const decoratorClasses = computed(() => {
const buttonClasses = computed(() => {
const isLinkOrText = props.link || props.text
return [
'transition inline-flex justify-center text-center items-center space-x-2 outline-none select-none',
'transition inline-flex justify-center text-center items-center outline-none select-none leading-[0.9rem]',
generalClasses.value,
sizeClasses.value,
foregroundClasses.value,
@@ -1,14 +1,12 @@
<template>
<div class="relative group bg-foundation-page p-2 rounded-lg pr-12">
<FormTextArea
<div
v-if="isMultiline"
color="transparent"
name="contentArea"
readonly
:model-value="value"
class="relative z-10 text-sm text-foreground font-mono"
:rows="rows"
/>
class="relative z-10 text-xs sm:text-sm text-foreground font-mono break-all p-2 pl-3 max-h-[4.8rem] simple-scrollbar overflow-y-auto"
@keypress="keyboardClick(selectAllText)"
>
{{ value }}
</div>
<FormTextInput
v-else
color="transparent"
@@ -21,7 +19,7 @@
<FormButton
color="invert"
size="sm"
:icon-left="ClipboardDocumentIcon"
:icon-left="copied ? ClipboardDocumentCheckIcon : ClipboardDocumentIcon"
hide-text
@click="handleCopy"
></FormButton>
@@ -31,8 +29,13 @@
<script setup lang="ts">
import { useClipboard } from '@vueuse/core'
import { ClipboardDocumentIcon } from '@heroicons/vue/24/outline'
import { FormTextArea, FormTextInput, FormButton } from '~~/src/lib'
import {
ClipboardDocumentIcon,
ClipboardDocumentCheckIcon
} from '@heroicons/vue/24/outline'
import { FormTextInput, FormButton } from '~~/src/lib'
import { ref } from 'vue'
import { keyboardClick } from '~~/src/helpers/global/accessibility'
type Props = {
value: string
@@ -48,10 +51,29 @@ const emit = defineEmits<{ (e: 'copy', val: string): void }>()
const { copy } = useClipboard({ legacy: true })
const copied = ref(false)
const handleCopy = async () => {
if (props.value) {
await copy(props.value)
copied.value = true
emit('copy', props.value)
setTimeout(() => {
copied.value = false
}, 2000)
}
}
const selectAllText = (event: Event) => {
const textElement = event.target as HTMLElement
const selection = window.getSelection()
if (selection) {
const range = document.createRange()
range.selectNodeContents(textElement)
selection.removeAllRanges()
selection.addRange(range)
}
}
</script>
@@ -19,14 +19,17 @@
@change="onChange"
/>
</div>
<div class="text-sm" :class="inlineDescription ? 'flex gap-2 items-center' : ''">
<div
class="text-xs sm:text-sm"
:class="inlineDescription ? 'flex gap-2 items-center' : ''"
>
<label
:for="finalId"
class="text-foreground flex gap-2 items-center"
:class="{ 'sr-only': hideLabel }"
>
<div v-if="icon" class="text-sm">
<component :is="icon" class="h-10 w-10"></component>
<component :is="icon" class="h-8 sm:h-10 w-8 sm:w-10"></component>
</div>
<span>{{ title }}</span>
<span v-if="showRequired" class="text-danger ml-1">*</span>
@@ -35,10 +35,10 @@
:as="isForm ? 'form' : 'div'"
@submit.prevent="onSubmit"
>
<div :class="scrolledFromTop && 'relative z-10 shadow-lg'">
<div :class="scrolledFromTop && 'relative z-20 shadow-lg'">
<div
v-if="hasTitle"
class="flex items-center justify-start rounded-t-lg shrink-0 min-h-[4rem] py-2 px-4 sm:px-8 truncate text-xl sm:text-2xl font-bold"
class="flex items-center justify-start rounded-t-lg shrink-0 min-h-[2rem] sm:min-h-[4rem] py-2 px-4 sm:px-8 truncate text-lg sm:text-2xl font-bold"
>
<div class="w-full truncate pr-12">
{{ title }}
@@ -49,22 +49,22 @@
<button
v-if="!hideCloser"
class="absolute z-20 right-4 bg-foundation rounded-full p-1"
:class="hasTitle ? 'top-4' : 'top-3'"
class="absolute z-20 bg-foundation rounded-full p-1"
:class="hasTitle ? 'top-2 right-3 sm:top-4' : 'right-4 top-3'"
@click="open = false"
>
<XMarkIcon class="h-6 w-6" />
<XMarkIcon class="h-5 sm:h-6 w-5 sm:w-6" />
</button>
<div
class="flex-1 simple-scrollbar overflow-y-auto"
:class="hasTitle ? 'p-4 sm:py-6 sm:px-8' : 'p-10'"
:class="hasTitle ? 'p-3 sm:py-6 sm:px-8' : 'p-10'"
@scroll="onScroll"
>
<slot>Put your content here!</slot>
</div>
<div
v-if="hasButtons"
class="flex px-4 py-2 sm:py-4 sm:px-6 gap-2 shrink-0"
class="relative z-50 flex px-4 py-2 sm:py-4 sm:px-6 gap-2 shrink-0 bg-foundation"
:class="!scrolledToBottom && 'shadow-t'"
>
<template v-if="buttons">
@@ -156,7 +156,7 @@ const maxWidthWeight = computed(() => {
})
const widthClasses = computed(() => {
const classParts: string[] = ['w-full', 'sm:w-full sm:max-w-xl']
const classParts: string[] = ['w-full', 'sm:w-full sm:max-w-2xl']
if (maxWidthWeight.value >= 1) {
classParts.push('md:max-w-2xl')
@@ -63,15 +63,27 @@
: `max-height: ${isExpanded ? contentHeight + 'px' : '0px'}`
"
>
<div ref="content" class="rounded-md text-sm pb-3 px-2 mt-1">
<slot>Panel contents</slot>
</div>
<template v-if="props.lazyLoad">
<div
v-if="isExpanded || props.alwaysOpen"
ref="content"
class="rounded-md text-sm pb-3 px-2 mt-1"
>
<slot>Panel contents</slot>
</div>
</template>
<template v-else>
<div ref="content" class="rounded-md text-sm pb-3 px-2 mt-1">
<slot>Panel contents</slot>
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, unref, computed } from 'vue'
import { ref, unref, computed, nextTick } from 'vue'
import type { Ref } from 'vue'
import { ChevronDownIcon } from '@heroicons/vue/24/outline'
import { FormButton } from '~~/src/lib'
@@ -109,7 +121,11 @@ const props = defineProps({
onClick?: () => void
}
| undefined,
alwaysOpen: Boolean
alwaysOpen: Boolean,
lazyLoad: {
type: Boolean,
default: false
}
})
const content: Ref<HTMLElement | null> = ref(null)
@@ -147,9 +163,11 @@ const titleClasses = computed(() => {
}
})
const toggleExpansion = () => {
const toggleExpansion = async () => {
isExpanded.value = !isExpanded.value
if (isExpanded.value) {
await nextTick()
contentHeight.value = (unref(content)?.scrollHeight || 0) + 64
}
}
@@ -97,7 +97,7 @@ const buildButtonClassses = (params: {
]
if (active && !color) {
classParts.push('bg-primary text-foreground-on-primary')
classParts.push('bg-foundation-focus text-foreground')
} else if (disabled) {
classParts.push('text-foreground-disabled')
} else if (color === 'danger' && active) {
@@ -1,6 +1,6 @@
<template>
<div class="flex flex-col space-y-4">
<div class="flex space-x-6">
<div class="flex flex-col gap-y-0 sm:gap-y-4">
<div class="flex gap-x-6">
<FormButton
v-for="item in items"
:key="item.id"
@@ -144,7 +144,7 @@ export class RenderTree {
public getRenderableNodes(...types: SpeckleType[]): TreeNode[] {
return this.root.all((node: TreeNode): boolean => {
return (
node.model.renderView !== null &&
node.model.renderView &&
(node.model.renderView.hasGeometry || node.model.renderView.hasMetadata) &&
types.includes(node.model.renderView.renderData.speckleType)
)
+1
View File
@@ -161,6 +161,7 @@ def main():
fe2env = yml_doc["services"]["speckle-frontend-2"]["environment"]
fe2env["NUXT_PUBLIC_SERVER_NAME"] = DoubleQuotedScalarString(canonical_url)
fe2env["NUXT_PUBLIC_API_ORIGIN"] = DoubleQuotedScalarString(canonical_url)
fe2env["NUXT_PUBLIC_BASE_URL"] = DoubleQuotedScalarString(canonical_url)
with open(os.path.join(FILE_PATH, "docker-compose.yml"), "w") as f:
f.write("# This file was generated by SpeckleServer setup.\n")
@@ -44,6 +44,7 @@ services:
environment:
NUXT_PUBLIC_SERVER_NAME: 'TODO: change' # e.g. 'my-speckle-server'
NUXT_PUBLIC_API_ORIGIN: 'TODO: change' # e.g. 'http://127.0.0.1'
NUXT_PUBLIC_BASE_URL: 'TODO: change' # e.g. 'http://127.0.0.1'
NUXT_PUBLIC_BACKEND_API_ORIGIN: 'http://speckle-server:3000'
NUXT_REDIS_URL: 'redis://redis'
@@ -66,6 +66,8 @@ spec:
value: {{ .Values.file_size_limit_mb | quote }}
- name: NUXT_PUBLIC_API_ORIGIN
value: {{ .Values.ssl_canonical_url | ternary (printf "https://%s" .Values.domain) (printf "http://%s" .Values.domain) }}
- name: NUXT_PUBLIC_BASE_URL
value: {{ .Values.ssl_canonical_url | ternary (printf "https://%s" .Values.domain) (printf "http://%s" .Values.domain) }}
- name: NUXT_PUBLIC_BACKEND_API_ORIGIN
value: {{ printf "http://%s.%s.svc.cluster.local.:3000" (include "server.name" $) .Values.namespace }}
- name: NUXT_PUBLIC_MIXPANEL_TOKEN_ID
+34 -1
View File
@@ -13669,7 +13669,7 @@ __metadata:
stylelint-config-recommended-vue: ^1.4.0
stylelint-config-standard: ^26.0.0
subscriptions-transport-ws: ^0.11.0
tailwindcss: ^3.3.2
tailwindcss: ^3.4.1
type-fest: ^3.5.1
typescript: ^4.8.3
vee-validate: ^4.7.0
@@ -43441,6 +43441,39 @@ __metadata:
languageName: node
linkType: hard
"tailwindcss@npm:^3.4.1":
version: 3.4.1
resolution: "tailwindcss@npm:3.4.1"
dependencies:
"@alloc/quick-lru": ^5.2.0
arg: ^5.0.2
chokidar: ^3.5.3
didyoumean: ^1.2.2
dlv: ^1.1.3
fast-glob: ^3.3.0
glob-parent: ^6.0.2
is-glob: ^4.0.3
jiti: ^1.19.1
lilconfig: ^2.1.0
micromatch: ^4.0.5
normalize-path: ^3.0.0
object-hash: ^3.0.0
picocolors: ^1.0.0
postcss: ^8.4.23
postcss-import: ^15.1.0
postcss-js: ^4.0.1
postcss-load-config: ^4.0.1
postcss-nested: ^6.0.1
postcss-selector-parser: ^6.0.11
resolve: ^1.22.2
sucrase: ^3.32.0
bin:
tailwind: lib/cli.js
tailwindcss: lib/cli.js
checksum: ef5a587dd32bb4e91e1549ead6162f85f0b78d3e6ffd8b4e8eeb15585b7b886cb3af6ae9df5092ed8ccb7e590608d1b3eec79ca08c862b07cd9ff7e72f73104b
languageName: node
linkType: hard
"tapable@npm:^1.0.0":
version: 1.1.3
resolution: "tapable@npm:1.1.3"