diff --git a/packages/frontend-2/.env.example b/packages/frontend-2/.env.example index b2ff12ddf..6caf83c7b 100644 --- a/packages/frontend-2/.env.example +++ b/packages/frontend-2/.env.example @@ -37,8 +37,5 @@ NUXT_PUBLIC_ENABLE_AUTOMATE_MODULE=false # Survicate NUXT_PUBLIC_SURVICATE_WORKSPACE_KEY= -# Enable direct preview image loading - way quicker, but requres server & frontend to be on the same origin -NUXT_PUBLIC_ENABLE_DIRECT_PREVIEWS=true - # Ghost API NUXT_PUBLIC_GHOST_API_KEY= \ No newline at end of file diff --git a/packages/frontend-2/components/common/TransitioningContents.vue b/packages/frontend-2/components/common/TransitioningContents.vue index 9c6f2f8c3..614d26772 100644 --- a/packages/frontend-2/components/common/TransitioningContents.vue +++ b/packages/frontend-2/components/common/TransitioningContents.vue @@ -3,7 +3,8 @@ import { waitIntervalUntil, type Nullable, timeoutAt, - WaitIntervalUntilCanceledError + WaitIntervalUntilCanceledError, + TimeoutError } from '@speckle/shared' import { until } from '@vueuse/core' import type { CSSProperties } from 'vue' @@ -26,6 +27,8 @@ export default defineComponent({ } }, setup(props, { slots, expose }) { + const logger = useLogger() + const transitioning = ref(false) const newWrapperRef = ref(null as Nullable) const oldWrapperRef = ref(null as Nullable) @@ -102,6 +105,8 @@ export default defineComponent({ * Cause default slot to update with an opacity transition */ const updateContents = async () => { + transitioning.value = true + // Stage 1: Just move new -> old w/o any transitions (visually should look the same) oldContents.value = newContents.value newContents.value = slots.default?.() @@ -155,15 +160,23 @@ export default defineComponent({ const triggerTransition = async () => { if (!transitioning.value) { - transitioning.value = true await updateContents() return } - await Promise.race([ - until(transitioning).toBe(false), - timeoutAt(props.duration + 1000) - ]) + try { + await Promise.race([ + until(transitioning).toBe(false), + timeoutAt(props.duration + 1000, 'Waiting for transition to finish timed out') + ]) + } catch (e) { + if (!(e instanceof TimeoutError)) { + throw e + } else { + logger.warn(e) + } + } + await updateContents() } diff --git a/packages/frontend-2/lib/core/errors/base.ts b/packages/frontend-2/lib/core/errors/base.ts index 4c37165ec..443708e01 100644 --- a/packages/frontend-2/lib/core/errors/base.ts +++ b/packages/frontend-2/lib/core/errors/base.ts @@ -1,3 +1,5 @@ +import { BaseError } from '@speckle/ui-components' + export { BaseError, LogicError, @@ -5,3 +7,7 @@ export { ComposableInvokedOutOfScopeError, UnsupportedEnvironmentError } from '@speckle/ui-components' + +export class ResourceLoadError extends BaseError { + static defaultMessage = 'External resource failed to load' +} diff --git a/packages/frontend-2/lib/core/helpers/observability.ts b/packages/frontend-2/lib/core/helpers/observability.ts index e6c993505..be9a4cd04 100644 --- a/packages/frontend-2/lib/core/helpers/observability.ts +++ b/packages/frontend-2/lib/core/helpers/observability.ts @@ -13,6 +13,7 @@ import { noop } from 'lodash-es' import type { Logger, Level } from 'pino' +import { ResourceLoadError } from '~/lib/core/errors/base' /** * Add pino-pretty like formatting @@ -152,6 +153,24 @@ export function enableCustomLoggerHandling(params: { return (...args: unknown[]) => { const log = logMethod.bind(target) + // Format passed in data, if needed + args = args + .map((arg) => { + // Convert error events to error type + if (arg instanceof Event && arg.type === 'error') { + return new ResourceLoadError() + } + + return arg + }) + .filter((arg) => { + // Filter out falsy values + return !!arg + }) + + // If nothing valid to log, skip entirely + if (args.length === 0) return + const level = prop as Level const firstError = args.find((arg): arg is Error => arg instanceof Error) diff --git a/packages/frontend-2/lib/projects/composables/previewImage.ts b/packages/frontend-2/lib/projects/composables/previewImage.ts index b4d000653..f3d65a087 100644 --- a/packages/frontend-2/lib/projects/composables/previewImage.ts +++ b/packages/frontend-2/lib/projects/composables/previewImage.ts @@ -1,6 +1,5 @@ import type { MaybeRef } from '@vueuse/core' import type { MaybeNullOrUndefined, Nullable } from '@speckle/shared' -import { useAuthCookie } from '~~/lib/auth/composables/auth' import { onProjectVersionsPreviewGeneratedSubscription } from '~~/lib/projects/graphql/subscriptions' import { useSubscription } from '@vue/apollo-composable' import { useLock } from '~~/lib/common/composables/singleton' @@ -26,11 +25,7 @@ export function usePreviewImageBlob( }> ) { const { enabled = ref(true) } = options || {} - const authToken = useAuthCookie() const logger = useLogger() - const { - public: { enableDirectPreviews } - } = useRuntimeConfig() const url = ref(PreviewPlaceholder as Nullable) const hasDoneFirstLoad = ref(false) @@ -49,21 +44,15 @@ export function usePreviewImageBlob( hasDoneFirstLoad: computed(() => hasDoneFirstLoad.value) } - if (enableDirectPreviews) { - const directPreviewUrl = unref(previewUrl) - // const directPanoramicUrl = basePanoramaUrl.value - - useHead({ - link: [ - ...(directPreviewUrl?.length - ? [{ rel: 'preload', as: 'image', href: directPreviewUrl }] - : []) - // ...(directPanoramicUrl?.length - // ? [{ rel: 'prefetch', as: 'image', href: directPanoramicUrl }] - // : []) - ] - }) - } + // Preload the image + const directPreviewUrl = unref(previewUrl) + useHead({ + link: [ + ...(directPreviewUrl?.length + ? [{ rel: 'preload', as: 'image', href: directPreviewUrl }] + : []) + ] + }) if (import.meta.server) return ret @@ -133,23 +122,9 @@ export function usePreviewImageBlob( return } - let blobUrl: string - if (enableDirectPreviews || import.meta.server) { - const blobUrlConfig = new URL(basePreviewUrl) - blobUrlConfig.searchParams.set('v', cacheBust.value.toString()) - blobUrl = blobUrlConfig.toString() - } else { - const res = await fetch(basePreviewUrl, { - headers: authToken.value ? { Authorization: `Bearer ${authToken.value}` } : {} - }) - - if (res.headers.has('X-Preview-Error')) { - throw new Error('Failed getting preview') - } - - const blob = await res.blob() - blobUrl = URL.createObjectURL(blob) - } + const blobUrlConfig = new URL(basePreviewUrl) + blobUrlConfig.searchParams.set('v', cacheBust.value.toString()) + const blobUrl = blobUrlConfig.toString() // Load img in browser first, before we set the url if (import.meta.client) { @@ -181,30 +156,9 @@ export function usePreviewImageBlob( return } - let blobUrl: string - if (enableDirectPreviews || import.meta.server) { - const blobUrlConfig = new URL(basePanoramaUrl.value) - blobUrlConfig.searchParams.set('v', cacheBust.value.toString()) - blobUrl = blobUrlConfig.toString() - } else { - const res = await fetch(basePanoramaUrl.value, { - headers: authToken.value ? { Authorization: `Bearer ${authToken.value}` } : {} - }) - - const errCode = res.headers.get('X-Preview-Error-Code') - if (errCode?.length) { - if (errCode === 'ANGLE_NOT_FOUND') { - throw new AngleNotFoundError() - } - } - - if (res.headers.has('X-Preview-Error')) { - throw new Error('Failed getting preview') - } - - const blob = await res.blob() - blobUrl = URL.createObjectURL(blob) - } + const blobUrlConfig = new URL(basePanoramaUrl.value) + blobUrlConfig.searchParams.set('v', cacheBust.value.toString()) + const blobUrl = blobUrlConfig.toString() // Load img in browser first, before we set the url if (import.meta.client) { diff --git a/packages/frontend-2/nuxt.config.ts b/packages/frontend-2/nuxt.config.ts index 2b633c1e9..9b2203f46 100644 --- a/packages/frontend-2/nuxt.config.ts +++ b/packages/frontend-2/nuxt.config.ts @@ -70,7 +70,6 @@ export default defineNuxtConfig({ datadogSite: '', datadogService: '', datadogEnv: '', - enableDirectPreviews: true, ghostApiKey: '' } },