Merge branch 'main' into achalmers/kw-fn-sketches

This commit is contained in:
Adam Chalmers
2025-02-03 11:01:55 -06:00
28 changed files with 194 additions and 97 deletions

View File

@ -2,8 +2,8 @@ NODE_ENV=development
DEV=true DEV=true
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev
VITE_KC_SKIP_AUTH=false VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000 VITE_KC_CONNECTION_TIMEOUT_MS=5000
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence! # ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!

View File

@ -1,5 +1,8 @@
NODE_ENV=production
DEV=false
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.zoo.dev VITE_KC_API_BASE_URL=https://api.zoo.dev
VITE_KC_SITE_BASE_URL=https://zoo.dev VITE_KC_SITE_BASE_URL=https://zoo.dev
VITE_KC_SITE_APP_URL=https://app.zoo.dev
VITE_KC_SKIP_AUTH=false VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=15000 VITE_KC_CONNECTION_TIMEOUT_MS=15000

View File

@ -134,8 +134,6 @@ jobs:
max_attempts: 3 max_attempts: 3
command: yarn install command: yarn install
- run: yarn tronb:vite
- name: Prepare certificate and variables (Windows only) - name: Prepare certificate and variables (Windows only)
if: ${{ (env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true') && matrix.os == 'windows-2022' }} if: ${{ (env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true') && matrix.os == 'windows-2022' }}
run: | run: |
@ -165,8 +163,8 @@ jobs:
- name: Build the app (debug) - name: Build the app (debug)
if: ${{ env.IS_RELEASE == 'false' && env.IS_NIGHTLY == 'false' }} if: ${{ env.IS_RELEASE == 'false' && env.IS_NIGHTLY == 'false' }}
# electron-builder doesn't have a concept of release vs debug, # electron-builder doesn't have a concept of release vs debug,
# this is just not doing any codesign or release yml generation # this is just not doing any codesign or release yml generation, and points to dev infra
run: yarn electron-builder --config run: yarn tronb:package:dev
- name: Build the app (release) - name: Build the app (release)
if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
@ -185,7 +183,7 @@ jobs:
with: with:
timeout_minutes: 10 timeout_minutes: 10
max_attempts: 3 max_attempts: 3
command: yarn electron-builder --config --publish always command: yarn tronb:package:prod
- name: List artifacts in out/ - name: List artifacts in out/
run: ls -R out run: ls -R out
@ -246,7 +244,7 @@ jobs:
with: with:
timeout_minutes: 10 timeout_minutes: 10
max_attempts: 3 max_attempts: 3
command: yarn electron-builder --config --publish always command: yarn tronb:package:prod
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ env.IS_RELEASE == 'true' }} if: ${{ env.IS_RELEASE == 'true' }}
@ -390,19 +388,19 @@ jobs:
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
if: ${{ env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_NIGHTLY == 'true' }}
uses: 'google-github-actions/auth@v2.1.7' uses: 'google-github-actions/auth@v2.1.8'
with: with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}' credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
- name: Set up Google Cloud SDK - name: Set up Google Cloud SDK
if: ${{ env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_NIGHTLY == 'true' }}
uses: google-github-actions/setup-gcloud@v2.1.2 uses: google-github-actions/setup-gcloud@v2.1.4
with: with:
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }} project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
- name: Upload nightly files to public bucket - name: Upload nightly files to public bucket
if: ${{ env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_NIGHTLY == 'true' }}
uses: google-github-actions/upload-cloud-storage@v2.2.1 uses: google-github-actions/upload-cloud-storage@v2.2.2
with: with:
path: out path: out
glob: '*' glob: '*'

View File

@ -123,9 +123,9 @@ jobs:
if: steps.download-wasm.outcome == 'failure' if: steps.download-wasm.outcome == 'failure'
shell: bash shell: bash
run: yarn build:wasm run: yarn build:wasm
- name: build electron - name: build web
shell: bash shell: bash
run: yarn tron:package run: yarn tronb:vite:dev
# - name: Run ubuntu/chrome snapshots # - name: Run ubuntu/chrome snapshots
# if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }} # if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
# shell: bash # shell: bash

View File

@ -108,17 +108,17 @@ jobs:
run: yarn files:set-notes run: yarn files:set-notes
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.7' uses: 'google-github-actions/auth@v2.1.8'
with: with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}' credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
- name: Set up Google Cloud SDK - name: Set up Google Cloud SDK
uses: google-github-actions/setup-gcloud@v2.1.2 uses: google-github-actions/setup-gcloud@v2.1.4
with: with:
project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }} project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }}
- name: Upload release files to public bucket - name: Upload release files to public bucket
uses: google-github-actions/upload-cloud-storage@v2.2.1 uses: google-github-actions/upload-cloud-storage@v2.2.2
with: with:
path: out path: out
glob: '*' glob: '*'

View File

@ -101,7 +101,7 @@ This will start the application and hot-reload on changes.
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows). Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows).
To build, run `yarn tron:package`. To build with electron-builder, run `yarn tronb:package:dev` (or `yarn tronb:package:prod` to point to the .env.production variables)
## Checking out commits / Bisecting ## Checking out commits / Bisecting

View File

@ -75,3 +75,6 @@ publish:
channel: latest channel: latest
releaseInfo: releaseInfo:
releaseNotesFile: release-notes.md releaseNotesFile: release-notes.md
protocols:
- name: Zoo Studio
schemes: ['zoo-studio']

View File

@ -9,23 +9,8 @@ const rootDir = process.cwd()
const config: ForgeConfig = { const config: ForgeConfig = {
packagerConfig: { packagerConfig: {
asar: true, asar: true,
osxSign: (process.env.BUILD_RELEASE === 'true' && {}) || undefined,
osxNotarize:
(process.env.BUILD_RELEASE === 'true' && {
appleId: process.env.APPLE_ID || '',
appleIdPassword: process.env.APPLE_PASSWORD || '',
teamId: process.env.APPLE_TEAM_ID || '',
}) ||
undefined,
executableName: 'zoo-modeling-app', executableName: 'zoo-modeling-app',
icon: path.resolve(rootDir, 'assets', 'icon'), icon: path.resolve(rootDir, 'assets', 'icon'),
protocols: [
{
name: 'Zoo Studio',
schemes: ['zoo-studio'],
},
],
extendInfo: 'Info.plist', // Information for file associations.
}, },
rebuildConfig: {}, rebuildConfig: {},
makers: [], makers: [],

1
interface.d.ts vendored
View File

@ -65,6 +65,7 @@ export interface IElectronAPI {
VITE_KC_API_WS_MODELING_URL: string VITE_KC_API_WS_MODELING_URL: string
VITE_KC_API_BASE_URL: string VITE_KC_API_BASE_URL: string
VITE_KC_SITE_BASE_URL: string VITE_KC_SITE_BASE_URL: string
VITE_KC_SITE_APP_URL: string
VITE_KC_SKIP_AUTH: string VITE_KC_SKIP_AUTH: string
VITE_KC_CONNECTION_TIMEOUT_MS: string VITE_KC_CONNECTION_TIMEOUT_MS: string
VITE_KC_DEV_TOKEN: string VITE_KC_DEV_TOKEN: string

View File

@ -103,11 +103,11 @@
"make:dev": "make dev", "make:dev": "make dev",
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts", "generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"tron:start": "electron-forge start", "tron:start": "electron-forge start",
"tron:package": "electron-forge package",
"chrome:test": "PLATFORM=web NODE_ENV=development yarn playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert='@snapshot'", "chrome:test": "PLATFORM=web NODE_ENV=development yarn playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert='@snapshot'",
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", "tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development",
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts", "tronb:vite:prod": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
"tronb:package": "electron-builder --config electron-builder.yml", "tronb:package:dev": "yarn tronb:vite:dev && electron-builder --config electron-builder.yml",
"tronb:package:prod": "yarn tronb:vite:prod && electron-builder --config electron-builder.yml --publish always",
"test-setup": "yarn install && yarn build:wasm", "test-setup": "yarn install && yarn build:wasm",
"test": "vitest --mode development", "test": "vitest --mode development",
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts", "test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
@ -116,10 +116,10 @@
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\" --quiet", "test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\" --quiet",
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot' --quiet", "test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot' --quiet",
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot' --quiet", "test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot' --quiet",
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", "test:playwright:electron:local": "yarn tronb:package:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"", "test:playwright:electron:windows:local": "yarn tronb:package:dev && set NODE_ENV='development' && playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'", "test:playwright:electron:macos:local": "yarn tronb:package:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
"test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'", "test:playwright:electron:ubuntu:local": "yarn tronb:package:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000", "test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
"test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000" "test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
}, },

View File

@ -29,6 +29,7 @@ import * as TWEEN from '@tweenjs/tween.js'
import { isQuaternionVertical } from './helpers' import { isQuaternionVertical } from './helpers'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType' import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
const ORTHOGRAPHIC_CAMERA_SIZE = 20 const ORTHOGRAPHIC_CAMERA_SIZE = 20
const FRAMES_TO_ANIMATE_IN = 30 const FRAMES_TO_ANIMATE_IN = 30
@ -406,7 +407,7 @@ export class CameraControls {
.sub(this.mouseDownPosition) .sub(this.mouseDownPosition)
this.mouseDownPosition.copy(this.mouseNewPosition) this.mouseDownPosition.copy(this.mouseNewPosition)
const interaction = this.getInteractionType(event) let interaction = this.getInteractionType(event)
if (interaction === 'none') return if (interaction === 'none') return
// If there's a valid interaction and the mouse is moving, // If there's a valid interaction and the mouse is moving,
@ -753,8 +754,6 @@ export class CameraControls {
didChange = true didChange = true
} }
this.safeLookAtTarget(this.camera.up)
// Update the camera's matrices // Update the camera's matrices
this.camera.updateMatrixWorld() this.camera.updateMatrixWorld()
if (didChange || forceUpdate) { if (didChange || forceUpdate) {
@ -1189,14 +1188,24 @@ export class CameraControls {
this.deferReactUpdate(this.reactCameraProperties) this.deferReactUpdate(this.reactCameraProperties)
Object.values(this._camChangeCallbacks).forEach((cb) => cb()) Object.values(this._camChangeCallbacks).forEach((cb) => cb())
} }
getInteractionType = (event: MouseEvent) => getInteractionType = (
_getInteractionType( event: MouseEvent
): CameraDragInteractionType_type | 'none' => {
const initialInteractionType = _getInteractionType(
this.interactionGuards, this.interactionGuards,
event, event,
this.enablePan, this.enablePan,
this.enableRotate, this.enableRotate,
this.enableZoom this.enableZoom
) )
if (
initialInteractionType === 'rotate' &&
this.engineCommandManager.settings.cameraOrbit === 'trackball'
) {
return 'rotatetrackball'
}
return initialInteractionType
}
} }
// Pure function helpers // Pure function helpers

View File

@ -119,6 +119,7 @@ export const ModelingMachineProvider = ({
cameraProjection, cameraProjection,
highlightEdges, highlightEdges,
showScaleGrid, showScaleGrid,
cameraOrbit,
}, },
}, },
}, },
@ -1154,6 +1155,7 @@ export const ModelingMachineProvider = ({
enableSSAO: enableSSAO.current, enableSSAO: enableSSAO.current,
showScaleGrid: showScaleGrid.current, showScaleGrid: showScaleGrid.current,
cameraProjection: cameraProjection.current, cameraProjection: cameraProjection.current,
cameraOrbit: cameraOrbit.current,
}, },
token token
) )
@ -1183,6 +1185,13 @@ export const ModelingMachineProvider = ({
editorManager.selectionRanges = modelingState.context.selectionRanges editorManager.selectionRanges = modelingState.context.selectionRanges
}, [modelingState.context.selectionRanges]) }, [modelingState.context.selectionRanges])
// When changing camera modes reset the camera to the default orientation to correct
// the up vector otherwise the conconical orientation for the camera modes will be
// wrong
useEffect(() => {
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
}, [cameraOrbit.current])
useEffect(() => { useEffect(() => {
const onConnectionStateChanged = ({ detail }: CustomEvent) => { const onConnectionStateChanged = ({ detail }: CustomEvent) => {
// If we are in sketch mode we need to exit it. // If we are in sketch mode we need to exit it.

View File

@ -33,7 +33,7 @@ export const OpenInDesktopAppHandler = (props: React.PropsWithChildren) => {
function onOpenInDesktopApp() { function onOpenInDesktopApp() {
const newSearchParams = new URLSearchParams(globalThis.location.search) const newSearchParams = new URLSearchParams(globalThis.location.search)
newSearchParams.delete(ASK_TO_OPEN_QUERY_PARAM) newSearchParams.delete(ASK_TO_OPEN_QUERY_PARAM)
const newURL = `${ZOO_STUDIO_PROTOCOL}${globalThis.location.pathname.replace( const newURL = `${ZOO_STUDIO_PROTOCOL}://${globalThis.location.pathname.replace(
'/', '/',
'' ''
)}${searchParams.size > 0 ? `?${newSearchParams.toString()}` : ''}` )}${searchParams.size > 0 ? `?${newSearchParams.toString()}` : ''}`

View File

@ -19,7 +19,7 @@ import { commandBarActor } from 'machines/commandBarMachine'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import { copyFileShareLink } from 'lib/links' import { copyFileShareLink } from 'lib/links'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { DEV } from 'env' import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
import { useToken } from 'machines/appMachine' import { useToken } from 'machines/appMachine'
const ProjectSidebarMenu = ({ const ProjectSidebarMenu = ({
@ -112,6 +112,7 @@ function ProjectMenuPopover({
const { onProjectClose } = useLspContext() const { onProjectClose } = useLspContext()
const exportCommandInfo = { name: 'Export', groupId: 'modeling' } const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
const makeCommandInfo = { name: 'Make', groupId: 'modeling' } const makeCommandInfo = { name: 'Make', groupId: 'modeling' }
const shareCommandInfo = { name: 'share-file-link', groupId: 'code' }
const findCommand = (obj: { name: string; groupId: string }) => const findCommand = (obj: { name: string; groupId: string }) =>
Boolean( Boolean(
commands.find((c) => c.name === obj.name && c.groupId === obj.groupId) commands.find((c) => c.name === obj.name && c.groupId === obj.groupId)
@ -193,7 +194,7 @@ function ProjectMenuPopover({
id: 'share-link', id: 'share-link',
Element: 'button', Element: 'button',
children: 'Share link to file', children: 'Share link to file',
disabled: !DEV, disabled: IS_NIGHTLY_OR_DEBUG || !findCommand(shareCommandInfo),
onClick: async () => { onClick: async () => {
await copyFileShareLink({ await copyFileShareLink({
token: token ?? '', token: token ?? '',

View File

@ -10,6 +10,7 @@ export const VITE_KC_API_WS_MODELING_URL = env.VITE_KC_API_WS_MODELING_URL as
| undefined | undefined
export const VITE_KC_API_BASE_URL = env.VITE_KC_API_BASE_URL as string export const VITE_KC_API_BASE_URL = env.VITE_KC_API_BASE_URL as string
export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL as string export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL as string
export const VITE_KC_SITE_APP_URL = env.VITE_KC_SITE_APP_URL as string
export const VITE_KC_SKIP_AUTH = env.VITE_KC_SKIP_AUTH as string | undefined export const VITE_KC_SKIP_AUTH = env.VITE_KC_SKIP_AUTH as string | undefined
export const VITE_KC_CONNECTION_TIMEOUT_MS = export const VITE_KC_CONNECTION_TIMEOUT_MS =
env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined

View File

@ -16,14 +16,15 @@ export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>, streamRef: React.RefObject<HTMLDivElement>,
modelingSend: ReturnType<typeof useModelingContext>['send'], modelingSend: ReturnType<typeof useModelingContext>['send'],
modelingContext: ReturnType<typeof useModelingContext>['context'], modelingContext: ReturnType<typeof useModelingContext>['context'],
settings = { settings: SettingsViaQueryString = {
pool: null, pool: null,
theme: Themes.System, theme: Themes.System,
highlightEdges: true, highlightEdges: true,
enableSSAO: true, enableSSAO: true,
showScaleGrid: false, showScaleGrid: false,
cameraProjection: 'perspective', cameraProjection: 'perspective',
} as SettingsViaQueryString, cameraOrbit: 'spherical',
},
token?: string token?: string
) { ) {
const networkContext = useNetworkContext() const networkContext = useNetworkContext()

View File

@ -1389,6 +1389,7 @@ export class EngineCommandManager extends EventTarget {
enableSSAO: true, enableSSAO: true,
showScaleGrid: false, showScaleGrid: false,
cameraProjection: 'perspective', cameraProjection: 'perspective',
cameraOrbit: 'spherical',
} }
} }
@ -1437,6 +1438,7 @@ export class EngineCommandManager extends EventTarget {
enableSSAO: true, enableSSAO: true,
showScaleGrid: false, showScaleGrid: false,
cameraProjection: 'orthographic', cameraProjection: 'orthographic',
cameraOrbit: 'spherical',
}, },
// When passed, use a completely separate connecting code path that simply // When passed, use a completely separate connecting code path that simply
// opens a websocket and this is a function that is called when connected. // opens a websocket and this is a function that is called when connected.

View File

@ -68,8 +68,6 @@ export const KCL_DEFAULT_DEGREE = `360`
/** localStorage key for the playwright test-specific app settings file */ /** localStorage key for the playwright test-specific app settings file */
export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings' export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings'
export const DEFAULT_HOST = 'https://api.zoo.dev'
export const PROD_APP_URL = 'https://app.zoo.dev'
export const SETTINGS_FILE_NAME = 'settings.toml' export const SETTINGS_FILE_NAME = 'settings.toml'
export const TOKEN_FILE_NAME = 'token.txt' export const TOKEN_FILE_NAME = 'token.txt'
export const PROJECT_SETTINGS_FILE_NAME = 'project.toml' export const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
@ -145,7 +143,7 @@ export const VIEW_NAMES_SEMANTIC = {
export const SIDEBAR_BUTTON_SUFFIX = '-pane-button' export const SIDEBAR_BUTTON_SUFFIX = '-pane-button'
/** Custom URL protocol our desktop registers */ /** Custom URL protocol our desktop registers */
export const ZOO_STUDIO_PROTOCOL = 'zoo-studio:' export const ZOO_STUDIO_PROTOCOL = 'zoo-studio'
/** /**
* A query parameter that triggers a modal * A query parameter that triggers a modal

View File

@ -1,11 +1,13 @@
import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning' import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning'
import { Command, CommandArgumentOption } from './commandTypes' import { Command, CommandArgumentOption } from './commandTypes'
import { kclManager } from './singletons' import { codeManager, kclManager } from './singletons'
import { isDesktop } from './isDesktop' import { isDesktop } from './isDesktop'
import { FILE_EXT } from './constants' import { FILE_EXT } from './constants'
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models' import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
import { reportRejection } from './trap' import { reportRejection } from './trap'
import { IndexLoaderData } from './types' import { IndexLoaderData } from './types'
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
import { copyFileShareLink } from './links'
interface OnSubmitProps { interface OnSubmitProps {
sampleName: string sampleName: string
@ -132,21 +134,22 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
}, },
}, },
}, },
// { {
// name: 'share-file-link', name: 'share-file-link',
// displayName: 'Share file', displayName: 'Share file',
// description: 'Create a link that contains a copy of the current file.', hide: IS_NIGHTLY_OR_DEBUG ? undefined : 'desktop',
// groupId: 'code', description: 'Create a link that contains a copy of the current file.',
// needsReview: false, groupId: 'code',
// icon: 'link', needsReview: false,
// onSubmit: () => { icon: 'link',
// copyFileShareLink({ onSubmit: () => {
// token: commandProps.authToken, copyFileShareLink({
// code: codeManager.code, token: commandProps.authToken,
// name: commandProps.projectData.project?.name || '', code: codeManager.code,
// units: commandProps.settings.defaultUnit, name: commandProps.projectData.project?.name || '',
// }).catch(reportRejection) units: commandProps.settings.defaultUnit,
// }, }).catch(reportRejection)
// }, },
},
] ]
} }

View File

@ -1,3 +1,4 @@
import { VITE_KC_SITE_APP_URL } from 'env'
import { createCreateFileUrl } from './links' import { createCreateFileUrl } from './links'
describe(`link creation tests`, () => { describe(`link creation tests`, () => {
@ -8,7 +9,7 @@ describe(`link creation tests`, () => {
// Converted with external online tools // Converted with external online tools
const expectedEncodedCode = `ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D` const expectedEncodedCode = `ZXh0cnVzaW9uRGlzdGFuY2UgPSAxMg%3D%3D`
const expectedLink = `http://localhost:3000/?create-file=true&name=test&units=mm&code=${expectedEncodedCode}&ask-open-desktop=true` const expectedLink = `${VITE_KC_SITE_APP_URL}/?create-file=true&name=test&units=mm&code=${expectedEncodedCode}&ask-open-desktop=true`
const result = createCreateFileUrl({ code, name, units }) const result = createCreateFileUrl({ code, name, units })
expect(result.toString()).toBe(expectedLink) expect(result.toString()).toBe(expectedLink)

View File

@ -1,11 +1,7 @@
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models' import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
import { import { ASK_TO_OPEN_QUERY_PARAM, CREATE_FILE_URL_PARAM } from './constants'
ASK_TO_OPEN_QUERY_PARAM,
CREATE_FILE_URL_PARAM,
PROD_APP_URL,
} from './constants'
import { stringToBase64 } from './base64' import { stringToBase64 } from './base64'
import { DEV, VITE_KC_API_BASE_URL } from 'env' import { VITE_KC_API_BASE_URL, VITE_KC_SITE_APP_URL } from 'env'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { err } from './trap' import { err } from './trap'
export interface FileLinkParams { export interface FileLinkParams {
@ -51,8 +47,7 @@ export async function copyFileShareLink(
* open the URL in the desktop app. * open the URL in the desktop app.
*/ */
export function createCreateFileUrl({ code, name, units }: FileLinkParams) { export function createCreateFileUrl({ code, name, units }: FileLinkParams) {
// Use the dev server if we are in development mode let origin = VITE_KC_SITE_APP_URL
let origin = DEV ? 'http://localhost:3000' : PROD_APP_URL
const searchParams = new URLSearchParams({ const searchParams = new URLSearchParams({
[CREATE_FILE_URL_PARAM]: String(true), [CREATE_FILE_URL_PARAM]: String(true),
name, name,

View File

@ -20,6 +20,7 @@ import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType' import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus' import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType'
/** /**
* A setting that can be set at the user or project level * A setting that can be set at the user or project level
@ -380,6 +381,30 @@ export function createSettings() {
})), })),
}, },
}), }),
/**
* What methodology to use for orbiting the camera
*/
cameraOrbit: new Setting<CameraOrbitType>({
defaultValue: 'spherical',
hideOnLevel: 'project',
description: 'What methodology to use for orbiting the camera',
validate: (v) => ['spherical', 'trackball'].includes(v),
commandConfig: {
inputType: 'options',
defaultValueFromContext: (context) =>
context.modeling.cameraOrbit.current,
options: (cmdContext, settingsContext) =>
(['spherical', 'trackball'] as const).map((v) => ({
name: v.charAt(0).toUpperCase() + v.slice(1),
value: v,
isCurrent:
settingsContext.modeling.cameraOrbit.shouldShowCurrentLabel(
cmdContext.argumentsToSubmit.level as SettingsLevel,
v
),
})),
},
}),
/** /**
* Whether to highlight edges of 3D objects * Whether to highlight edges of 3D objects
*/ */

View File

@ -4,6 +4,7 @@ import { AtLeast, PathValue, Paths } from 'lib/types'
import { CommandArgumentConfig } from 'lib/commandTypes' import { CommandArgumentConfig } from 'lib/commandTypes'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType' import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType'
export interface SettingsViaQueryString { export interface SettingsViaQueryString {
pool: string | null pool: string | null
@ -12,6 +13,7 @@ export interface SettingsViaQueryString {
enableSSAO: boolean enableSSAO: boolean
showScaleGrid: boolean showScaleGrid: boolean
cameraProjection: CameraProjectionType cameraProjection: CameraProjectionType
cameraOrbit: CameraOrbitType
} }
export enum UnitSystem { export enum UnitSystem {

View File

@ -49,6 +49,7 @@ export function configurationToSettingsPayload(
modeling: { modeling: {
defaultUnit: configuration?.settings?.modeling?.base_unit, defaultUnit: configuration?.settings?.modeling?.base_unit,
cameraProjection: configuration?.settings?.modeling?.camera_projection, cameraProjection: configuration?.settings?.modeling?.camera_projection,
cameraOrbit: configuration?.settings?.modeling?.camera_orbit,
mouseControls: mouseControlsToCameraSystem( mouseControls: mouseControlsToCameraSystem(
configuration?.settings?.modeling?.mouse_controls configuration?.settings?.modeling?.mouse_controls
), ),

View File

@ -31,23 +31,27 @@ let mainWindow: BrowserWindow | null = null
// Check the command line arguments for a project path // Check the command line arguments for a project path
const args = parseCLIArgs() const args = parseCLIArgs()
// If it's not set, scream. // @ts-ignore: TS1343
const NODE_ENV = process.env.NODE_ENV || 'production' const viteEnv = import.meta.env
if (!process.env.NODE_ENV) const NODE_ENV = process.env.NODE_ENV || viteEnv.MODE
console.warn(
'*FOX SCREAM* process.env.NODE_ENV is not explicitly set!, defaulting to production'
)
// Default prod values
// dotenv override when present // dotenv override when present
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] }) dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
process.env.VITE_KC_API_WS_MODELING_URL ??= // default vite values based on mode
'wss://api.zoo.dev/ws/modeling/commands' process.env.NODE_ENV ??= viteEnv.MODE
process.env.VITE_KC_API_BASE_URL ??= 'https://api.zoo.dev' process.env.DEV ??= viteEnv.DEV + ''
process.env.VITE_KC_SITE_BASE_URL ??= 'https://zoo.dev' process.env.BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL
process.env.VITE_KC_SKIP_AUTH ??= 'false' process.env.VITE_KC_API_WS_MODELING_URL ??= viteEnv.VITE_KC_API_WS_MODELING_URL
process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??= '15000' process.env.VITE_KC_API_BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL
process.env.VITE_KC_SITE_BASE_URL ??= viteEnv.VITE_KC_SITE_BASE_URL
process.env.VITE_KC_SITE_APP_URL ??= viteEnv.VITE_KC_SITE_APP_URL
process.env.VITE_KC_SKIP_AUTH ??= viteEnv.VITE_KC_SKIP_AUTH
process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??=
viteEnv.VITE_KC_CONNECTION_TIMEOUT_MS
// Likely convenient to keep for debugging
console.log('process.env', process.env)
/// Register our application to handle all "zoo-studio:" protocols. /// Register our application to handle all "zoo-studio:" protocols.
if (process.defaultApp) { if (process.defaultApp) {
@ -89,22 +93,43 @@ const createWindow = (pathToOpen?: string, reuse?: boolean): BrowserWindow => {
}) })
} }
// Deep Link: Case of a cold start from Windows or Linux
if (
!pathToOpen &&
process.argv.length > 1 &&
process.argv[1].indexOf(ZOO_STUDIO_PROTOCOL + '://') > -1
) {
pathToOpen = process.argv[1]
console.log('Retrieved deep link from argv', pathToOpen)
}
// Deep Link: Case of a second window opened for macOS
// @ts-ignore
if (!pathToOpen && global['openUrls'] && global['openUrls'][0]) {
// @ts-ignore
pathToOpen = global['openUrls'][0]
console.log('Retrieved deep link from open-url', pathToOpen)
}
const pathIsCustomProtocolLink = const pathIsCustomProtocolLink =
pathToOpen?.startsWith(ZOO_STUDIO_PROTOCOL) ?? false pathToOpen?.startsWith(ZOO_STUDIO_PROTOCOL) ?? false
// and load the index.html of the app. // and load the index.html of the app.
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
const filteredPath = pathToOpen const filteredPath = pathToOpen
? decodeURI(pathToOpen.replace(ZOO_STUDIO_PROTOCOL, '')) ? decodeURI(pathToOpen.replace(ZOO_STUDIO_PROTOCOL + '://', ''))
: '' : ''
const fullHashBasedUrl = `${MAIN_WINDOW_VITE_DEV_SERVER_URL}/#/${filteredPath}` const fullHashBasedUrl = `${MAIN_WINDOW_VITE_DEV_SERVER_URL}/#/${filteredPath}`
newWindow.loadURL(fullHashBasedUrl).catch(reportRejection) newWindow.loadURL(fullHashBasedUrl).catch(reportRejection)
} else { } else {
if (pathIsCustomProtocolLink && pathToOpen) { if (pathIsCustomProtocolLink && pathToOpen) {
// We're trying to open a custom protocol link // We're trying to open a custom protocol link
const filteredPath = pathToOpen // TODO: fix the replace %3 thing
? decodeURI(pathToOpen.replace(ZOO_STUDIO_PROTOCOL, '')) const urlNoProtocol = pathToOpen
: '' .replace(ZOO_STUDIO_PROTOCOL + '://', '')
.replaceAll('%3D', '')
.replaceAll('%3', '')
const filteredPath = decodeURI(urlNoProtocol)
const startIndex = path.join( const startIndex = path.join(
__dirname, __dirname,
`../renderer/${MAIN_WINDOW_VITE_NAME}/index.html` `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`
@ -342,7 +367,7 @@ export function getAutoUpdater(): AppUpdater {
app.on('ready', () => { app.on('ready', () => {
// Disable auto updater on non-versioned builds // Disable auto updater on non-versioned builds
if (packageJSON.version === '0.0.0') { if (packageJSON.version === '0.0.0' && viteEnv.MODE !== 'production') {
return return
} }
@ -459,6 +484,14 @@ function parseCLIArgs(): minimist.ParsedArgs {
} }
function registerStartupListeners() { function registerStartupListeners() {
// Linux and Windows from https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Deep Link: second instance for Windows and Linux
const url = commandLine.pop()?.slice(0, -1)
console.log('Retrieved deep link from commandLine', url)
createWindow(url)
})
/** /**
* macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before * macOS: when someone drops a file to the not-yet running VSCode, the open-file event fires even before
* the app-ready event. We listen very early for open-file and remember this upon startup as path to open. * the app-ready event. We listen very early for open-file and remember this upon startup as path to open.
@ -478,7 +511,7 @@ function registerStartupListeners() {
}) })
/** /**
* macOS: react to open-url requests. * macOS: react to open-url requests (including Deep Link on second instances)
*/ */
const openUrls: string[] = [] const openUrls: string[] = []
// @ts-ignore // @ts-ignore

View File

@ -185,6 +185,7 @@ contextBridge.exposeInMainWorld('electron', {
'VITE_KC_API_WS_MODELING_URL', 'VITE_KC_API_WS_MODELING_URL',
'VITE_KC_API_BASE_URL', 'VITE_KC_API_BASE_URL',
'VITE_KC_SITE_BASE_URL', 'VITE_KC_SITE_BASE_URL',
'VITE_KC_SITE_APP_URL',
'VITE_KC_SKIP_AUTH', 'VITE_KC_SKIP_AUTH',
'VITE_KC_CONNECTION_TIMEOUT_MS', 'VITE_KC_CONNECTION_TIMEOUT_MS',
'VITE_KC_DEV_TOKEN', 'VITE_KC_DEV_TOKEN',

View File

@ -259,6 +259,9 @@ pub struct ModelingSettings {
/// The projection mode the camera should use while modeling. /// The projection mode the camera should use while modeling.
#[serde(default, alias = "cameraProjection", skip_serializing_if = "is_default")] #[serde(default, alias = "cameraProjection", skip_serializing_if = "is_default")]
pub camera_projection: CameraProjectionType, pub camera_projection: CameraProjectionType,
/// The methodology the camera should use to orbit around the model.
#[serde(default, alias = "cameraOrbit", skip_serializing_if = "is_default")]
pub camera_orbit: CameraOrbitType,
/// The controls for how to navigate the 3D view. /// The controls for how to navigate the 3D view.
#[serde(default, alias = "mouseControls", skip_serializing_if = "is_default")] #[serde(default, alias = "mouseControls", skip_serializing_if = "is_default")]
pub mouse_controls: MouseControlType, pub mouse_controls: MouseControlType,
@ -415,6 +418,21 @@ pub enum CameraProjectionType {
Orthographic, Orthographic,
} }
/// The types of camera orbit methods.
#[derive(Debug, Default, Eq, PartialEq, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, Display, FromStr)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
#[display(style = "snake_case")]
pub enum CameraOrbitType {
/// Orbit using a spherical camera movement.
#[default]
#[display("spherical")]
Spherical,
/// Orbit using a trackball camera movement.
#[display("trackball")]
Trackball,
}
/// Settings that affect the behavior of the KCL text editor. /// Settings that affect the behavior of the KCL text editor.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Eq, Validate)] #[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Eq, Validate)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -543,6 +561,8 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use validator::Validate; use validator::Validate;
use crate::settings::types::CameraOrbitType;
use super::{ use super::{
AppColor, AppSettings, AppTheme, AppearanceSettings, CameraProjectionType, CommandBarSettings, Configuration, AppColor, AppSettings, AppTheme, AppearanceSettings, CameraProjectionType, CommandBarSettings, Configuration,
ModelingSettings, OnboardingStatus, ProjectSettings, Settings, TextEditorSettings, UnitLength, ModelingSettings, OnboardingStatus, ProjectSettings, Settings, TextEditorSettings, UnitLength,
@ -594,6 +614,7 @@ textWrapping = true
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::In, base_unit: UnitLength::In,
camera_projection: CameraProjectionType::Orthographic, camera_projection: CameraProjectionType::Orthographic,
camera_orbit: Default::default(),
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true, show_debug_panel: true,
@ -656,6 +677,7 @@ includeSettings = false
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::Yd, base_unit: UnitLength::Yd,
camera_projection: Default::default(), camera_projection: Default::default(),
camera_orbit: Default::default(),
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true, show_debug_panel: true,
@ -723,6 +745,7 @@ defaultProjectName = "projects-$nnn"
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::Yd, base_unit: UnitLength::Yd,
camera_projection: Default::default(), camera_projection: Default::default(),
camera_orbit: CameraOrbitType::Spherical,
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true, show_debug_panel: true,
@ -802,6 +825,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::Mm, base_unit: UnitLength::Mm,
camera_projection: Default::default(), camera_projection: Default::default(),
camera_orbit: Default::default(),
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: true.into(), highlight_edges: true.into(),
show_debug_panel: false, show_debug_panel: false,

View File

@ -129,6 +129,7 @@ includeSettings = false
modeling: ModelingSettings { modeling: ModelingSettings {
base_unit: UnitLength::Yd, base_unit: UnitLength::Yd,
camera_projection: Default::default(), camera_projection: Default::default(),
camera_orbit: Default::default(),
mouse_controls: Default::default(), mouse_controls: Default::default(),
highlight_edges: Default::default(), highlight_edges: Default::default(),
show_debug_panel: true, show_debug_panel: true,