diff --git a/packages/frontend-2/components/feedback/Dialog.vue b/packages/frontend-2/components/feedback/Dialog.vue index dc9f3792c..2fb608e49 100644 --- a/packages/frontend-2/components/feedback/Dialog.vue +++ b/packages/frontend-2/components/feedback/Dialog.vue @@ -6,7 +6,7 @@ :on-submit="onSubmit" max-width="md" > -
+

How can we improve Speckle? If you have a feature request, please also share how you would use it and why it's important to you @@ -18,6 +18,13 @@ label="Feedback" color="foundation" /> +

+ Need help? For support, head over to our + + community forum + + where we can chat and solve problems together. +

diff --git a/packages/frontend-2/components/header/NavLink.vue b/packages/frontend-2/components/header/NavLink.vue index 55cced73b..007b41af7 100644 --- a/packages/frontend-2/components/header/NavLink.vue +++ b/packages/frontend-2/components/header/NavLink.vue @@ -9,16 +9,16 @@ viewBox="0 0 8 24" fill="none" xmlns="http://www.w3.org/2000/svg" - class="text-outline-2" + class="text-outline-5" >
{{ name || to }} diff --git a/packages/frontend-2/components/invite/Banner.vue b/packages/frontend-2/components/invite/Banner.vue index 0d605747a..74a3b1d65 100644 --- a/packages/frontend-2/components/invite/Banner.vue +++ b/packages/frontend-2/components/invite/Banner.vue @@ -98,7 +98,7 @@ const token = computed( ) const mainClasses = computed(() => { const classParts = [ - 'flex flex-col space-y-4 px-4 py-5 transition border-x border-b border-outline-2 first:border-t first:rounded-t-lg last:rounded-b-lg' + 'flex flex-col space-y-4 px-4 py-5 transition bg-foundation border-x border-b border-outline-2 first:border-t first:rounded-t-lg last:rounded-b-lg' ] if (props.block) { diff --git a/packages/frontend-2/components/projects/DashboardHeader.vue b/packages/frontend-2/components/projects/DashboardHeader.vue index 39037d8ca..4ab541986 100644 --- a/packages/frontend-2/components/projects/DashboardHeader.vue +++ b/packages/frontend-2/components/projects/DashboardHeader.vue @@ -1,9 +1,6 @@ + diff --git a/packages/frontend-2/components/settings/workspaces/General/EditSlugDialog.vue b/packages/frontend-2/components/settings/workspaces/General/EditSlugDialog.vue index 85f9f3483..18a482134 100644 --- a/packages/frontend-2/components/settings/workspaces/General/EditSlugDialog.vue +++ b/packages/frontend-2/components/settings/workspaces/General/EditSlugDialog.vue @@ -19,11 +19,16 @@ @@ -33,11 +38,11 @@ import { useForm } from 'vee-validate' import { graphql } from '~~/lib/common/generated/gql' import type { LayoutDialogButton } from '@speckle/ui-components' import type { SettingsWorkspacesGeneralEditSlugDialog_WorkspaceFragment } from '~/lib/common/generated/gql/graphql' -import { - isStringOfLength, - isValidWorkspaceSlug -} from '~~/lib/common/helpers/validation' +import { isStringOfLength } from '~~/lib/common/helpers/validation' import { workspaceRoute } from '~/lib/common/helpers/route' +import { useQuery } from '@vue/apollo-composable' +import { validateWorkspaceSlugQuery } from '~/lib/workspaces/graphql/queries' +import { debounce } from 'lodash' graphql(` fragment SettingsWorkspacesGeneralEditSlugDialog_Workspace on Workspace { @@ -57,13 +62,27 @@ const emit = defineEmits<{ (e: 'update:slug', newSlug: string): void }>() -const { handleSubmit } = useForm<{ slug: string }>() - +// Main ref that holds the current value of the slug input. const workspaceShortId = ref(props.workspace.slug) +// Used to debounce API calls for slug validation. +const debouncedWorkspaceShortId = ref(props.workspace.slug) +// Keeps track of the initially generated slug to prevent unnecessary validations. +const originalSlug = ref(props.workspace.slug) + +const { error, loading } = useQuery( + validateWorkspaceSlugQuery, + () => ({ + slug: debouncedWorkspaceShortId.value + }), + () => ({ + enabled: debouncedWorkspaceShortId.value !== props.workspace.slug + }) +) + +const { handleSubmit, resetForm } = useForm<{ slug: string }>() const updateSlug = handleSubmit(() => { emit('update:slug', workspaceShortId.value) - isOpen.value = false }) const dialogButtons = computed((): LayoutDialogButton[] => [ @@ -78,12 +97,16 @@ const dialogButtons = computed((): LayoutDialogButton[] => [ text: 'Update', props: { color: 'primary', - disabled: workspaceShortId.value === props.workspace.slug + disabled: workspaceShortId.value === props.workspace.slug || error.value !== null }, submit: true } ]) +const updateDebouncedShortId = debounce((value: string) => { + debouncedWorkspaceShortId.value = value +}, 300) + watch( () => props.workspace.slug, (newValue) => { @@ -91,4 +114,14 @@ watch( }, { immediate: true } ) + +watch( + () => isOpen.value, + (newValue) => { + if (!newValue) { + resetForm() + error.value = null + } + } +) diff --git a/packages/frontend-2/components/workspace/CreateDialog.vue b/packages/frontend-2/components/workspace/CreateDialog.vue index fb59bb43b..06b96ec8e 100644 --- a/packages/frontend-2/components/workspace/CreateDialog.vue +++ b/packages/frontend-2/components/workspace/CreateDialog.vue @@ -23,12 +23,11 @@ label="Short ID" :help="getShortIdHelp" color="foundation" - :rules="[ - isStringOfLength({ maxLength: 50, minLength: 3 }), - isValidWorkspaceSlug - ]" + :loading="loading" + :rules="isStringOfLength({ maxLength: 50, minLength: 3 })" + :custom-error-message="error?.graphQLErrors[0]?.message" show-label - @update:model-value="shortIdManuallyEdited = true" + @update:model-value="onSlugChange" /> void>() @@ -69,15 +66,25 @@ const isOpen = defineModel('open', { required: true }) const createWorkspace = useCreateWorkspace() const { generateDefaultLogoIndex, getDefaultAvatar } = useWorkspacesAvatar() -const { handleSubmit } = useForm<{ name: string; slug: string }>() +const { handleSubmit, resetForm } = useForm<{ name: string; slug: string }>() const workspaceName = ref('') const workspaceShortId = ref('') +const debouncedWorkspaceShortId = ref('') const editAvatarMode = ref(false) const workspaceLogo = ref>() const defaultLogoIndex = ref(0) const shortIdManuallyEdited = ref(false) -const customShortIdError = ref('') + +const { error, loading } = useQuery( + validateWorkspaceSlugQuery, + () => ({ + slug: debouncedWorkspaceShortId.value + }), + () => ({ + enabled: !!debouncedWorkspaceShortId.value + }) +) const baseUrl = useRuntimeConfig().public.baseUrl @@ -105,7 +112,7 @@ const dialogButtons = computed((): LayoutDialogButton[] => [ disabled: !workspaceName.value.trim() || !workspaceShortId.value.trim() || - !!customShortIdError.value + error.value !== null } } ]) @@ -122,7 +129,7 @@ const handleCreateWorkspace = handleSubmit(async () => { { source: props.eventSource } ) - if (newWorkspace) { + if (newWorkspace && !newWorkspace?.errors) { emit('created') isOpen.value = false } @@ -135,21 +142,36 @@ const onLogoSave = (newVal: MaybeNullOrUndefined) => { const reset = () => { defaultLogoIndex.value = generateDefaultLogoIndex() - workspaceName.value = '' - workspaceShortId.value = '' + debouncedWorkspaceShortId.value = '' workspaceLogo.value = null editAvatarMode.value = false shortIdManuallyEdited.value = false - customShortIdError.value = '' + error.value = null } const updateShortId = debounce((newName: string) => { if (!shortIdManuallyEdited.value) { - workspaceShortId.value = generateSlugFromName({ name: newName }) + const newSlug = generateSlugFromName({ name: newName }) + workspaceShortId.value = newSlug + updateDebouncedShortId(newSlug) } }, 600) +const updateDebouncedShortId = debounce((newSlug: string) => { + debouncedWorkspaceShortId.value = newSlug +}, 300) + +const onSlugChange = (newSlug: string) => { + workspaceShortId.value = newSlug + shortIdManuallyEdited.value = true + updateDebouncedShortId(newSlug) +} + +// Seperate resets to avoid a temporary invalid state on submission watch(isOpen, (newVal) => { - if (newVal) reset() + if (!newVal) { + reset() + resetForm() + } }) diff --git a/packages/frontend-2/components/workspace/MoveProjectsDialog.vue b/packages/frontend-2/components/workspace/MoveProjectsDialog.vue index 67e9d55ff..5a9e6b9e0 100644 --- a/packages/frontend-2/components/workspace/MoveProjectsDialog.vue +++ b/packages/frontend-2/components/workspace/MoveProjectsDialog.vue @@ -29,7 +29,7 @@

- You don't have any projects that are moveable to this workspace + You don't have any projects that can be moved into this workspace

-
+
; updatedAt: Scalars['DateTime']['output']; /** Retrieve a specific project version by its ID */ - version?: Maybe; + version: Version; /** Returns a flat list of all project versions */ versions: VersionCollection; /** Return metadata about resources being requested in the viewer */ @@ -4749,7 +4749,7 @@ export type GendoAiRenderQueryVariables = Exact<{ }>; -export type GendoAiRenderQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, version?: { __typename?: 'Version', id: string, gendoAIRender: { __typename?: 'GendoAIRender', id: string, projectId: string, modelId: string, versionId: string, createdAt: string, updatedAt: string, gendoGenerationId?: string | null, status: string, prompt: string, camera?: {} | null, responseImage?: string | null, user?: { __typename?: 'AvatarUser', name: string, avatar?: string | null, id: string } | null } } | null } }; +export type GendoAiRenderQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, version: { __typename?: 'Version', id: string, gendoAIRender: { __typename?: 'GendoAIRender', id: string, projectId: string, modelId: string, versionId: string, createdAt: string, updatedAt: string, gendoGenerationId?: string | null, status: string, prompt: string, camera?: {} | null, responseImage?: string | null, user?: { __typename?: 'AvatarUser', name: string, avatar?: string | null, id: string } | null } } } }; export type GendoAiRendersQueryVariables = Exact<{ versionId: Scalars['String']['input']; @@ -4757,7 +4757,7 @@ export type GendoAiRendersQueryVariables = Exact<{ }>; -export type GendoAiRendersQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, version?: { __typename?: 'Version', id: string, gendoAIRenders: { __typename?: 'GendoAIRenderCollection', totalCount: number, items: Array<{ __typename?: 'GendoAIRender', id: string, createdAt: string, updatedAt: string, status: string, gendoGenerationId?: string | null, prompt: string, camera?: {} | null } | null> } } | null } }; +export type GendoAiRendersQuery = { __typename?: 'Query', project: { __typename?: 'Project', id: string, version: { __typename?: 'Version', id: string, gendoAIRenders: { __typename?: 'GendoAIRenderCollection', totalCount: number, items: Array<{ __typename?: 'GendoAIRender', id: string, createdAt: string, updatedAt: string, status: string, gendoGenerationId?: string | null, prompt: string, camera?: {} | null } | null> } } } }; export type ProjectVersionGendoAiRenderCreatedSubscriptionVariables = Exact<{ id: Scalars['String']['input']; @@ -5702,6 +5702,13 @@ export type MoveProjectsDialogQueryVariables = Exact<{ [key: string]: never; }>; export type MoveProjectsDialogQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', projects: { __typename?: 'ProjectCollection', items: Array<{ __typename?: 'Project', role?: string | null, id: string, name: string, workspace?: { __typename?: 'Workspace', id: string } | null, modelCount: { __typename?: 'ModelCollection', totalCount: number }, versions: { __typename?: 'VersionCollection', totalCount: number } }> } } | null }; +export type ValidateWorkspaceSlugQueryVariables = Exact<{ + slug: Scalars['String']['input']; +}>; + + +export type ValidateWorkspaceSlugQuery = { __typename?: 'Query', validateWorkspaceSlug: boolean }; + export type LegacyBranchRedirectMetadataQueryVariables = Exact<{ streamId: Scalars['String']['input']; branchName: Scalars['String']['input']; @@ -5716,7 +5723,7 @@ export type LegacyViewerCommitRedirectMetadataQueryVariables = Exact<{ }>; -export type LegacyViewerCommitRedirectMetadataQuery = { __typename?: 'Query', project: { __typename?: 'Project', version?: { __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string } } | null } }; +export type LegacyViewerCommitRedirectMetadataQuery = { __typename?: 'Query', project: { __typename?: 'Project', version: { __typename?: 'Version', id: string, model: { __typename?: 'Model', id: string } } } }; export type LegacyViewerStreamRedirectMetadataQueryVariables = Exact<{ streamId: Scalars['String']['input']; @@ -6086,6 +6093,7 @@ export const WorkspacePageQueryDocument = {"kind":"Document","definitions":[{"ki export const WorkspaceProjectsQueryDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceProjectsQuery"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceProjectsFilter"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceBySlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceSlug"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}},{"kind":"Argument","name":{"kind":"Name","value":"cursor"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}},{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsPageTeamDialogManagePermissions_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":"visibility"}},{"kind":"Field","name":{"kind":"Name","value":"role"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsModelPageEmbed_Project"},"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":"ProjectsPageTeamDialogManagePermissions_Project"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"},"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":"ProjectsModelPageEmbed_Project"}}]}},{"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"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageModelsActions_Project"}}]}},{"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":"id"}},{"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"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PendingFileUpload"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"FileUpload"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"projectId"}},{"kind":"Field","name":{"kind":"Name","value":"modelName"}},{"kind":"Field","name":{"kind":"Name","value":"convertedStatus"}},{"kind":"Field","name":{"kind":"Name","value":"convertedMessage"}},{"kind":"Field","name":{"kind":"Name","value":"uploadDate"}},{"kind":"Field","name":{"kind":"Name","value":"convertedLastUpdate"}},{"kind":"Field","name":{"kind":"Name","value":"fileType"}},{"kind":"Field","name":{"kind":"Name","value":"fileName"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardRenameDialog"},"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":"description"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsCardDeleteDialog"},"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":"FragmentDefinition","name":{"kind":"Name","value":"ProjectPageModelsActions"},"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":"FragmentDefinition","name":{"kind":"Name","value":"FunctionRunStatusForSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"FunctionRunStatusForSummary"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateFunctionRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"results"}},{"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":"function"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automation"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"AutomateRun"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"functionRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogFunctionRun_AutomateFunctionRun"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomationsStatusOrderedRuns_AutomationRun"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"automationRuns"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialogRunsRows_AutomateRun"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"TriggeredAutomationsStatus"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"TriggeredAutomationsStatusSummary"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatusDialog_TriggeredAutomationsStatus"}}]}},{"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":"Field","name":{"kind":"Name","value":"automationsStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AutomateRunsTriggerStatus_TriggeredAutomationsStatus"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceAvatar_Workspace"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Workspace"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"logo"}},{"kind":"Field","name":{"kind":"Name","value":"defaultLogoIndex"}}]}},{"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"}}],"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":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"slug"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceAvatar_Workspace"}}]}},{"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"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceProjectList_ProjectCollection"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCollection"}},"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":"ProjectDashboardItem"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cursor"}}]}}]} as unknown as DocumentNode; export const WorkspaceInviteDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkspaceInvite"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"token"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"options"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkspaceInviteLookupOptions"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workspaceInvite"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"workspaceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"workspaceId"}}},{"kind":"Argument","name":{"kind":"Name","value":"token"},"value":{"kind":"Variable","name":{"kind":"Name","value":"token"}}},{"kind":"Argument","name":{"kind":"Name","value":"options"},"value":{"kind":"Variable","name":{"kind":"Name","value":"options"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInviteBanner_PendingWorkspaceCollaborator"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"WorkspaceInviteBlock_PendingWorkspaceCollaborator"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LimitedUserAvatar"},"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"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceSlug"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInviteBanner_PendingWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"invitedBy"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"WorkspaceInviteBlock_PendingWorkspaceCollaborator"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"PendingWorkspaceCollaborator"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"workspaceName"}},{"kind":"Field","name":{"kind":"Name","value":"token"}},{"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":"FragmentSpread","name":{"kind":"Name","value":"LimitedUserAvatar"}}]}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"UseWorkspaceInviteManager_PendingWorkspaceCollaborator"}}]}}]} as unknown as DocumentNode; export const MoveProjectsDialogDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"MoveProjectsDialog"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MoveProjectsDialog_User"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_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","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"}}]}},{"kind":"Field","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":"FragmentDefinition","name":{"kind":"Name","value":"MoveProjectsDialog_User"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"items"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectsMoveToWorkspaceDialog_Project"}},{"kind":"Field","name":{"kind":"Name","value":"role"}},{"kind":"Field","name":{"kind":"Name","value":"workspace"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const ValidateWorkspaceSlugDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ValidateWorkspaceSlug"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"slug"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"validateWorkspaceSlug"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"slug"},"value":{"kind":"Variable","name":{"kind":"Name","value":"slug"}}}]}]}}]} as unknown as DocumentNode; export const LegacyBranchRedirectMetadataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LegacyBranchRedirectMetadata"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"branchName"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelByName"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"name"},"value":{"kind":"Variable","name":{"kind":"Name","value":"branchName"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]}}]} as unknown as DocumentNode; export const LegacyViewerCommitRedirectMetadataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LegacyViewerCommitRedirectMetadata"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"commitId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"version"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"commitId"}}}],"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"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const LegacyViewerStreamRedirectMetadataDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"LegacyViewerStreamRedirectMetadata"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"project"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"streamId"}}}],"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":"1"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"totalCount"}},{"kind":"Field","name":{"kind":"Name","value":"items"},"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"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; diff --git a/packages/frontend-2/lib/workspaces/graphql/queries.ts b/packages/frontend-2/lib/workspaces/graphql/queries.ts index 0faecce7d..72e9998a8 100644 --- a/packages/frontend-2/lib/workspaces/graphql/queries.ts +++ b/packages/frontend-2/lib/workspaces/graphql/queries.ts @@ -72,3 +72,9 @@ export const moveProjectsDialogQuery = graphql(` } } `) + +export const validateWorkspaceSlugQuery = graphql(` + query ValidateWorkspaceSlug($slug: String!) { + validateWorkspaceSlug(slug: $slug) + } +`) diff --git a/packages/frontend-2/pages/projects/[id]/index.vue b/packages/frontend-2/pages/projects/[id]/index.vue index eaf19dc67..639ae269d 100644 --- a/packages/frontend-2/pages/projects/[id]/index.vue +++ b/packages/frontend-2/pages/projects/[id]/index.vue @@ -171,10 +171,10 @@ const actionsItems = computed(() => { if (isWorkspacesEnabled.value && !project.value?.workspace?.id) { items.push([ { - title: 'Move...', + title: 'Move project...', id: ActionTypes.Move, disabled: !isOwner.value, - disabledTooltip: 'Only project owners can move projects into workspaces' + disabledTooltip: 'Only the project owner can move this project into a workspace' } ]) } diff --git a/packages/server/assets/core/typedefs/modelsAndVersions.graphql b/packages/server/assets/core/typedefs/modelsAndVersions.graphql index a10b78598..f03b38464 100644 --- a/packages/server/assets/core/typedefs/modelsAndVersions.graphql +++ b/packages/server/assets/core/typedefs/modelsAndVersions.graphql @@ -39,7 +39,7 @@ extend type Project { """ Retrieve a specific project version by its ID """ - version(id: String!): Version + version(id: String!): Version! """ Retrieve a specific project model by its ID diff --git a/packages/server/modules/core/graph/generated/graphql.ts b/packages/server/modules/core/graph/generated/graphql.ts index 5db603fcc..f482f2d4f 100644 --- a/packages/server/modules/core/graph/generated/graphql.ts +++ b/packages/server/modules/core/graph/generated/graphql.ts @@ -1836,7 +1836,7 @@ export type Project = { team: Array; updatedAt: Scalars['DateTime']['output']; /** Retrieve a specific project version by its ID */ - version?: Maybe; + version: Version; /** Returns a flat list of all project versions */ versions: VersionCollection; /** Return metadata about resources being requested in the viewer */ @@ -5508,7 +5508,7 @@ export type ProjectResolvers, ParentType, ContextType>; team?: Resolver, ParentType, ContextType>; updatedAt?: Resolver; - version?: Resolver, ParentType, ContextType, RequireFields>; + version?: Resolver>; versions?: Resolver>; viewerResources?: Resolver, ParentType, ContextType, RequireFields>; visibility?: Resolver; diff --git a/packages/server/modules/core/graph/resolvers/versions.ts b/packages/server/modules/core/graph/resolvers/versions.ts index 484380246..294304023 100644 --- a/packages/server/modules/core/graph/resolvers/versions.ts +++ b/packages/server/modules/core/graph/resolvers/versions.ts @@ -11,7 +11,7 @@ import { batchDeleteCommits, batchMoveCommitsFactory } from '@/modules/core/services/commit/batchCommitActions' -import { CommitUpdateError } from '@/modules/core/errors/commit' +import { CommitNotFoundError, CommitUpdateError } from '@/modules/core/errors/commit' import { createCommitByBranchIdFactory, markCommitReceivedAndNotify, @@ -106,9 +106,14 @@ const batchMoveCommits = batchMoveCommitsFactory({ export = { Project: { async version(parent, args, ctx) { - return await ctx.loaders.streams.getStreamCommit + const version = await ctx.loaders.streams.getStreamCommit .forStream(parent.id) .load(args.id) + if (!version) { + throw new CommitNotFoundError('Version not found') + } + + return version } }, Version: { diff --git a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts index a2decff57..b1a487086 100644 --- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts +++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts @@ -1820,7 +1820,7 @@ export type Project = { team: Array; updatedAt: Scalars['DateTime']['output']; /** Retrieve a specific project version by its ID */ - version?: Maybe; + version: Version; /** Returns a flat list of all project versions */ versions: VersionCollection; /** Return metadata about resources being requested in the viewer */ diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts index 9087d829b..aa31005ee 100644 --- a/packages/server/test/graphql/generated/graphql.ts +++ b/packages/server/test/graphql/generated/graphql.ts @@ -1821,7 +1821,7 @@ export type Project = { team: Array; updatedAt: Scalars['DateTime']['output']; /** Retrieve a specific project version by its ID */ - version?: Maybe; + version: Version; /** Returns a flat list of all project versions */ versions: VersionCollection; /** Return metadata about resources being requested in the viewer */ diff --git a/packages/ui-components/src/components/common/loading/Icon.vue b/packages/ui-components/src/components/common/loading/Icon.vue index 2dd25d431..911c16ada 100644 --- a/packages/ui-components/src/components/common/loading/Icon.vue +++ b/packages/ui-components/src/components/common/loading/Icon.vue @@ -1,8 +1,25 @@ + + diff --git a/packages/ui-components/src/components/form/Button.vue b/packages/ui-components/src/components/form/Button.vue index bef07387b..8d2c4043d 100644 --- a/packages/ui-components/src/components/form/Button.vue +++ b/packages/ui-components/src/components/form/Button.vue @@ -25,8 +25,8 @@ import { isObjectLike } from 'lodash' import type { PropAnyComponent } from '~~/src/helpers/common/components' import { computed, resolveDynamicComponent } from 'vue' import type { Nullable } from '@speckle/shared' -import { ArrowPathIcon } from '@heroicons/vue/24/solid' import type { FormButtonStyle, FormButtonSize } from '~~/src/helpers/form/button' +import { CommonLoadingIcon } from '~~/src/lib' const emit = defineEmits<{ /** @@ -122,7 +122,9 @@ const buttonType = computed(() => { }) const isDisabled = computed(() => props.disabled || props.loading) -const finalLeftIcon = computed(() => (props.loading ? ArrowPathIcon : props.iconLeft)) +const finalLeftIcon = computed(() => + props.loading ? CommonLoadingIcon : props.iconLeft +) const bgAndBorderClasses = computed(() => { const classParts: string[] = [] @@ -266,10 +268,6 @@ const buttonClasses = computed(() => { const iconClasses = computed(() => { const classParts: string[] = ['shrink-0'] - if (props.loading) { - classParts.push('animate-spin') - } - switch (props.size) { case 'sm': classParts.push('h-5 w-5 p-0.5') diff --git a/packages/ui-components/src/components/form/TextInput.stories.ts b/packages/ui-components/src/components/form/TextInput.stories.ts index 22337f00f..005b8c7a6 100644 --- a/packages/ui-components/src/components/form/TextInput.stories.ts +++ b/packages/ui-components/src/components/form/TextInput.stories.ts @@ -154,6 +154,14 @@ export const LabelLeft = mergeStories(Default, { } }) +export const Loading = mergeStories(Default, { + args: { + name: generateRandomName('loading'), + label: 'With loading spinner', + loading: true + } +}) + export const WithCustomRightSlot = mergeStories(Default, { render: (args) => ({ components: { FormTextInput, FormButton }, diff --git a/packages/ui-components/src/components/form/TextInput.vue b/packages/ui-components/src/components/form/TextInput.vue index 27c5c1599..2bcb8ca7a 100644 --- a/packages/ui-components/src/components/form/TextInput.vue +++ b/packages/ui-components/src/components/form/TextInput.vue @@ -37,6 +37,13 @@ aria-hidden="true" />
+
+ +
+ '' diff --git a/packages/ui-components/src/composables/form/textInput.ts b/packages/ui-components/src/composables/form/textInput.ts index 0e191e0b6..a9e60bee1 100644 --- a/packages/ui-components/src/composables/form/textInput.ts +++ b/packages/ui-components/src/composables/form/textInput.ts @@ -26,6 +26,7 @@ export function useTextInputCore(params: { autoFocus?: boolean showClear?: boolean useLabelInErrors?: boolean + customErrorMessage?: string hideErrorMessage?: boolean color?: InputColor labelPosition?: LabelPosition @@ -41,11 +42,15 @@ export function useTextInputCore(params: { }) { const { props, inputEl, emit, options } = params - const { value, errorMessage: error } = useField(props.name, props.rules, { - validateOnMount: unref(props.validateOnMount), - validateOnValueUpdate: unref(props.validateOnValueUpdate), - initialValue: unref(props.modelValue) || undefined - }) + const { value, errorMessage: veeErrorMessage } = useField( + props.name, + props.rules, + { + validateOnMount: unref(props.validateOnMount), + validateOnValueUpdate: unref(props.validateOnValueUpdate), + initialValue: unref(props.modelValue) || undefined + } + ) const labelClasses = computed(() => { const classParts = [ @@ -76,7 +81,7 @@ export function useTextInputCore(params: { coreInputClasses.value ] - if (error.value) { + if (hasError.value) { classParts.push('!border-danger') } else { classParts.push('border-0 focus:ring-2 focus:ring-outline-2') @@ -99,12 +104,19 @@ export function useTextInputCore(params: { const internalHelpTipId = ref(nanoid()) const title = computed(() => unref(props.label) || unref(props.name)) + const errorMessage = computed(() => { - const base = error.value + if (unref(props.customErrorMessage)) { + return unref(props.customErrorMessage) + } + + const base = veeErrorMessage.value if (!base || !unref(props.useLabelInErrors)) return base return base.replace('Value', title.value) }) + const hasError = computed(() => !!errorMessage.value) + const hideHelpTip = computed( () => errorMessage.value && unref(props.hideErrorMessage) ) @@ -115,7 +127,7 @@ export function useTextInputCore(params: { ) const helpTipClasses = computed((): string => { const classParts = ['text-body-2xs break-words'] - classParts.push(error.value ? 'text-danger' : 'text-foreground-2') + classParts.push(hasError.value ? 'text-danger' : 'text-foreground-2') return classParts.join(' ') }) const shouldShowClear = computed(() => { @@ -154,7 +166,8 @@ export function useTextInputCore(params: { clear, focus, labelClasses, - shouldShowClear + shouldShowClear, + hasError } } diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index 20ae1b9f7..1f4e6da35 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -1357,11 +1357,11 @@ export default class Sandbox { url.includes('latest') ? 'AuthTokenLatest' : 'AuthToken' ) as string const objUrls = await UrlHelper.getResourceUrls(url, authToken) - for (const url of objUrls) { + for (const objUrl of objUrls) { console.log(`Loading ${url}`) const loader = new SpeckleLoader( this.viewer.getWorldTree(), - url, + objUrl, authToken, true, undefined @@ -1377,7 +1377,7 @@ export default class Sandbox { console.error(`Loader warning: ${arg.message}`) }) - await this.viewer.loadObject(loader, true) + void this.viewer.loadObject(loader, true) } localStorage.setItem('last-load-url', url) } diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 6859ac6b6..d172c4cd7 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -105,7 +105,7 @@ const getStream = () => { // prettier-ignore // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' // Revit sample house (good for bim-like stuff with many display meshes) - // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' + 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.systems/streams/c1faab5c62/commits/ab1a1ab2b6' // 'https://app.speckle.systems/streams/da9e320dad/commits/5388ef24b8' // 'https://latest.speckle.systems/streams/58b5648c4d/commits/60371ecb2d' @@ -424,7 +424,8 @@ const getStream = () => { // 'https://app.speckle.systems/projects/00a5c443d6/models/de56edf901' - 'https://app.speckle.systems/projects/6cf358a40e/models/e01ffbc891@f1ddc19011' + // 'https://app.speckle.systems/projects/6cf358a40e/models/e01ffbc891@f1ddc19011' + // 'https://latest.speckle.systems/projects/2c5c4cd493/models/cb20c2e87b02003d4b2ddf4df96912b9,8a2c9a45093fecb42273607d0c936d66' ) } diff --git a/packages/viewer/src/modules/batching/Batcher.ts b/packages/viewer/src/modules/batching/Batcher.ts index 9049fabc0..003c60d43 100644 --- a/packages/viewer/src/modules/batching/Batcher.ts +++ b/packages/viewer/src/modules/batching/Batcher.ts @@ -74,7 +74,7 @@ export default class Batcher { await pause.wait(50) } - let instancedNodes = worldTree.findId(g) + let instancedNodes = worldTree.findId(g, renderTree.subtreeId) if (!instancedNodes) { continue } @@ -99,7 +99,7 @@ export default class Batcher { } for (const v in instancedBatches) { for (let k = 0; k < instancedBatches[v].length; k++) { - const nodes = worldTree.findId(instancedBatches[v][k]) + const nodes = worldTree.findId(instancedBatches[v][k], renderTree.subtreeId) if (!nodes) continue /** Make sure entire instance set is instanced */ let instanced = true diff --git a/packages/viewer/src/modules/tree/RenderTree.ts b/packages/viewer/src/modules/tree/RenderTree.ts index 13ea770a9..06beaff53 100644 --- a/packages/viewer/src/modules/tree/RenderTree.ts +++ b/packages/viewer/src/modules/tree/RenderTree.ts @@ -15,6 +15,10 @@ export class RenderTree { return this.root.model.id } + public get subtreeId(): number { + return this.root.model.subtreeId + } + public constructor(tree: WorldTree, subtreeRoot: TreeNode) { this.tree = tree this.root = subtreeRoot @@ -192,8 +196,11 @@ export class RenderTree { }) } - public getRenderViewsForNodeId(id: string): NodeRenderView[] | null { - const nodes = this.tree.findId(id) + public getRenderViewsForNodeId( + id: string, + subtreeId?: number + ): NodeRenderView[] | null { + const nodes = this.tree.findId(id, subtreeId) if (!nodes) { Logger.warn(`Id ${id} does not exist`) return null