+
diff --git a/packages/frontend-2/lib/auth/composables/activeUser.ts b/packages/frontend-2/lib/auth/composables/activeUser.ts
index 849ca9751..e98f96a17 100644
--- a/packages/frontend-2/lib/auth/composables/activeUser.ts
+++ b/packages/frontend-2/lib/auth/composables/activeUser.ts
@@ -57,11 +57,21 @@ export function useActiveUser() {
const user = activeUser.value
return getDistinctId(user)
})
+ const userId = computed(() => activeUser.value?.id)
const isGuest = computed(() => activeUser.value?.role === Roles.Server.Guest)
const isAdmin = computed(() => activeUser.value?.role === Roles.Server.Admin)
- return { activeUser, isLoggedIn, distinctId, refetch, onResult, isGuest, isAdmin }
+ return {
+ activeUser,
+ userId,
+ isLoggedIn,
+ distinctId,
+ refetch,
+ onResult,
+ isGuest,
+ isAdmin
+ }
}
/**
diff --git a/packages/frontend-2/lib/auth/composables/postAuthRedirect.ts b/packages/frontend-2/lib/auth/composables/postAuthRedirect.ts
index 8b1819655..ee372cc1c 100644
--- a/packages/frontend-2/lib/auth/composables/postAuthRedirect.ts
+++ b/packages/frontend-2/lib/auth/composables/postAuthRedirect.ts
@@ -1,3 +1,4 @@
+import type { RouteLocationNormalized } from '#vue-router'
import type { Optional } from '@speckle/shared'
import { reduce } from 'lodash-es'
import { useSynchronizedCookie } from '~~/lib/common/composables/reactiveCookie'
@@ -8,10 +9,12 @@ const usePostAuthRedirectCookie = () =>
maxAge: 60 * 5 // 5 mins
})
-export const usePostAuthRedirect = () => {
+export const usePostAuthRedirect = (
+ options?: Partial<{ route: RouteLocationNormalized }>
+) => {
const cookie = usePostAuthRedirectCookie()
const router = useRouter()
- const route = useRoute()
+ const route = options?.route || useRoute()
const logger = useLogger()
const hadPendingRedirect = computed(() => !!cookie.value?.length)
diff --git a/packages/frontend-2/lib/common/composables/toast.ts b/packages/frontend-2/lib/common/composables/toast.ts
index a5b7ee997..e52d37666 100644
--- a/packages/frontend-2/lib/common/composables/toast.ts
+++ b/packages/frontend-2/lib/common/composables/toast.ts
@@ -38,7 +38,7 @@ export function useGlobalToastManager() {
// (re-)init timeout
stop()
- start()
+ if (newVal.autoClose !== false) start()
})
},
{ deep: true }
diff --git a/packages/frontend-2/lib/common/generated/gql/gql.ts b/packages/frontend-2/lib/common/generated/gql/gql.ts
index b7c58cfdf..8dc4422d8 100644
--- a/packages/frontend-2/lib/common/generated/gql/gql.ts
+++ b/packages/frontend-2/lib/common/generated/gql/gql.ts
@@ -86,8 +86,10 @@ const documents = {
"\n mutation DeleteApplication($appId: String!) {\n appDelete(appId: $appId)\n }\n": types.DeleteApplicationDocument,
"\n mutation CreateApplication($app: AppCreateInput!) {\n appCreate(app: $app)\n }\n": types.CreateApplicationDocument,
"\n mutation EditApplication($app: AppUpdateInput!) {\n appUpdate(app: $app)\n }\n": types.EditApplicationDocument,
+ "\n mutation RevokeAppAccess($appId: String!) {\n appRevokeAccess(appId: $appId)\n }\n": types.RevokeAppAccessDocument,
"\n query DeveloperSettingsAccessTokens {\n activeUser {\n id\n apiTokens {\n id\n name\n lastUsed\n lastChars\n createdAt\n scopes\n }\n }\n }\n": types.DeveloperSettingsAccessTokensDocument,
"\n query DeveloperSettingsApplications {\n activeUser {\n createdApps {\n id\n secret\n name\n description\n redirectUrl\n scopes {\n name\n description\n }\n }\n id\n }\n }\n": types.DeveloperSettingsApplicationsDocument,
+ "\n query DeveloperSettingsAuthorizedApps {\n activeUser {\n id\n authorizedApps {\n id\n description\n name\n author {\n id\n name\n avatar\n }\n }\n }\n }\n": types.DeveloperSettingsAuthorizedAppsDocument,
"\n query SearchProjects($search: String, $onlyWithRoles: [String!] = null) {\n activeUser {\n projects(limit: 10, filter: { search: $search, onlyWithRoles: $onlyWithRoles }) {\n totalCount\n items {\n ...FormSelectProjects_Project\n }\n }\n }\n }\n": types.SearchProjectsDocument,
"\n fragment ProjectDashboardItemNoModels on Project {\n id\n name\n createdAt\n updatedAt\n role\n team {\n user {\n id\n name\n avatar\n }\n }\n ...ProjectPageModelsCardProject\n }\n": types.ProjectDashboardItemNoModelsFragmentDoc,
"\n fragment ProjectDashboardItem on Project {\n id\n ...ProjectDashboardItemNoModels\n models(limit: 4, filter: { onlyWithVersions: true }) {\n totalCount\n items {\n ...ProjectPageLatestItemsModelItem\n }\n }\n pendingImportedModels(limit: 4) {\n ...PendingFileUpload\n }\n }\n": types.ProjectDashboardItemFragmentDoc,
@@ -481,6 +483,10 @@ export function graphql(source: "\n mutation CreateApplication($app: AppCreateI
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation EditApplication($app: AppUpdateInput!) {\n appUpdate(app: $app)\n }\n"): (typeof documents)["\n mutation EditApplication($app: AppUpdateInput!) {\n appUpdate(app: $app)\n }\n"];
+/**
+ * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
+ */
+export function graphql(source: "\n mutation RevokeAppAccess($appId: String!) {\n appRevokeAccess(appId: $appId)\n }\n"): (typeof documents)["\n mutation RevokeAppAccess($appId: String!) {\n appRevokeAccess(appId: $appId)\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
@@ -489,6 +495,10 @@ export function graphql(source: "\n query DeveloperSettingsAccessTokens {\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 DeveloperSettingsApplications {\n activeUser {\n createdApps {\n id\n secret\n name\n description\n redirectUrl\n scopes {\n name\n description\n }\n }\n id\n }\n }\n"): (typeof documents)["\n query DeveloperSettingsApplications {\n activeUser {\n createdApps {\n id\n secret\n name\n description\n redirectUrl\n scopes {\n name\n description\n }\n }\n id\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 DeveloperSettingsAuthorizedApps {\n activeUser {\n id\n authorizedApps {\n id\n description\n name\n author {\n id\n name\n avatar\n }\n }\n }\n }\n"): (typeof documents)["\n query DeveloperSettingsAuthorizedApps {\n activeUser {\n id\n authorizedApps {\n id\n description\n name\n author {\n id\n name\n avatar\n }\n }\n }\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 51e92e93e..fe8e9704e 100644
--- a/packages/frontend-2/lib/common/generated/gql/graphql.ts
+++ b/packages/frontend-2/lib/common/generated/gql/graphql.ts
@@ -2603,7 +2603,7 @@ export type User = {
/** Returns a list of your personal api tokens. */
apiTokens: Array
;
/** Returns the apps you have authorized. */
- authorizedApps?: Maybe>>;
+ authorizedApps?: Maybe>;
avatar?: Maybe;
bio?: Maybe;
/**
@@ -3171,6 +3171,13 @@ export type EditApplicationMutationVariables = Exact<{
export type EditApplicationMutation = { __typename?: 'Mutation', appUpdate: boolean };
+export type RevokeAppAccessMutationVariables = Exact<{
+ appId: Scalars['String'];
+}>;
+
+
+export type RevokeAppAccessMutation = { __typename?: 'Mutation', appRevokeAccess?: boolean | null };
+
export type DeveloperSettingsAccessTokensQueryVariables = Exact<{ [key: string]: never; }>;
@@ -3181,6 +3188,11 @@ export type DeveloperSettingsApplicationsQueryVariables = Exact<{ [key: string]:
export type DeveloperSettingsApplicationsQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, createdApps?: Array<{ __typename?: 'ServerApp', id: string, secret?: string | null, name: string, description?: string | null, redirectUrl: string, scopes: Array<{ __typename?: 'Scope', name: string, description: string }> }> | null } | null };
+export type DeveloperSettingsAuthorizedAppsQueryVariables = Exact<{ [key: string]: never; }>;
+
+
+export type DeveloperSettingsAuthorizedAppsQuery = { __typename?: 'Query', activeUser?: { __typename?: 'User', id: string, authorizedApps?: Array<{ __typename?: 'ServerAppListItem', id: string, description?: string | null, name: string, author?: { __typename?: 'AppAuthor', id: string, name: string, avatar?: string | null } | null }> | null } | null };
+
export type SearchProjectsQueryVariables = Exact<{
search?: InputMaybe;
onlyWithRoles?: InputMaybe | Scalars['String']>;
@@ -3838,8 +3850,10 @@ export const CreateAccessTokenDocument = {"kind":"Document","definitions":[{"kin
export const DeleteApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appDelete"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}}]}]}}]} as unknown as DocumentNode;
export const CreateApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"app"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AppCreateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appCreate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"app"},"value":{"kind":"Variable","name":{"kind":"Name","value":"app"}}}]}]}}]} as unknown as DocumentNode;
export const EditApplicationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"EditApplication"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"app"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AppUpdateInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appUpdate"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"app"},"value":{"kind":"Variable","name":{"kind":"Name","value":"app"}}}]}]}}]} as unknown as DocumentNode;
+export const RevokeAppAccessDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RevokeAppAccess"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"appId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"appRevokeAccess"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"appId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"appId"}}}]}]}}]} as unknown as DocumentNode;
export const DeveloperSettingsAccessTokensDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DeveloperSettingsAccessTokens"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"apiTokens"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"lastUsed"}},{"kind":"Field","name":{"kind":"Name","value":"lastChars"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"}}]}}]}}]}}]} as unknown as DocumentNode;
export const DeveloperSettingsApplicationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DeveloperSettingsApplications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createdApps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"secret"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"redirectUrl"}},{"kind":"Field","name":{"kind":"Name","value":"scopes"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode;
+export const DeveloperSettingsAuthorizedAppsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"DeveloperSettingsAuthorizedApps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"authorizedApps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"author"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}}]}}]}}]}}]}}]} as unknown as DocumentNode;
export const SearchProjectsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"SearchProjects"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"search"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"onlyWithRoles"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},"defaultValue":{"kind":"NullValue"}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"activeUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projects"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"limit"},"value":{"kind":"IntValue","value":"10"}},{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"search"},"value":{"kind":"Variable","name":{"kind":"Name","value":"search"}}},{"kind":"ObjectField","name":{"kind":"Name","value":"onlyWithRoles"},"value":{"kind":"Variable","name":{"kind":"Name","value":"onlyWithRoles"}}}]}}],"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":"FormSelectProjects_Project"}}]}}]}}]}}]}},...FormSelectProjects_ProjectFragmentDoc.definitions]} as unknown as DocumentNode;
export const CreateModelDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateModel"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateModelInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageLatestItemsModelItem"}}]}}]}}]}},...ProjectPageLatestItemsModelItemFragmentDoc.definitions,...PendingFileUploadFragmentDoc.definitions,...ProjectPageModelsCardRenameDialogFragmentDoc.definitions,...ProjectPageModelsCardDeleteDialogFragmentDoc.definitions,...ProjectPageModelsActionsFragmentDoc.definitions,...ModelCardAutomationStatus_ModelFragmentDoc.definitions,...ModelCardAutomationStatus_AutomationsStatusFragmentDoc.definitions]} as unknown as DocumentNode;
export const CreateProjectDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateProject"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"ProjectCreateInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"projectMutations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"create"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectPageProject"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"ProjectDashboardItem"}}]}}]}}]}},...ProjectPageProjectFragmentDoc.definitions,...ProjectPageProjectHeaderFragmentDoc.definitions,...ProjectPageStatsBlockTeamFragmentDoc.definitions,...LimitedUserAvatarFragmentDoc.definitions,...ProjectPageTeamDialogFragmentDoc.definitions,...ProjectsPageTeamDialogManagePermissions_ProjectFragmentDoc.definitions,...ProjectPageStatsBlockVersionsFragmentDoc.definitions,...ProjectPageStatsBlockModelsFragmentDoc.definitions,...ProjectPageStatsBlockCommentsFragmentDoc.definitions,...ProjectPageLatestItemsModelsFragmentDoc.definitions,...ProjectPageModelsStructureItem_ProjectFragmentDoc.definitions,...ProjectPageModelsActions_ProjectFragmentDoc.definitions,...ProjectsModelPageEmbed_ProjectFragmentDoc.definitions,...ProjectPageLatestItemsCommentsFragmentDoc.definitions,...ProjectDashboardItemFragmentDoc.definitions,...ProjectDashboardItemNoModelsFragmentDoc.definitions,...ProjectPageModelsCardProjectFragmentDoc.definitions,...ProjectPageLatestItemsModelItemFragmentDoc.definitions,...PendingFileUploadFragmentDoc.definitions,...ProjectPageModelsCardRenameDialogFragmentDoc.definitions,...ProjectPageModelsCardDeleteDialogFragmentDoc.definitions,...ProjectPageModelsActionsFragmentDoc.definitions,...ModelCardAutomationStatus_ModelFragmentDoc.definitions,...ModelCardAutomationStatus_AutomationsStatusFragmentDoc.definitions]} as unknown as DocumentNode;
diff --git a/packages/frontend-2/lib/common/helpers/md5.js b/packages/frontend-2/lib/common/helpers/md5.js
deleted file mode 100644
index 75d2be9a4..000000000
--- a/packages/frontend-2/lib/common/helpers/md5.js
+++ /dev/null
@@ -1,179 +0,0 @@
-/**
- * Lightweight MD5 implementation.
- * @see http://www.myersdaily.org/joseph/javascript/md5-text.html
- */
-
-function md5cycle(x, k) {
- let a = x[0],
- b = x[1],
- c = x[2],
- d = x[3]
-
- a = ff(a, b, c, d, k[0], 7, -680876936)
- d = ff(d, a, b, c, k[1], 12, -389564586)
- c = ff(c, d, a, b, k[2], 17, 606105819)
- b = ff(b, c, d, a, k[3], 22, -1044525330)
- a = ff(a, b, c, d, k[4], 7, -176418897)
- d = ff(d, a, b, c, k[5], 12, 1200080426)
- c = ff(c, d, a, b, k[6], 17, -1473231341)
- b = ff(b, c, d, a, k[7], 22, -45705983)
- a = ff(a, b, c, d, k[8], 7, 1770035416)
- d = ff(d, a, b, c, k[9], 12, -1958414417)
- c = ff(c, d, a, b, k[10], 17, -42063)
- b = ff(b, c, d, a, k[11], 22, -1990404162)
- a = ff(a, b, c, d, k[12], 7, 1804603682)
- d = ff(d, a, b, c, k[13], 12, -40341101)
- c = ff(c, d, a, b, k[14], 17, -1502002290)
- b = ff(b, c, d, a, k[15], 22, 1236535329)
-
- a = gg(a, b, c, d, k[1], 5, -165796510)
- d = gg(d, a, b, c, k[6], 9, -1069501632)
- c = gg(c, d, a, b, k[11], 14, 643717713)
- b = gg(b, c, d, a, k[0], 20, -373897302)
- a = gg(a, b, c, d, k[5], 5, -701558691)
- d = gg(d, a, b, c, k[10], 9, 38016083)
- c = gg(c, d, a, b, k[15], 14, -660478335)
- b = gg(b, c, d, a, k[4], 20, -405537848)
- a = gg(a, b, c, d, k[9], 5, 568446438)
- d = gg(d, a, b, c, k[14], 9, -1019803690)
- c = gg(c, d, a, b, k[3], 14, -187363961)
- b = gg(b, c, d, a, k[8], 20, 1163531501)
- a = gg(a, b, c, d, k[13], 5, -1444681467)
- d = gg(d, a, b, c, k[2], 9, -51403784)
- c = gg(c, d, a, b, k[7], 14, 1735328473)
- b = gg(b, c, d, a, k[12], 20, -1926607734)
-
- a = hh(a, b, c, d, k[5], 4, -378558)
- d = hh(d, a, b, c, k[8], 11, -2022574463)
- c = hh(c, d, a, b, k[11], 16, 1839030562)
- b = hh(b, c, d, a, k[14], 23, -35309556)
- a = hh(a, b, c, d, k[1], 4, -1530992060)
- d = hh(d, a, b, c, k[4], 11, 1272893353)
- c = hh(c, d, a, b, k[7], 16, -155497632)
- b = hh(b, c, d, a, k[10], 23, -1094730640)
- a = hh(a, b, c, d, k[13], 4, 681279174)
- d = hh(d, a, b, c, k[0], 11, -358537222)
- c = hh(c, d, a, b, k[3], 16, -722521979)
- b = hh(b, c, d, a, k[6], 23, 76029189)
- a = hh(a, b, c, d, k[9], 4, -640364487)
- d = hh(d, a, b, c, k[12], 11, -421815835)
- c = hh(c, d, a, b, k[15], 16, 530742520)
- b = hh(b, c, d, a, k[2], 23, -995338651)
-
- a = ii(a, b, c, d, k[0], 6, -198630844)
- d = ii(d, a, b, c, k[7], 10, 1126891415)
- c = ii(c, d, a, b, k[14], 15, -1416354905)
- b = ii(b, c, d, a, k[5], 21, -57434055)
- a = ii(a, b, c, d, k[12], 6, 1700485571)
- d = ii(d, a, b, c, k[3], 10, -1894986606)
- c = ii(c, d, a, b, k[10], 15, -1051523)
- b = ii(b, c, d, a, k[1], 21, -2054922799)
- a = ii(a, b, c, d, k[8], 6, 1873313359)
- d = ii(d, a, b, c, k[15], 10, -30611744)
- c = ii(c, d, a, b, k[6], 15, -1560198380)
- b = ii(b, c, d, a, k[13], 21, 1309151649)
- a = ii(a, b, c, d, k[4], 6, -145523070)
- d = ii(d, a, b, c, k[11], 10, -1120210379)
- c = ii(c, d, a, b, k[2], 15, 718787259)
- b = ii(b, c, d, a, k[9], 21, -343485551)
-
- x[0] = add32(a, x[0])
- x[1] = add32(b, x[1])
- x[2] = add32(c, x[2])
- x[3] = add32(d, x[3])
-}
-
-function cmn(q, a, b, x, s, t) {
- a = add32(add32(a, q), add32(x, t))
- return add32((a << s) | (a >>> (32 - s)), b)
-}
-
-function ff(a, b, c, d, x, s, t) {
- return cmn((b & c) | (~b & d), a, b, x, s, t)
-}
-
-function gg(a, b, c, d, x, s, t) {
- return cmn((b & d) | (c & ~d), a, b, x, s, t)
-}
-
-function hh(a, b, c, d, x, s, t) {
- return cmn(b ^ c ^ d, a, b, x, s, t)
-}
-
-function ii(a, b, c, d, x, s, t) {
- return cmn(c ^ (b | ~d), a, b, x, s, t)
-}
-
-function md51(s) {
- const n = s.length,
- state = [1732584193, -271733879, -1732584194, 271733878]
-
- let i
- for (i = 64; i <= s.length; i += 64) {
- md5cycle(state, md5blk(s.substring(i - 64, i)))
- }
- s = s.substring(i - 64)
- const tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
- for (i = 0; i < s.length; i++) tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3)
- tail[i >> 2] |= 0x80 << (i % 4 << 3)
- if (i > 55) {
- md5cycle(state, tail)
- for (i = 0; i < 16; i++) tail[i] = 0
- }
- tail[14] = n * 8
- md5cycle(state, tail)
- return state
-}
-
-function md5blk(s) {
- /* I figured global was faster. */
- const md5blks = []
- let i
- for (i = 0; i < 64; i += 4) {
- md5blks[i >> 2] =
- s.charCodeAt(i) +
- (s.charCodeAt(i + 1) << 8) +
- (s.charCodeAt(i + 2) << 16) +
- (s.charCodeAt(i + 3) << 24)
- }
- return md5blks
-}
-
-const HEX_CHR = '0123456789abcdef'.split('')
-
-function rhex(n) {
- let s = '',
- j = 0
- for (; j < 4; j++)
- s += HEX_CHR[(n >> (j * 8 + 4)) & 0x0f] + HEX_CHR[(n >> (j * 8)) & 0x0f]
- return s
-}
-
-function hex(x) {
- for (let i = 0; i < x.length; i++) x[i] = rhex(x[i])
- return x.join('')
-}
-
-let add32 = (a, b) => {
- return (a + b) & 0xffffffff
-}
-
-/**
- * Generate an MD5 hash for an input string
- * @param {string} s input string
- * @returns {string} md5 hash
- */
-function md5(s) {
- return hex(md51(s))
-}
-
-if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') {
- add32 = (x, y) => {
- const lsw = (x & 0xffff) + (y & 0xffff),
- msw = (x >> 16) + (y >> 16) + (lsw >> 16)
- return (msw << 16) | (lsw & 0xffff)
- }
-}
-
-export default md5
-export { md5 }
diff --git a/packages/frontend-2/lib/common/helpers/md5.ts b/packages/frontend-2/lib/common/helpers/md5.ts
new file mode 100644
index 000000000..10727f9a6
--- /dev/null
+++ b/packages/frontend-2/lib/common/helpers/md5.ts
@@ -0,0 +1,4 @@
+import { md5 } from '@speckle/shared'
+
+export default md5
+export { md5 }
diff --git a/packages/frontend-2/lib/common/helpers/resources.ts b/packages/frontend-2/lib/common/helpers/resources.ts
index eec12357c..a4c584b1f 100644
--- a/packages/frontend-2/lib/common/helpers/resources.ts
+++ b/packages/frontend-2/lib/common/helpers/resources.ts
@@ -1,6 +1,34 @@
+const streamRewriteRgx = /stream(?=$|s|\W)/gi
+const branchRewriteRgx = /branch(es)?(?=$|\W)/gi
+const commitRewriteRgx = /commit(?=$|s|\W)/gi
+
export function isObjectId(id: string) {
return id.length === 32
}
export const buildModelTreeItemId = (projectId: string, fullName: string) =>
`${projectId}-${fullName}`
+
+/**
+ * Converts old terminology (streams, branches, commits, etc.) to new one (projects, models, versions)
+ */
+export const toNewProductTerminology = (str: string): string => {
+ const isFirstCharUppercase = (val: string): boolean =>
+ val?.length ? val[0] === val[0].toUpperCase() : false
+
+ return str
+ .replaceAll(streamRewriteRgx, (match) =>
+ isFirstCharUppercase(match) ? 'Project' : 'project'
+ )
+ .replaceAll(branchRewriteRgx, (match) => {
+ const shouldBeUppercase = isFirstCharUppercase(match)
+ if (match === 'branches') {
+ return shouldBeUppercase ? 'Models' : 'models'
+ } else {
+ return shouldBeUppercase ? 'Model' : 'model'
+ }
+ })
+ .replaceAll(commitRewriteRgx, (match) =>
+ isFirstCharUppercase(match) ? 'Version' : 'version'
+ )
+}
diff --git a/packages/frontend-2/lib/common/helpers/route.ts b/packages/frontend-2/lib/common/helpers/route.ts
index 02bd3c80a..f6032dab2 100644
--- a/packages/frontend-2/lib/common/helpers/route.ts
+++ b/packages/frontend-2/lib/common/helpers/route.ts
@@ -35,11 +35,6 @@ export const automationDataPageRoute = (baseUrl: string, automationId: string) =
export const threadRedirectRoute = (projectId: string, threadId: string) =>
`/projects/${projectId}/threads/${threadId}`
-/**
- * TODO: Page doesn't exist
- */
-export const userProfileRoute = (userId: string) => `/profile/${userId}`
-
const buildNavigationComposable = (route: string) => () => {
const router = useRouter()
return (params?: { query?: LocationQueryRaw }) => {
diff --git a/packages/frontend-2/lib/core/composables/theme.ts b/packages/frontend-2/lib/core/composables/theme.ts
index b2b586ae6..f934ba8cb 100644
--- a/packages/frontend-2/lib/core/composables/theme.ts
+++ b/packages/frontend-2/lib/core/composables/theme.ts
@@ -15,10 +15,21 @@ export function useTheme() {
const isDarkTheme = computed(() => themeCookie.value === AppTheme.Dark)
const isLightTheme = computed(() => !isDarkTheme.value)
+ const setTheme = (newTheme: AppTheme) => {
+ themeCookie.value = newTheme
+ }
+
+ const toggleTheme = () => {
+ if (isDarkTheme.value) {
+ setTheme(AppTheme.Light)
+ } else {
+ setTheme(AppTheme.Dark)
+ }
+ }
+
return {
- setTheme: (newTheme: AppTheme) => {
- themeCookie.value = newTheme
- },
+ setTheme,
+ toggleTheme,
isDarkTheme,
isLightTheme
}
diff --git a/packages/frontend-2/lib/developer-settings/graphql/mutations.ts b/packages/frontend-2/lib/developer-settings/graphql/mutations.ts
index 3d1508abf..b8d109120 100644
--- a/packages/frontend-2/lib/developer-settings/graphql/mutations.ts
+++ b/packages/frontend-2/lib/developer-settings/graphql/mutations.ts
@@ -29,3 +29,9 @@ export const editApplicationMutation = graphql(`
appUpdate(app: $app)
}
`)
+
+export const revokeAppAccessMutation = graphql(`
+ mutation RevokeAppAccess($appId: String!) {
+ appRevokeAccess(appId: $appId)
+ }
+`)
diff --git a/packages/frontend-2/lib/developer-settings/graphql/queries.ts b/packages/frontend-2/lib/developer-settings/graphql/queries.ts
index bdb679a9e..7ea949e6f 100644
--- a/packages/frontend-2/lib/developer-settings/graphql/queries.ts
+++ b/packages/frontend-2/lib/developer-settings/graphql/queries.ts
@@ -34,3 +34,21 @@ export const developerSettingsApplicationsQuery = graphql(`
}
}
`)
+
+export const developerSettingsAuthorizedAppsQuery = graphql(`
+ query DeveloperSettingsAuthorizedApps {
+ activeUser {
+ id
+ authorizedApps {
+ id
+ description
+ name
+ author {
+ id
+ name
+ avatar
+ }
+ }
+ }
+ }
+`)
diff --git a/packages/frontend-2/lib/developer-settings/helpers/types.ts b/packages/frontend-2/lib/developer-settings/helpers/types.ts
index 648598179..11155e8d4 100644
--- a/packages/frontend-2/lib/developer-settings/helpers/types.ts
+++ b/packages/frontend-2/lib/developer-settings/helpers/types.ts
@@ -2,7 +2,8 @@ import { AllScopes } from '@speckle/shared'
import type { Get } from 'type-fest'
import type {
DeveloperSettingsAccessTokensQuery,
- DeveloperSettingsApplicationsQuery
+ DeveloperSettingsApplicationsQuery,
+ DeveloperSettingsAuthorizedAppsQuery
} from '~~/lib/common/generated/gql/graphql'
export type TokenItem = NonNullable<
@@ -18,6 +19,10 @@ export type ApplicationItem = NonNullable<
Get
>
+export type AuthorizedAppItem = NonNullable<
+ Get
+>
+
export type ApplicationFormValues = {
name: string
scopes: Array<{ id: (typeof AllScopes)[number]; text: string }>
diff --git a/packages/frontend-2/middleware/auth.ts b/packages/frontend-2/middleware/auth.ts
index d91f463a1..063b7d11c 100644
--- a/packages/frontend-2/middleware/auth.ts
+++ b/packages/frontend-2/middleware/auth.ts
@@ -10,7 +10,7 @@ import { loginRoute } from '~~/lib/common/helpers/route'
export default defineNuxtRouteMiddleware(async (to) => {
const nuxt = useNuxtApp()
const client = useApolloClientFromNuxt()
- const postAuthRedirect = usePostAuthRedirect()
+ const postAuthRedirect = usePostAuthRedirect({ route: to })
const { data } = await client
.query({
diff --git a/packages/frontend-2/pages/authn/verify/[appId]/[challenge]/index.vue b/packages/frontend-2/pages/authn/verify/[appId]/[challenge]/index.vue
index 5349c8b34..bfc03cf0b 100644
--- a/packages/frontend-2/pages/authn/verify/[appId]/[challenge]/index.vue
+++ b/packages/frontend-2/pages/authn/verify/[appId]/[challenge]/index.vue
@@ -1,101 +1,184 @@
+
-
-
-
-
-
-
-
{{ activeUser.name }}
-
logout()">
- Not you? Switch accounts
-
-
-
-
-
- {{ app?.name }}
-
- is requesting access to your Speckle account
-
-
-
-
+
+
+
+ Authorize Application
+
+
+
+
+
+
{{ activeUser.name }}
+
+
-
- App info & requested permissions ({{ app.scopes.length }})
-
-
+
+
+
+
-
-
-
- Author:
- {{ app.author.name }}
-
-
-
- Description:
- {{ app.description }}
-
-
- Permissions:
-
- {{ scope.name }}
- {{ scope.description }}
-
-
-
+ {{ app?.name }}
+
+ wants to access your Speckle account.
+
+
+
+
+
+
+
+ App info & Requested permissions ({{ app.scopes.length }})
+
+
+
+
+
+
+
+
+
+ | Author: |
+
+
+ {{ app.author.name }}
+ |
+
+
+ | Description: |
+
+ {{ app.description }}
+ |
+
+
+
+
+
Permissions:
+
+
+
+ -
+ {{ group }}
+
+
+
+
+
+
+
+
+
+
+ Deny
+
+
+ Authorize
+
+
+
+
+
+
+
+
+ {{ action === ChosenAction.Allow ? 'Success' : 'Denied' }}
+
+ Error
+
+
+
+
+
+ {{ app?.name }}
+ is connected to your
+ Speckle
+ account.
+
+
+ {{ app?.name }}
+ has not been connected to your
+ Speckle
+ account.
+
+
+
+ Could not resolve app.
+ Go Home
+
+
+
+ You will be redirected automatically, please wait a moment.
+
-
-
- Deny
-
- Allow
-
-
- Clicking 'Allow' will redirect you to {{ app.redirectUrl }}
-
-
-
-
- Permission granted.
- Permission denied.
-
-
- You will be redirected automatically
-
-
-
- Could not resolve app.
- Go Home
+
diff --git a/packages/frontend-2/pages/developer-settings.vue b/packages/frontend-2/pages/developer-settings.vue
index a75aabc02..33bc7e65c 100644
--- a/packages/frontend-2/pages/developer-settings.vue
+++ b/packages/frontend-2/pages/developer-settings.vue
@@ -26,6 +26,7 @@
@@ -104,6 +105,7 @@
@@ -184,6 +186,64 @@
+
+
+
+ Here you can review the apps that you have granted access to. If something
+ looks suspicious, revoke the access.
+
+
+
+ {{ item.name }}
+
+
+
+
+
+ {{ item.author.name }}
+
+
+
+ Speckle
+
+
+
+
+ {{ item.description }}
+
+
+
(null)
+const itemToModify = ref(null)
const tokenSuccess = ref('')
const showCreateTokenDialog = ref(false)
const showCreateTokenSuccessDialog = ref(false)
@@ -262,7 +330,13 @@ const applications = computed(() => {
return applicationsResult.value?.activeUser?.createdApps || []
})
-const openDeleteDialog = (item: TokenItem | ApplicationItem) => {
+const authorizedApps = computed(() =>
+ (authorizedAppsResult.value?.activeUser?.authorizedApps || []).filter(
+ (app) => app.id !== 'spklwebapp'
+ )
+)
+
+const openDeleteDialog = (item: TokenItem | ApplicationItem | AuthorizedAppItem) => {
itemToModify.value = item
showDeleteDialog.value = true
}
diff --git a/packages/frontend-2/utils/globals.ts b/packages/frontend-2/utils/globals.ts
new file mode 100644
index 000000000..7959be854
--- /dev/null
+++ b/packages/frontend-2/utils/globals.ts
@@ -0,0 +1,3 @@
+import { ToastNotificationType } from '~~/lib/common/composables/toast'
+
+export { ToastNotificationType }
diff --git a/packages/server/assets/auth/typedefs/apps.graphql b/packages/server/assets/auth/typedefs/apps.graphql
index fd421930f..05258a8b7 100644
--- a/packages/server/assets/auth/typedefs/apps.graphql
+++ b/packages/server/assets/auth/typedefs/apps.graphql
@@ -46,7 +46,7 @@ extend type User {
"""
Returns the apps you have authorized.
"""
- authorizedApps: [ServerAppListItem]
+ authorizedApps: [ServerAppListItem!]
@hasServerRole(role: SERVER_GUEST)
@hasScope(scope: "apps:read")
diff --git a/packages/server/modules/auth/rest/index.js b/packages/server/modules/auth/rest/index.js
index bacfbc35b..f8759fbcc 100644
--- a/packages/server/modules/auth/rest/index.js
+++ b/packages/server/modules/auth/rest/index.js
@@ -24,6 +24,7 @@ module.exports = (app) => {
*/
app.get('/auth/accesscode', async (req, res) => {
try {
+ const preventRedirect = !!req.query.preventRedirect
const appId = req.query.appId
const app = await getApp({ id: appId })
@@ -42,7 +43,11 @@ module.exports = (app) => {
await validateScopes(scopes, Scopes.Tokens.Write)
const ac = await createAuthorizationCode({ appId, userId, challenge })
- return res.redirect(`${app.redirectUrl}?access_code=${ac}`)
+
+ const redirectUrl = `${app.redirectUrl}?access_code=${ac}`
+ return preventRedirect
+ ? res.status(200).json({ redirectUrl })
+ : res.redirect(redirectUrl)
} catch (err) {
if (
err instanceof InvalidAccessCodeRequestError ||
diff --git a/packages/server/modules/auth/scopes.js b/packages/server/modules/auth/scopes.js
index 0131b37de..680540e0f 100644
--- a/packages/server/modules/auth/scopes.js
+++ b/packages/server/modules/auth/scopes.js
@@ -5,12 +5,12 @@ const { Scopes } = require('@speckle/shared')
module.exports = [
{
name: Scopes.Apps.Read,
- description: 'See what applications you have created or have authorized.',
+ description: 'See created or authorized applications.',
public: false
},
{
name: Scopes.Apps.Write,
- description: 'Register applications on your behalf.',
+ description: 'Register new applications.',
public: false
}
]
diff --git a/packages/server/modules/automations/index.ts b/packages/server/modules/automations/index.ts
index f6ecb7687..f0b21eba5 100644
--- a/packages/server/modules/automations/index.ts
+++ b/packages/server/modules/automations/index.ts
@@ -8,7 +8,7 @@ async function initScopes() {
const scopes: ScopeRecord[] = [
{
name: Scopes.Automate.ReportResults,
- description: 'Allows the app to report automation results to the server.',
+ description: 'Report automation results to the server.',
public: true
}
]
diff --git a/packages/server/modules/core/graph/generated/graphql.ts b/packages/server/modules/core/graph/generated/graphql.ts
index 5fdcbb1fd..f0b116196 100644
--- a/packages/server/modules/core/graph/generated/graphql.ts
+++ b/packages/server/modules/core/graph/generated/graphql.ts
@@ -2616,7 +2616,7 @@ export type User = {
/** Returns a list of your personal api tokens. */
apiTokens: Array;
/** Returns the apps you have authorized. */
- authorizedApps?: Maybe>>;
+ authorizedApps?: Maybe>;
avatar?: Maybe;
bio?: Maybe;
/**
@@ -4242,7 +4242,7 @@ export type TokenResourceIdentifierResolvers = {
activity?: Resolver, ParentType, ContextType, RequireFields>;
apiTokens?: Resolver, ParentType, ContextType>;
- authorizedApps?: Resolver>>, ParentType, ContextType>;
+ authorizedApps?: Resolver>, ParentType, ContextType>;
avatar?: Resolver, ParentType, ContextType>;
bio?: Resolver, ParentType, ContextType>;
commits?: Resolver, ParentType, ContextType, RequireFields>;
diff --git a/packages/server/modules/core/scopes.js b/packages/server/modules/core/scopes.js
index f9d5e9a81..df421ca20 100644
--- a/packages/server/modules/core/scopes.js
+++ b/packages/server/modules/core/scopes.js
@@ -16,49 +16,49 @@ module.exports = [
},
{
name: Scopes.Profile.Read,
- description: 'Read your profile information (name, bio, company).',
+ description: 'Read your profile information.',
public: true
},
{
name: Scopes.Profile.Email,
- description: 'Grants access to the email address you registered with.',
+ description: 'Read the email address you registered with.',
public: true
},
{
name: Scopes.Profile.Delete,
- description: 'Allows a user to delete their account, with all associated data.',
+ description: 'Delete the account with all associated data.',
public: false
},
{
name: Scopes.Users.Read,
- description: "Read other users' profile on your behalf.",
+ description: "Read other users' profiles.",
public: true
},
{
name: Scopes.Server.Stats,
description:
- 'Request server stats from the api. Only works in conjunction with a "server:admin" role.',
+ 'Request server stats from the API. Only works in conjunction with a "server:admin" role.',
public: true
},
{
name: Scopes.Users.Email,
- description: 'Access the emails of other users on your behalf.',
+ description: 'Access the emails of other users.',
public: false
},
{
name: Scopes.Server.Setup,
description:
- 'Edit server information. Note: only server admins will be able to use this token.',
+ 'Edit server information. Note: Only server admins will be able to use this token.',
public: false
},
{
name: Scopes.Tokens.Read,
- description: 'Access your api tokens.',
+ description: 'Access API tokens.',
public: false
},
{
name: Scopes.Tokens.Write,
- description: 'Create and delete api tokens on your behalf.',
+ description: 'Create and delete API tokens.',
public: false
}
]
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 1aae6aabc..1ce1540a4 100644
--- a/packages/server/modules/cross-server-sync/graph/generated/graphql.ts
+++ b/packages/server/modules/cross-server-sync/graph/generated/graphql.ts
@@ -2606,7 +2606,7 @@ export type User = {
/** Returns a list of your personal api tokens. */
apiTokens: Array;
/** Returns the apps you have authorized. */
- authorizedApps?: Maybe>>;
+ authorizedApps?: Maybe>;
avatar?: Maybe;
bio?: Maybe;
/**
diff --git a/packages/server/test/graphql/generated/graphql.ts b/packages/server/test/graphql/generated/graphql.ts
index 80348ee08..988ebba58 100644
--- a/packages/server/test/graphql/generated/graphql.ts
+++ b/packages/server/test/graphql/generated/graphql.ts
@@ -2607,7 +2607,7 @@ export type User = {
/** Returns a list of your personal api tokens. */
apiTokens: Array;
/** Returns the apps you have authorized. */
- authorizedApps?: Maybe>>;
+ authorizedApps?: Maybe>;
avatar?: Maybe;
bio?: Maybe;
/**
diff --git a/packages/ui-components/postcss.config.js b/packages/ui-components/postcss.config.js
index a9b0b9302..3fe6e863a 100644
--- a/packages/ui-components/postcss.config.js
+++ b/packages/ui-components/postcss.config.js
@@ -1,7 +1,7 @@
export default {
plugins: {
+ 'tailwindcss/nesting': {},
tailwindcss: {},
- autoprefixer: {},
- 'postcss-nesting': {}
+ autoprefixer: {}
}
}
diff --git a/packages/ui-components/src/assets/setup/mentions.css b/packages/ui-components/src/assets/setup/mentions.css
new file mode 100644
index 000000000..d20f05c33
--- /dev/null
+++ b/packages/ui-components/src/assets/setup/mentions.css
@@ -0,0 +1,15 @@
+/**
+ * Tippy mentions theme
+ */
+.tippy-box[data-theme='mention'] {
+ background: none;
+ pointer-events: none;
+
+ .tippy-arrow {
+ display: none;
+ }
+
+ .tippy-content > * {
+ pointer-events: auto;
+ }
+}
diff --git a/packages/ui-components/src/components/common/text/Link.vue b/packages/ui-components/src/components/common/text/Link.vue
index 5de7e9ab7..1782c7f9a 100644
--- a/packages/ui-components/src/components/common/text/Link.vue
+++ b/packages/ui-components/src/components/common/text/Link.vue
@@ -1,6 +1,7 @@
,
default: 'default'
},
+ /**
+ * Text color, only used when color=secondary
+ */
+ textColor: {
+ type: String as PropType,
+ default: 'default'
+ },
/**
* Whether the target location should be forcefully treated as an external URL
* (for relative paths this will likely cause a redirect)
@@ -253,12 +252,32 @@ const bgAndBorderClasses = computed(() => {
const foregroundClasses = computed(() => {
const classParts: string[] = []
+ const hasCustomTextColor = props.textColor !== 'default'
+
+ if (hasCustomTextColor && !isDisabled.value) {
+ switch (props.textColor) {
+ case 'primary':
+ classParts.push('text-primary')
+ break
+ case 'warning':
+ classParts.push('text-warning')
+ break
+ case 'success':
+ classParts.push('text-success')
+ break
+ case 'danger':
+ classParts.push('text-danger')
+ break
+ case 'info':
+ classParts.push('text-info')
+ break
+ }
+ }
+
if (!props.text && !props.link) {
if (isDisabled.value) {
- classParts.push(
- props.outlined ? 'text-foreground-disabled' : 'text-foreground-disabled'
- )
- } else {
+ classParts.push('text-foreground-disabled')
+ } else if (!hasCustomTextColor) {
switch (props.color) {
case 'invert':
classParts.push(
@@ -308,7 +327,7 @@ const foregroundClasses = computed(() => {
} else {
if (isDisabled.value) {
classParts.push('text-foreground-disabled')
- } else {
+ } else if (!hasCustomTextColor) {
if (props.color === 'invert') {
classParts.push(
'text-foundation hover:text-foundation-2 dark:text-foreground dark:hover:text-foreground'
diff --git a/packages/ui-components/src/components/global/ToastRenderer.stories.ts b/packages/ui-components/src/components/global/ToastRenderer.stories.ts
index b057a9584..93a85c095 100644
--- a/packages/ui-components/src/components/global/ToastRenderer.stories.ts
+++ b/packages/ui-components/src/components/global/ToastRenderer.stories.ts
@@ -5,6 +5,9 @@ import ToastRenderer from '~~/src/components/global/ToastRenderer.vue'
import FormButton from '~~/src/components/form/Button.vue'
import { ToastNotificationType } from '~~/src/helpers/global/toast'
import type { ToastNotification } from '~~/src/helpers/global/toast'
+import { useGlobalToast } from '~~/src/stories/composables/toast'
+
+type StoryType = StoryObj<{ notification: ToastNotification }>
export default {
component: ToastRenderer,
@@ -27,31 +30,20 @@ export default {
}
} as Meta
-export const Default: StoryObj = {
+export const Default: StoryType = {
render: (args) => ({
components: { ToastRenderer, FormButton },
setup() {
+ const { triggerNotification } = useGlobalToast()
const notification = ref(null as Nullable)
const onClick = () => {
- notification.value = {
- type: ToastNotificationType.Info,
- title: 'Title',
- description: 'Description',
- cta: {
- title: 'CTA',
- onClick: () => console.log('Clicked')
- }
- }
-
- // Clear after 2s
- setTimeout(() => (notification.value = null), 2000)
+ triggerNotification(args.notification)
}
return { args, onClick, notification }
},
template: `
Trigger!
-
`
}),
@@ -61,5 +53,26 @@ export const Default: StoryObj = {
code: ''
}
}
+ },
+ args: {
+ notification: {
+ type: ToastNotificationType.Info,
+ title: 'Title',
+ description: 'Description',
+ cta: {
+ title: 'CTA',
+ onClick: () => console.log('Clicked')
+ }
+ }
+ }
+}
+
+export const WithManualClose: StoryType = {
+ ...Default,
+ args: {
+ notification: {
+ ...Default.args!.notification!,
+ autoClose: false
+ }
}
}
diff --git a/packages/ui-components/src/components/layout/Table.vue b/packages/ui-components/src/components/layout/Table.vue
index a9ac7a095..925a765ab 100644
--- a/packages/ui-components/src/components/layout/Table.vue
+++ b/packages/ui-components/src/components/layout/Table.vue
@@ -22,9 +22,8 @@
-