From 75af7e5418421ca0b13c3c1f0ca1a79cda557f10 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle <139135120+andrewwallacespeckle@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:13:00 +0100 Subject: [PATCH 1/6] feat(fe2): Add deletion verification when deleting workspaces (#3277) * Add confirm input to delete workspace * Redirect home and open deletion reason dialog * Merge delete dialogs * Remove glitch file * Update route * Update feedback message * Improve messaging in discord * Testing changes * Improve text * Remove lg prop * Spacing --- .../workspaces/General/DeleteDialog.vue | 82 ++++++++++++++----- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue b/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue index 6de17bd4f..e69523428 100644 --- a/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue +++ b/packages/frontend-2/components/settings/workspaces/General/DeleteDialog.vue @@ -5,23 +5,36 @@ max-width="sm" :buttons="dialogButtons" > -

- Are you sure you want to - permanently delete - the selected workspace? -

-
- {{ workspace.name }} -
-

- This action - cannot - be undone. +

+ Are you sure you want to permanently delete + {{ workspace.name }}? + This action cannot be undone.

+ + + From 922f6a2b5b5d7d910aaf108d383bdd0e703d4295 Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle <139135120+andrewwallacespeckle@users.noreply.github.com> Date: Thu, 17 Oct 2024 14:41:31 +0100 Subject: [PATCH 2/6] fix(fe2): Add Validation for Slugs (#3255) * Don't close dialog on invalid slug * Custom error message on textInput * Query backend to validate slug * Updated loading spinner * Update to error in Create Dialog * Add to edit. Debounce input * GQL * Update CreateDialog.vue * Update Edit Dialog * Fix typo * Update reset to avoid error on submit * Temporary replacement until we swap to WebFlow API * Update Icon.vue * Fix build! --- .../settings/workspaces/General.vue | 2 + .../workspaces/General/EditSlugDialog.vue | 51 +++++++++++--- .../components/workspace/CreateDialog.vue | 60 +++++++++++------ .../lib/common/generated/gql/gql.ts | 5 ++ .../lib/common/generated/gql/graphql.ts | 8 +++ .../lib/workspaces/graphql/queries.ts | 6 ++ .../src/components/common/loading/Icon.vue | 67 +++++++++++++++++-- .../src/components/form/Button.vue | 10 ++- .../src/components/form/TextInput.stories.ts | 8 +++ .../src/components/form/TextInput.vue | 16 +++++ .../src/composables/form/textInput.ts | 31 ++++++--- 11 files changed, 214 insertions(+), 50 deletions(-) diff --git a/packages/frontend-2/components/settings/workspaces/General.vue b/packages/frontend-2/components/settings/workspaces/General.vue index 9b233d67d..0c1ab0304 100644 --- a/packages/frontend-2/components/settings/workspaces/General.vue +++ b/packages/frontend-2/components/settings/workspaces/General.vue @@ -290,6 +290,8 @@ const updateWorkspaceSlug = async (newSlug: string) => { title: 'Workspace short ID updated' }) + showEditSlugDialog.value = false + slug.value = newSlug if (route.params.slug === oldSlug) { diff --git a/packages/frontend-2/components/settings/workspaces/General/EditSlugDialog.vue b/packages/frontend-2/components/settings/workspaces/General/EditSlugDialog.vue index 85f9f3483..a46000ebf 100644 --- a/packages/frontend-2/components/settings/workspaces/General/EditSlugDialog.vue +++ b/packages/frontend-2/components/settings/workspaces/General/EditSlugDialog.vue @@ -22,8 +22,13 @@ label="Short ID" :help="`${baseUrl}${workspaceRoute(workspaceShortId)}`" color="foundation" - :rules="[isStringOfLength({ maxLength: 50, minLength: 3 }), isValidWorkspaceSlug]" + :rules="[isStringOfLength({ maxLength: 50, minLength: 3 })]" + :custom-error-message=" + workspaceShortId !== originalSlug ? error?.graphQLErrors[0]?.message : undefined + " + :loading="loading" show-label + @update:model-value="updateDebouncedShortId" /> @@ -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/lib/common/generated/gql/gql.ts b/packages/frontend-2/lib/common/generated/gql/gql.ts index c768e8e19..b0688ce24 100644 --- a/packages/frontend-2/lib/common/generated/gql/gql.ts +++ b/packages/frontend-2/lib/common/generated/gql/gql.ts @@ -325,6 +325,7 @@ const documents = { "\n query WorkspaceProjectsQuery(\n $workspaceSlug: String!\n $filter: WorkspaceProjectsFilter\n $cursor: String\n ) {\n workspaceBySlug(slug: $workspaceSlug) {\n id\n projects(filter: $filter, cursor: $cursor, limit: 10) {\n ...WorkspaceProjectList_ProjectCollection\n }\n }\n }\n": types.WorkspaceProjectsQueryDocument, "\n query WorkspaceInvite(\n $workspaceId: String\n $token: String\n $options: WorkspaceInviteLookupOptions\n ) {\n workspaceInvite(workspaceId: $workspaceId, token: $token, options: $options) {\n ...WorkspaceInviteBanner_PendingWorkspaceCollaborator\n ...WorkspaceInviteBlock_PendingWorkspaceCollaborator\n }\n }\n": types.WorkspaceInviteDocument, "\n query MoveProjectsDialog {\n activeUser {\n ...MoveProjectsDialog_User\n }\n }\n": types.MoveProjectsDialogDocument, + "\n query ValidateWorkspaceSlug($slug: String!) {\n validateWorkspaceSlug(slug: $slug)\n }\n": types.ValidateWorkspaceSlugDocument, "\n query LegacyBranchRedirectMetadata($streamId: String!, $branchName: String!) {\n project(id: $streamId) {\n modelByName(name: $branchName) {\n id\n }\n }\n }\n": types.LegacyBranchRedirectMetadataDocument, "\n query LegacyViewerCommitRedirectMetadata($streamId: String!, $commitId: String!) {\n project(id: $streamId) {\n version(id: $commitId) {\n id\n model {\n id\n }\n }\n }\n }\n": types.LegacyViewerCommitRedirectMetadataDocument, "\n query LegacyViewerStreamRedirectMetadata($streamId: String!) {\n project(id: $streamId) {\n id\n versions(limit: 1) {\n totalCount\n items {\n id\n model {\n id\n }\n }\n }\n }\n }\n": types.LegacyViewerStreamRedirectMetadataDocument, @@ -1601,6 +1602,10 @@ export function graphql(source: "\n query WorkspaceInvite(\n $workspaceId: S * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n query MoveProjectsDialog {\n activeUser {\n ...MoveProjectsDialog_User\n }\n }\n"): (typeof documents)["\n query MoveProjectsDialog {\n activeUser {\n ...MoveProjectsDialog_User\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n query ValidateWorkspaceSlug($slug: String!) {\n validateWorkspaceSlug(slug: $slug)\n }\n"): (typeof documents)["\n query ValidateWorkspaceSlug($slug: String!) {\n validateWorkspaceSlug(slug: $slug)\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/frontend-2/lib/common/generated/gql/graphql.ts b/packages/frontend-2/lib/common/generated/gql/graphql.ts index dafb6814f..e10c45735 100644 --- a/packages/frontend-2/lib/common/generated/gql/graphql.ts +++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts @@ -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']; @@ -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/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 } } From 4bce48a6d80c7ef52c5af7bf4ef0e01ae6e14a10 Mon Sep 17 00:00:00 2001 From: Alexandru Popovici Date: Thu, 17 Oct 2024 17:02:08 +0300 Subject: [PATCH 3/6] Handled WEB-2025 (#3329) --- packages/viewer-sandbox/src/Sandbox.ts | 6 +++--- packages/viewer-sandbox/src/main.ts | 5 +++-- packages/viewer/src/modules/batching/Batcher.ts | 4 ++-- packages/viewer/src/modules/tree/RenderTree.ts | 11 +++++++++-- 4 files changed, 17 insertions(+), 9 deletions(-) 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 From e224613c70f65c8afb92207322b9640ad277952b Mon Sep 17 00:00:00 2001 From: andrewwallacespeckle <139135120+andrewwallacespeckle@users.noreply.github.com> Date: Fri, 18 Oct 2024 11:33:16 +0100 Subject: [PATCH 4/6] feat(fe2): Add forum message to feedback dialog (#3327) * Update feedback dialog with forum message * Adjust leading of forum message --- packages/frontend-2/components/feedback/Dialog.vue | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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. +

From 1f1300baaf8649362e3f4587fab27213581e6002 Mon Sep 17 00:00:00 2001 From: Benjamin Ottensten Date: Fri, 18 Oct 2024 12:40:20 +0200 Subject: [PATCH 5/6] Fix: Various papercuts (#3296) * Update move copy * Ensure version counter doesn't break onto 2 lines * Increase contrast in breadcrumb Darker '/' and only font-medium on the active item * Make it clearer to input new short ID and not current * Add ... to menu items * Change more "..." in menu items * Fix gap and border between invite banners And move the background to the individual banners --- packages/frontend-2/components/header/NavLink.vue | 6 +++--- packages/frontend-2/components/invite/Banner.vue | 2 +- packages/frontend-2/components/projects/DashboardHeader.vue | 5 +---- .../frontend-2/components/settings/server/ActiveUsers.vue | 4 ++-- .../components/settings/server/PendingInvitations.vue | 2 +- .../components/settings/shared/projects/index.vue | 4 ++-- .../settings/workspaces/General/EditSlugDialog.vue | 2 +- .../frontend-2/components/workspace/MoveProjectsDialog.vue | 4 ++-- .../frontend-2/components/workspace/page/VersionCount.vue | 2 +- packages/frontend-2/pages/projects/[id]/index.vue | 4 ++-- 10 files changed, 16 insertions(+), 19 deletions(-) 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 @@