diff --git a/e2e/playwright/regression-tests.spec.ts b/e2e/playwright/regression-tests.spec.ts index c18f8663c..4d145cf47 100644 --- a/e2e/playwright/regression-tests.spec.ts +++ b/e2e/playwright/regression-tests.spec.ts @@ -575,7 +575,7 @@ extrude002 = extrude(profile002, length = 150) name: 'Projects', }) const projectLink = page.getByRole('link', { name: 'bracket' }) - const networkHealthIndicator = page.getByTestId('network-toggle') + const networkHealthIndicator = page.getByTestId(/network-toggle/) await test.step('Check the home page', async () => { await expect(projectsHeading).toBeVisible() diff --git a/e2e/playwright/snapshot-tests.spec.ts b/e2e/playwright/snapshot-tests.spec.ts index a8fc23eed..5d59d9caa 100644 --- a/e2e/playwright/snapshot-tests.spec.ts +++ b/e2e/playwright/snapshot-tests.spec.ts @@ -798,7 +798,7 @@ test('theme persists', async ({ page, context, homePage }) => { await page.getByTestId('settings-close-button').click() - const networkToggle = page.getByTestId('network-toggle') + const networkToggle = page.getByTestId(/network-toggle/) // simulate network down await u.emulateNetworkConditions({ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-1-Google-Chrome-linux.png index 8169515bc..72f41e2f7 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-linux.png index a6b36345b..6c6e7410b 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Inch-scale-2-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-1-Google-Chrome-linux.png index 2813e518e..1f6d90460 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png index 68c4e63d1..080607eec 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Client-side-scene-scale-should-match-engine-scale-Millimeter-scale-2-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png index 7d58ac96f..9a058eae7 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-circle-should-look-right-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-rectangles-should-look-right-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-rectangles-should-look-right-1-Google-Chrome-linux.png index 3d87b4c6b..665d1c763 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-rectangles-should-look-right-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-rectangles-should-look-right-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-1-Google-Chrome-linux.png index f78ac1c7c..d5d507f97 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-2-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-2-Google-Chrome-linux.png index e7c016cf6..f7466592e 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-2-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-2-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-3-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-3-Google-Chrome-linux.png index ede3e9b1f..bfbdf0fb1 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-3-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-3-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-4-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-4-Google-Chrome-linux.png index 378c19541..33d23582c 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-4-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Draft-segments-should-look-right-4-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-1-Google-Chrome-linux.png index cd1316369..6312c56dc 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-to-on-via-command-bar-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-to-on-via-command-bar-1-Google-Chrome-linux.png index 06ed38fea..2f04122de 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-to-on-via-command-bar-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-to-on-via-command-bar-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-on-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-on-1-Google-Chrome-linux.png index e98e4a7da..56510189e 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-on-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-on-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Sketch-on-face-with-none-z-up-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Sketch-on-face-with-none-z-up-1-Google-Chrome-linux.png index b26067537..1ca2087b8 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Sketch-on-face-with-none-z-up-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Sketch-on-face-with-none-z-up-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-2d-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-2d-1-Google-Chrome-linux.png index 1d4be10ef..56816d37c 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-2d-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-2d-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-3d-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-3d-1-Google-Chrome-linux.png index 7ca949dc9..4406218ad 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-3d-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Zoom-to-fit-on-load---solid-3d-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-1-Google-Chrome-linux.png index b7defec7f..1a6b72f11 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-opening-window-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-opening-window-1-Google-Chrome-linux.png index b639a02ef..ae4371537 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-opening-window-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-opening-window-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-works-with-single-quotes-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-works-with-single-quotes-1-Google-Chrome-linux.png index 8a6d089c7..b585ffbd7 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-works-with-single-quotes-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-works-with-single-quotes-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XY-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XY-1-Google-Chrome-linux.png index 8329b155e..879865095 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XY-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XY-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XZ-1-Google-Chrome-linux.png index 70fba85c9..a5e0f6b23 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XZ-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XZ-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--YZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--YZ-1-Google-Chrome-linux.png index 15d5bfdf6..8bc75df66 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--YZ-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--YZ-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-linux.png index 2ce82555f..a4c7b6aa0 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XY-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png index e517714f0..ca8c66362 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png index bed90c647..cbc3ec80d 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/test-network-and-connection-issues.spec.ts b/e2e/playwright/test-network-and-connection-issues.spec.ts index e14886289..8bf2b3e2f 100644 --- a/e2e/playwright/test-network-and-connection-issues.spec.ts +++ b/e2e/playwright/test-network-and-connection-issues.spec.ts @@ -24,16 +24,15 @@ test.describe('Test network related behaviors', () => { await homePage.goToModelingScene() - const networkToggle = page.getByTestId('network-toggle') + const networkToggle = page.getByTestId(/network-toggle/) // This is how we wait until the stream is online await expect( page.getByRole('button', { name: 'Start Sketch' }) ).not.toBeDisabled({ timeout: 15000 }) - const networkWidget = page.locator('[data-testid="network-toggle"]') - await expect(networkWidget).toBeVisible() - await networkWidget.hover() + await expect(networkToggle).toBeVisible() + await networkToggle.hover() const networkPopover = page.locator('[data-testid="network-popover"]') await expect(networkPopover).not.toBeVisible() @@ -44,7 +43,7 @@ test.describe('Test network related behaviors', () => { ).toBeVisible() // Click the network widget - await networkWidget.click() + await networkToggle.click() // Check the modal opened. await expect(networkPopover).toBeVisible() @@ -65,8 +64,8 @@ test.describe('Test network related behaviors', () => { // Expect the network to be down await expect(networkToggle).toContainText('Network health (Offline)') - // Click the network widget - await networkWidget.click() + // Click the network toggle + await networkToggle.click() // Check the modal opened. await expect(networkPopover).toBeVisible() @@ -99,7 +98,7 @@ test.describe('Test network related behaviors', () => { 'Engine disconnect & reconnect in sketch mode', { tag: '@skipLocalEngine' }, async ({ page, homePage, toolbar, scene, cmdBar }) => { - const networkToggle = page.getByTestId('network-toggle') + const networkToggle = page.getByTestId(/network-toggle/) const networkToggleConnectedText = page.getByText( 'Network health (Strong)' ) @@ -286,7 +285,7 @@ profile001 = startProfile(sketch001, at = [12.34, -12.34]) 'Paused stream freezes view frame, unpause reconnect is seamless to user', { tag: ['@desktop', '@skipLocalEngine'] }, async ({ page, homePage, scene, cmdBar, toolbar, tronApp }) => { - const networkToggle = page.getByTestId('network-toggle') + const networkToggle = page.getByTestId(/network-toggle/) const networkToggleConnectedText = page.getByText( 'Network health (Strong)' ) diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index 7b83ea0b1..588706882 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -37,7 +37,7 @@ export const headerMasks = (page: Page) => [ ] export const lowerRightMasks = (page: Page) => [ - page.getByTestId('network-toggle'), + page.getByTestId(/network-toggle/), page.getByTestId('billing-remaining-bar'), ] diff --git a/e2e/playwright/testing-gizmo.spec.ts b/e2e/playwright/testing-gizmo.spec.ts index be26eb252..b87ffeb05 100644 --- a/e2e/playwright/testing-gizmo.spec.ts +++ b/e2e/playwright/testing-gizmo.spec.ts @@ -8,37 +8,37 @@ test.describe('Testing Gizmo', () => { const cases = [ { testDescription: 'top view', - clickPosition: { x: 951, y: 385 }, + clickPosition: { x: 951, y: 402 }, expectedCameraPosition: { x: 800, y: -152, z: 4886.02 }, expectedCameraTarget: { x: 800, y: -152, z: 26 }, }, { testDescription: 'bottom view', - clickPosition: { x: 951, y: 429 }, + clickPosition: { x: 951, y: 449 }, expectedCameraPosition: { x: 800, y: -152, z: -4834.02 }, expectedCameraTarget: { x: 800, y: -152, z: 26 }, }, { testDescription: 'right view', - clickPosition: { x: 929, y: 417 }, + clickPosition: { x: 929, y: 435 }, expectedCameraPosition: { x: 5660.02, y: -152, z: 26 }, expectedCameraTarget: { x: 800, y: -152, z: 26 }, }, { testDescription: 'left view', - clickPosition: { x: 974, y: 397 }, + clickPosition: { x: 974, y: 417 }, expectedCameraPosition: { x: -4060.02, y: -152, z: 26 }, expectedCameraTarget: { x: 800, y: -152, z: 26 }, }, { testDescription: 'back view', - clickPosition: { x: 967, y: 421 }, + clickPosition: { x: 967, y: 441 }, expectedCameraPosition: { x: 800, y: 4708.02, z: 26 }, expectedCameraTarget: { x: 800, y: -152, z: 26 }, }, { testDescription: 'front view', - clickPosition: { x: 935, y: 393 }, + clickPosition: { x: 935, y: 413 }, expectedCameraPosition: { x: 800, y: -5012.02, z: 26 }, expectedCameraTarget: { x: 800, y: -152, z: 26 }, }, diff --git a/src/App.tsx b/src/App.tsx index 710cd213a..3b7d9eae5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react' +import { useEffect, useState } from 'react' import toast from 'react-hot-toast' import { useHotkeys } from 'react-hotkeys-hook' import ModalContainer from 'react-modal-promise' @@ -12,7 +12,6 @@ import { import { AppHeader } from '@src/components/AppHeader' import { EngineStream } from '@src/components/EngineStream' import Gizmo from '@src/components/Gizmo' -import { LowerRightControls } from '@src/components/LowerRightControls' import { useLspContext } from '@src/components/LspProvider' import { ModelingSidebar } from '@src/components/ModelingSidebar/ModelingSidebar' import { UnitsMenu } from '@src/components/UnitsMenu' @@ -29,6 +28,7 @@ import { codeManager, kclManager, settingsActor, + getSettings, } from '@src/lib/singletons' import { maybeWriteToDisk } from '@src/lib/telemetry' import type { IndexLoaderData } from '@src/lib/types' @@ -53,6 +53,17 @@ import { } from '@src/lib/constants' import { isPlaywright } from '@src/lib/isPlaywright' import { VITE_KC_SITE_BASE_URL } from '@src/env' +import { useNetworkHealthStatus } from '@src/components/NetworkHealthIndicator' +import { useNetworkMachineStatus } from '@src/components/NetworkMachineIndicator' +import { + defaultLocalStatusBarItems, + defaultGlobalStatusBarItems, +} from '@src/components/StatusBar/defaultStatusBarItems' +import { StatusBar } from '@src/components/StatusBar/StatusBar' +import { useModelingContext } from '@src/hooks/useModelingContext' +import { xStateValueToString } from '@src/lib/xStateValueToString' +import { getSelectionTypeDisplayText } from '@src/lib/selections' +import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes' // CYCLIC REF sceneInfra.camControls.engineStreamActor = engineStreamActor @@ -62,6 +73,7 @@ maybeWriteToDisk() .catch(() => {}) export function App() { + const { state: modelingState } = useModelingContext() useQueryParamEffects() const { project, file } = useLoaderData() as IndexLoaderData const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false) @@ -70,9 +82,10 @@ export function App() { const navigate = useNavigate() const filePath = useAbsoluteFilePath() const { onProjectOpen } = useLspContext() + const networkHealthStatus = useNetworkHealthStatus() + const networkMachineStatus = useNetworkMachineStatus() // We need the ref for the outermost div so we can screenshot the app for // the coredump. - const ref = useRef(null) // Stream related refs and data const [searchParams] = useSearchParams() @@ -220,24 +233,62 @@ export function App() { }, []) return ( -
- - - - - - - - {/* */} - - - - +
+
+ + + + + + + + {/* */} +
+ + +
+
+
) } diff --git a/src/components/BillingRemaining.tsx b/src/components/BillingRemaining.tsx index 004f5d478..c58e8dfec 100644 --- a/src/components/BillingRemaining.tsx +++ b/src/components/BillingRemaining.tsx @@ -89,20 +89,19 @@ export const BillingRemaining = (props: BillingRemainingProps) => { const isFlex = props.mode === BillingRemainingMode.ProgressBarStretch const cssWrapper = [ 'bg-ml-green', + 'dark:bg-transparent', 'select-none', 'cursor-pointer', - 'py-1', - 'rounded', '!no-underline', 'text-xs', '!text-chalkboard-100', - 'dark:!text-chalkboard-0', + 'dark:!text-ml-green', ] return (
diff --git a/src/components/CommandBar/CommandBarSelectionInput.tsx b/src/components/CommandBar/CommandBarSelectionInput.tsx index 2d536d34d..3f2f40770 100644 --- a/src/components/CommandBar/CommandBarSelectionInput.tsx +++ b/src/components/CommandBar/CommandBarSelectionInput.tsx @@ -2,12 +2,12 @@ import { useSelector } from '@xstate/react' import { useEffect, useMemo, useRef, useState } from 'react' import type { StateFrom } from 'xstate' -import type { Artifact } from '@src/lang/std/artifactGraph' import type { CommandArgument } from '@src/lib/commandTypes' import { canSubmitSelectionArg, getSelectionCountByType, getSelectionTypeDisplayText, + getSemanticSelectionType, type Selections, } from '@src/lib/selections' import { engineCommandManager, kclManager } from '@src/lib/singletons' @@ -16,29 +16,6 @@ import { toSync } from '@src/lib/utils' import { commandBarActor, useCommandBarState } from '@src/lib/singletons' import type { modelingMachine } from '@src/machines/modelingMachine' -const semanticEntityNames: { - [key: string]: Array -} = { - face: ['wall', 'cap'], - profile: ['solid2d'], - edge: ['segment', 'sweepEdge', 'edgeCutEdge'], - point: [], - plane: ['defaultPlane'], -} - -function getSemanticSelectionType(selectionType: Array) { - const semanticSelectionType = new Set() - selectionType.forEach((type) => { - Object.entries(semanticEntityNames).forEach(([entity, entityTypes]) => { - if (entityTypes.includes(type)) { - semanticSelectionType.add(entity) - } - }) - }) - - return Array.from(semanticSelectionType) -} - const selectionSelector = (snapshot?: StateFrom) => snapshot?.context.selectionRanges diff --git a/src/components/CustomIcon.tsx b/src/components/CustomIcon.tsx index 59ce40897..b5e8f2d94 100644 --- a/src/components/CustomIcon.tsx +++ b/src/components/CustomIcon.tsx @@ -748,6 +748,16 @@ const CustomIconMap = { /> ), + loading: ( + + + + ), lockClosed: ( (
) -export function HelpMenu({ - navigate = () => {}, -}: { - navigate?: NavigateFunction -}) { +export function HelpMenu() { + const navigate = useNavigate() const location = useLocation() const filePath = useAbsoluteFilePath() @@ -49,15 +45,12 @@ export function HelpMenu({ useMenuListener(cb) return ( - + - + Help and resources Help and resources diff --git a/src/components/LowerRightControls.tsx b/src/components/LowerRightControls.tsx deleted file mode 100644 index cbe148919..000000000 --- a/src/components/LowerRightControls.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { Link, type NavigateFunction, useLocation } from 'react-router-dom' -import { Popover } from '@headlessui/react' -import { - BillingRemaining, - BillingRemainingMode, -} from '@src/components/BillingRemaining' -import { BillingDialog } from '@src/components/BillingDialog' - -import { CustomIcon } from '@src/components/CustomIcon' -import { HelpMenu } from '@src/components/HelpMenu' -import { NetworkHealthIndicator } from '@src/components/NetworkHealthIndicator' -import { NetworkMachineIndicator } from '@src/components/NetworkMachineIndicator' -import Tooltip from '@src/components/Tooltip' -import { useAbsoluteFilePath } from '@src/hooks/useAbsoluteFilePath' -import { PATHS } from '@src/lib/paths' -import { APP_VERSION, getReleaseUrl } from '@src/routes/utils' - -import { billingActor } from '@src/lib/singletons' -import { ActionButton } from '@src/components/ActionButton' -import { isDesktop } from '@src/lib/isDesktop' -import { VITE_KC_SITE_BASE_URL } from '@src/env' -import { APP_DOWNLOAD_PATH } from '@src/lib/constants' - -export function LowerRightControls({ - children, - navigate = () => {}, -}: { - children?: React.ReactNode - navigate?: NavigateFunction -}) { - const location = useLocation() - const filePath = useAbsoluteFilePath() - - const linkOverrideClassName = - '!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30' - - return ( -
- {children} - - - - - - Text-to-CAD credits - - - - - - - {isDesktop() ? ( - - v{APP_VERSION} - - ) : ( - - Download the app - - )} - - - Telemetry - - Telemetry - - - - - Settings - - Settings - - - - {!location.pathname.startsWith(PATHS.HOME) && ( - - )} - - -
- ) -} diff --git a/src/components/NetworkHealthIndicator.tsx b/src/components/NetworkHealthIndicator.tsx index ad6987a39..74cdb6979 100644 --- a/src/components/NetworkHealthIndicator.tsx +++ b/src/components/NetworkHealthIndicator.tsx @@ -1,13 +1,11 @@ -import { Popover } from '@headlessui/react' - import type { ActionIconProps } from '@src/components/ActionIcon' import { ActionIcon } from '@src/components/ActionIcon' -import Tooltip from '@src/components/Tooltip' import { useNetworkContext } from '@src/hooks/useNetworkContext' import { NetworkHealthState } from '@src/hooks/useNetworkStatus' import type { ConnectingTypeGroup } from '@src/lang/std/engineConnection' import { reportRejection } from '@src/lib/trap' import { toSync } from '@src/lib/utils' +import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes' export const NETWORK_HEALTH_TEXT: Record = { [NetworkHealthState.Ok]: 'Strong', @@ -66,143 +64,125 @@ const overallConnectionStateColor: Record = }, } -const overallConnectionStateIcon: Record< - NetworkHealthState, - ActionIconProps['icon'] -> = { +const overallConnectionStateIcon = { [NetworkHealthState.Ok]: 'network', [NetworkHealthState.Weak]: 'network', [NetworkHealthState.Issue]: 'networkCrossedOut', [NetworkHealthState.Disconnected]: 'networkCrossedOut', +} as const + +export const useNetworkHealthStatus = (): StatusBarItemType => { + const { overallState } = useNetworkContext() + + return { + id: 'network-health', + 'data-testid': `network-toggle-${ + overallState === NetworkHealthState.Ok ? 'ok' : 'other' + }`, + label: `Network health (${NETWORK_HEALTH_TEXT[overallState]})`, + hideLabel: true, + element: 'popover', + className: overallConnectionStateColor[overallState].icon, + icon: overallConnectionStateIcon[overallState], + popoverContent: , + } } -export const NetworkHealthIndicator = () => { +function NetworkHealthPopoverContent() { const { - hasIssues, overallState, internetConnected, steps, issues, error, + ping, setHasCopied, hasCopied, - ping, } = useNetworkContext() return ( - - +
- - - Network health ({NETWORK_HEALTH_TEXT[overallState]}) - - - -
Network health +

-

Network health

-

- {NETWORK_HEALTH_TEXT[overallState]} -

-
-
-

- Ping -

-

- {ping ?? 'N/A'} -

-
-
    - {Object.keys(steps).map((name) => ( -
  • -
    -

    {name}

    - {internetConnected ? ( - - ) : ( - - )} -
    - {issues[name as ConnectingTypeGroup] && ( - + {NETWORK_HEALTH_TEXT[overallState]} +

    +
+
+

+ Ping +

+

+ {ping ?? 'N/A'} +

+
+
    + {Object.keys(steps).map((name) => ( +
  • +
    +

    {name}

    + {internetConnected ? ( + + ) : ( + )} -
  • - ))} -
- -
+
+ {issues[name as ConnectingTypeGroup] && ( + + )} + + ))} + +
) } diff --git a/src/components/NetworkMachineIndicator.tsx b/src/components/NetworkMachineIndicator.tsx index 924dac5fb..6f7b67844 100644 --- a/src/components/NetworkMachineIndicator.tsx +++ b/src/components/NetworkMachineIndicator.tsx @@ -5,7 +5,7 @@ import { MachineManagerContext } from '@src/components/MachineManagerProvider' import Tooltip from '@src/components/Tooltip' import { isDesktop } from '@src/lib/isDesktop' import type { components } from '@src/lib/machine-api' -import { capitaliseFC } from '@src/lib/utils' +import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes' export const NetworkMachineIndicator = ({ className, @@ -22,68 +22,95 @@ export const NetworkMachineIndicator = ({ return isDesktop() ? ( - - {machineCount > 0 && ( -

- {machineCount} -

- )} - + + Network machines ({machineCount}) {reason && `: ${reason}`}
-
-

Network machines

-

- {machineCount} -

-
- {machineCount > 0 && ( -
    - {machines.map( - (machine: components['schemas']['MachineInfoResponse']) => { - return ( -
  • -

    {machine.id.toUpperCase()}

    -

    - {machine.make_model.model} -

    - {machine.extra && - machine.extra.type === 'bambu' && - machine.extra.nozzle_diameter && ( -

    - Nozzle Diameter: {machine.extra.nozzle_diameter} -

    - )} -

    - {`Status: ${capitaliseFC(machine.state.state)}`} - {machine.state.state === 'failed' && machine.state.message - ? ` (${machine.state.message})` - : ''} - {machine.state.state === 'running' && machine.progress - ? ` (${Math.round(machine.progress)}%)` - : ''} -

    -
  • - ) - } - )} -
- )} +
) : null } + +export const useNetworkMachineStatus = (): StatusBarItemType => { + return { + id: 'network-machines', + component: NetworkMachineIndicator, + } +} + +function NetworkMachinesIcon({ machineCount }: { machineCount: number }) { + return ( + <> + + {machineCount > 0 && ( +

+ {machineCount} +

+ )} + + ) +} + +function NetworkMachinesPopoverContent({ + machines, +}: { machines: components['schemas']['MachineInfoResponse'][] }) { + return ( +
+
+

Network machines

+

+ {machines.length} +

+
+ {machines.length > 0 && ( +
    + {machines.map( + (machine: components['schemas']['MachineInfoResponse']) => { + return ( +
  • +

    {machine.id.toUpperCase()}

    +

    + {machine.make_model.model} +

    + {machine.extra && + machine.extra.type === 'bambu' && + machine.extra.nozzle_diameter && ( +

    + Nozzle Diameter: {machine.extra.nozzle_diameter} +

    + )} +

    + {`Status: ${machine.state.state + .charAt(0) + .toUpperCase()}${machine.state.state.slice(1)}`} + {machine.state.state === 'failed' && machine.state.message + ? ` (${machine.state.message})` + : ''} + {machine.state.state === 'running' && machine.progress + ? ` (${Math.round(machine.progress)}%)` + : ''} +

    +
  • + ) + } + )} +
+ )} +
+ ) +} diff --git a/src/components/StatusBar/StatusBar.tsx b/src/components/StatusBar/StatusBar.tsx new file mode 100644 index 000000000..9002bc012 --- /dev/null +++ b/src/components/StatusBar/StatusBar.tsx @@ -0,0 +1,161 @@ +import { ActionButton } from '@src/components/ActionButton' +import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes' +import Tooltip, { type TooltipProps } from '@src/components/Tooltip' +import { ActionIcon } from '@src/components/ActionIcon' +import { Popover } from '@headlessui/react' + +export function StatusBar({ + globalItems, + localItems, +}: { + globalItems: StatusBarItemType[] + localItems: StatusBarItemType[] +}) { + return ( +
+ + {globalItems.map((item) => ( + + ))} + + + {localItems.map((item) => ( + + ))} + +
+ ) +} + +function StatusBarItem( + props: StatusBarItemType & { position: 'left' | 'middle' | 'right' } +) { + const defaultClassNames = + 'flex items-center px-2 py-1 text-xs text-chalkboard-80 dark:text-chalkboard-30 rounded-none border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-80 focus:bg-chalkboard-30 dark:focus:bg-chalkboard-80 hover:text-chalkboard-100 dark:hover:text-chalkboard-10 focus:text-chalkboard-100 dark:focus:text-chalkboard-10 focus:outline-none focus-visible:ring-2 focus:ring-primary focus:ring-opacity-50' + const tooltipPosition: TooltipProps['position'] = + props.position === 'middle' ? 'top' : `top-${props.position}` + + // If the consumer used `component`, just render that + if ('component' in props) { + return props.component({}) + } + + switch (props.element) { + case 'button': + return ( + + {'label' in props && props.label && !props.hideLabel && ( + {props.label} + )} + {(props.toolTip || (props.label && props.hideLabel)) && ( + + )} + + ) + case 'popover': + return ( + + + {'label' in props && props.label && !props.hideLabel && ( + {props.label} + )} + {(props.toolTip || (props.label && props.hideLabel)) && ( + + )} + + {props.popoverContent} + + ) + case 'text': + return ( +
+ {'icon' in props && props.icon && ( + + )} + {'label' in props && props.label && !props.hideLabel && ( + {props.label} + )} + {(props.toolTip || (props.label && props.hideLabel)) && ( + + )} +
+ ) + default: + return ( + + {'label' in props && props.label && !props.hideLabel && ( + {props.label} + )} + {(props.toolTip || (props.label && props.hideLabel)) && ( + + )} + + ) + } +} diff --git a/src/components/StatusBar/defaultStatusBarItems.tsx b/src/components/StatusBar/defaultStatusBarItems.tsx new file mode 100644 index 000000000..e00540946 --- /dev/null +++ b/src/components/StatusBar/defaultStatusBarItems.tsx @@ -0,0 +1,95 @@ +import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes' +import type { Location } from 'react-router-dom' +import { PATHS } from '@src/lib/paths' +import { APP_VERSION } from '@src/routes/utils' +import { + BillingRemaining, + BillingRemainingMode, +} from '@src/components/BillingRemaining' +import { billingActor } from '@src/lib/singletons' +import { BillingDialog } from '@src/components/BillingDialog' +import { Popover } from '@headlessui/react' +import Tooltip from '@src/components/Tooltip' +import { HelpMenu } from '@src/components/HelpMenu' + +export const defaultGlobalStatusBarItems = ({ + location, + filePath, +}: { + location: Location + filePath?: string +}): StatusBarItemType[] => [ + { + id: 'version', + element: 'externalLink', + label: `v${APP_VERSION}`, + href: `https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`, + toolTip: { + children: 'View the release notes on GitHub', + }, + }, + { + id: 'telemetry', + element: 'link', + icon: 'stopwatch', + href: location.pathname.includes(PATHS.FILE) + ? filePath + PATHS.TELEMETRY + '?tab=project' + : PATHS.HOME + PATHS.TELEMETRY, + 'data-testid': 'telemetry-link', + label: 'Telemetry', + hideLabel: true, + toolTip: { + children: 'Telemetry', + }, + }, + { + id: 'settings', + element: 'link', + icon: 'settings', + href: location.pathname.includes(PATHS.FILE) + ? filePath + PATHS.SETTINGS + '?tab=project' + : PATHS.HOME + PATHS.SETTINGS, + 'data-testid': 'settings-link', + label: 'Settings', + }, + { + id: 'credits', + 'data-testid': 'billing-remaining-bar', + component: BillingStatusBarItem, + }, +] + +function BillingStatusBarItem() { + return ( + + + + + Text-to-CAD credits + + + + + + + ) +} + +export const defaultLocalStatusBarItems: StatusBarItemType[] = [ + { + id: 'help', + 'data-testid': 'help-button', + component: HelpMenu, + }, +] diff --git a/src/components/StatusBar/statusBarTypes.ts b/src/components/StatusBar/statusBarTypes.ts new file mode 100644 index 000000000..405b55083 --- /dev/null +++ b/src/components/StatusBar/statusBarTypes.ts @@ -0,0 +1,34 @@ +import type { CustomIconName } from '@src/components/CustomIcon' +import type { TooltipProps } from '@src/components/Tooltip' + +export type StatusBarItemType = { + id: string + 'data-testid'?: string +} & ( + | ({ + label: string + hideLabel?: boolean + className?: string + toolTip?: Omit + icon?: CustomIconName + } & ( + | { + element: 'button' + onClick: (event: React.MouseEvent) => void + } + | { + element: 'popover' + popoverContent: React.ReactNode + } + | { + element: 'link' | 'externalLink' + href: string + } + | { + element: 'text' + } + )) + | { + component: React.FC + } +) diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx index a5295dc49..4de1ce452 100644 --- a/src/components/Tooltip.tsx +++ b/src/components/Tooltip.tsx @@ -8,7 +8,7 @@ type LeftOrRight = 'left' | 'right' type Corner = `${TopOrBottom}-${LeftOrRight}` type TooltipPosition = TopOrBottom | LeftOrRight | Corner -interface TooltipProps extends React.PropsWithChildren { +export interface TooltipProps extends React.PropsWithChildren { position?: TooltipPosition wrapperClassName?: string contentClassName?: string diff --git a/src/lib/selections.ts b/src/lib/selections.ts index c9a17b27c..52ddb8d10 100644 --- a/src/lib/selections.ts +++ b/src/lib/selections.ts @@ -779,3 +779,27 @@ export function updateSelections( otherSelections: prevSelectionRanges.otherSelections, } } + +const semanticEntityNames: { + [key: string]: Array +} = { + face: ['wall', 'cap'], + profile: ['solid2d'], + edge: ['segment', 'sweepEdge', 'edgeCutEdge'], + point: [], + plane: ['defaultPlane'], +} + +/** Convert selections to a human-readable format */ +export function getSemanticSelectionType(selectionType: Artifact['type'][]) { + const semanticSelectionType = new Set() + for (const type of selectionType) { + for (const [entity, entityTypes] of Object.entries(semanticEntityNames)) { + if (entityTypes.includes(type)) { + semanticSelectionType.add(entity) + } + } + } + + return Array.from(semanticSelectionType) +} diff --git a/src/lib/xStateValueToString.ts b/src/lib/xStateValueToString.ts new file mode 100644 index 000000000..d8163678a --- /dev/null +++ b/src/lib/xStateValueToString.ts @@ -0,0 +1,23 @@ +import type { AnyStateMachine, StateFrom } from 'xstate' + +/** + * Convert an XState state value to a pretty string, + * with nested states separated by slashes + */ +export function xStateValueToString( + stateValue: StateFrom['value'] +) { + const sep = ' / ' + let output = '' + let remainingValues = stateValue + let isFirstStep = true + while (remainingValues instanceof Object) { + const key: keyof typeof remainingValues = Object.keys(remainingValues)[0] + output += (isFirstStep ? '' : sep) + key + remainingValues = remainingValues[key] + isFirstStep = false + } + if (typeof remainingValues === 'string' && remainingValues.trim().length) { + return output + sep + remainingValues.trim() + } +} diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index b323216bd..358fad270 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -12,7 +12,6 @@ import { import { ActionButton } from '@src/components/ActionButton' import { AppHeader } from '@src/components/AppHeader' import Loading from '@src/components/Loading' -import { LowerRightControls } from '@src/components/LowerRightControls' import ProjectCard from '@src/components/ProjectCard/ProjectCard' import { ProjectSearchBar, @@ -61,6 +60,12 @@ import { import { CustomIcon } from '@src/components/CustomIcon' import Tooltip from '@src/components/Tooltip' import { ML_EXPERIMENTAL_MESSAGE } from '@src/lib/constants' +import { StatusBar } from '@src/components/StatusBar/StatusBar' +import { useNetworkMachineStatus } from '@src/components/NetworkMachineIndicator' +import { + defaultLocalStatusBarItems, + defaultGlobalStatusBarItems, +} from '@src/components/StatusBar/defaultStatusBarItems' type ReadWriteProjectState = { value: boolean @@ -75,6 +80,7 @@ const Home = () => { const readWriteProjectDir = useCanReadWriteProjectDirectory() const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false) const apiToken = useToken() + const networkMachineStatus = useNetworkMachineStatus() // Only create the native file menus on desktop useEffect(() => { @@ -216,7 +222,7 @@ const Home = () => { nativeFileMenuCreated={nativeFileMenuCreated} showToolbar={false} /> -
+
{ readWriteProjectDir={readWriteProjectDir} className="col-start-2 -col-end-1" /> -
+
) }