Compare commits

...

5 Commits

Author SHA1 Message Date
9e1cf90c81 Release derive-docs (#4446) 2024-11-08 15:23:55 -05:00
062fae1e54 Release kcl and kcl-test-server (#4438) 2024-11-08 11:09:58 -06:00
d7660e221c Nadro/1919/on drag number fix (#3997)
* fix: fixing on drag number inc/dec massive amount of unit tests

* fix: implemented all scenarios for inc/dec formatting

* fix: deleting unused code

* fix: clearer name

* fix: adding commments

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* fix: does this trigger the CI?

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

---------

Co-authored-by: 49fl <ircsurfer33@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-08 08:24:37 -06:00
938e27adac Nadro/3799/perf (#4145)
* chore: building out perf testing

* chore: adding my printing code for the different formats of the marks

* feat: adding invocation count table

* fix: markOnce iunstead

* fix: typescript additions

* fix: adding more types

* chore: adding telemetry panel as MVP, gonna remove the pane

* chore: view telemetry from command bar in file route and home route

* fix: deleting unused imports

* fix: deleting some unused files

* fix: auto cleanup

* chore: adding other routes, these will need to be moved...

* chore: moving some printing logic around and unit testing some of it

* fix: moving command init

* fix: removing debugging marks

* fix: adding some comments

* fix: fixed a bug with generating the go to page commands

* chore: adding will pages load within the router config

* chore: implementing marks for routes

* fix: auto fixes and checkers

* chore: implemented a route watcher at the root level...

* fix: auto fixes, removing unused code

* chore: timing for syntax highlighting and auto fixes

* fix: didAuth issue and syntax highlighting in the packaged application. Constructor name gets renamed

* fix: fixing typescript checks

* chore: adding mag bar chart icon for telemetry

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* chore: swapped telemetry icon for stopwatch

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* chore: writing telemetry to disk

* fix: auto fixers

* chore: getting args parsed for cli flags and writing telemetry file

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* chore: swapped mark for markOnce since we infinitely write marks to a JS array... need to solve this run time marking in another way. We only need this for startup right now

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* chore: writing raw marks to disk as well

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* fix: cleaned up the testing names

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* Fix fmt and codespell

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* fix: moving this route loader data stuff

* chore: adding comment

* fix: fmt

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* empty :(

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* empty :(

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-07 16:23:03 -06:00
17b9af2416 Bump image from 0.25.3 to 0.25.5 in /src/wasm-lib (#4416)
Bumps [image](https://github.com/image-rs/image) from 0.25.3 to 0.25.5.
- [Changelog](https://github.com/image-rs/image/blob/main/CHANGES.md)
- [Commits](https://github.com/image-rs/image/compare/v0.25.3...v0.25.5)

---
updated-dependencies:
- dependency-name: image
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-07 13:04:33 -08:00
75 changed files with 2251 additions and 55 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 37 KiB

1
interface.d.ts vendored
View File

@ -78,6 +78,7 @@ export interface IElectronAPI {
) => Electron.IpcRenderer
onUpdateError: (callback: (value: { error: Error }) => void) => Electron
appRestart: () => void
getArgvParsed: () => any
}
declare global {

View File

@ -65,7 +65,8 @@
"vscode-languageserver-protocol": "^3.17.5",
"vscode-uri": "^3.0.8",
"web-vitals": "^3.5.2",
"xstate": "^5.17.4"
"xstate": "^5.17.4",
"yargs": "^17.7.2"
},
"scripts": {
"start": "vite",

View File

@ -22,6 +22,10 @@ import Gizmo from 'components/Gizmo'
import { CoreDumpManager } from 'lib/coredump'
import { UnitsMenu } from 'components/UnitsMenu'
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
import { maybeWriteToDisk } from 'lib/telemetry'
maybeWriteToDisk()
.then(() => {})
.catch(() => {})
export function App() {
const { project, file } = useLoaderData() as IndexLoaderData

View File

@ -8,6 +8,7 @@ import {
} from 'react-router-dom'
import { ErrorPage } from './components/ErrorPage'
import { Settings } from './routes/Settings'
import { Telemetry } from './routes/Telemetry'
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
import SignIn from './routes/SignIn'
import { Auth } from './Auth'
@ -28,6 +29,7 @@ import {
homeLoader,
onboardingRedirectLoader,
settingsLoader,
telemetryLoader,
} from 'lib/routeLoaders'
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider'
import SettingsAuthProvider from 'components/SettingsAuthProvider'
@ -43,6 +45,7 @@ import { coreDump } from 'lang/wasm'
import { useMemo } from 'react'
import { AppStateProvider } from 'AppState'
import { reportRejection } from 'lib/trap'
import { RouteProvider } from 'components/RouteProvider'
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
@ -56,19 +59,21 @@ const router = createRouter([
* inefficient re-renders, use the react profiler to see. */
element: (
<CommandBarProvider>
<SettingsAuthProvider>
<LspProvider>
<ProjectsContextProvider>
<KclContextProvider>
<AppStateProvider>
<MachineManagerProvider>
<Outlet />
</MachineManagerProvider>
</AppStateProvider>
</KclContextProvider>
</ProjectsContextProvider>
</LspProvider>
</SettingsAuthProvider>
<RouteProvider>
<SettingsAuthProvider>
<LspProvider>
<ProjectsContextProvider>
<KclContextProvider>
<AppStateProvider>
<MachineManagerProvider>
<Outlet />
</MachineManagerProvider>
</AppStateProvider>
</KclContextProvider>
</ProjectsContextProvider>
</LspProvider>
</SettingsAuthProvider>
</RouteProvider>
</CommandBarProvider>
),
errorElement: <ErrorPage />,
@ -124,6 +129,16 @@ const router = createRouter([
},
],
},
{
id: PATHS.FILE + 'TELEMETRY',
loader: telemetryLoader,
children: [
{
path: makeUrlPathRelative(PATHS.TELEMETRY),
element: <Telemetry />,
},
],
},
],
},
{
@ -149,6 +164,11 @@ const router = createRouter([
loader: settingsLoader,
element: <Settings />,
},
{
path: makeUrlPathRelative(PATHS.TELEMETRY),
loader: telemetryLoader,
element: <Telemetry />,
},
],
},
{

12
src/commandLineArgs.ts Normal file
View File

@ -0,0 +1,12 @@
import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'
const argv = yargs(hideBin(process.argv))
.option('telemetry', {
alias: 't',
type: 'boolean',
description: 'Writes startup telemetry to file on disk.',
})
.parse()
export default argv

View File

@ -1161,6 +1161,29 @@ const CustomIconMap = {
/>
</svg>
),
stopwatch: (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.95705 5.99046C7.05643 6.44935 6.33654 7.19809 5.91336 8.11602C5.49019 9.03396 5.38838 10.0676 5.62434 11.0505C5.8603 12.0334 6.42029 12.9081 7.21408 13.5339C8.00787 14.1597 8.98922 14.5 10 14.5C11.0108 14.5 11.9921 14.1597 12.7859 13.5339C13.5797 12.9082 14.1397 12.0334 14.3757 11.0505C14.6116 10.0676 14.5098 9.03396 14.0866 8.11603C13.6635 7.19809 12.9436 6.44935 12.043 5.99046L12.497 5.09946C13.5977 5.66032 14.4776 6.57544 14.9948 7.69737C15.512 8.81929 15.6364 10.0827 15.348 11.2839C15.0596 12.4852 14.3752 13.5544 13.405 14.3192C12.4348 15.0841 11.2354 15.5 10 15.5C8.7646 15.5 7.56517 15.0841 6.59499 14.3192C5.6248 13.5544 4.94037 12.4852 4.65197 11.2839C4.36357 10.0827 4.488 8.81929 5.00522 7.69736C5.52243 6.57544 6.40231 5.66032 7.50306 5.09946L7.95705 5.99046Z"
fill="currentColor"
/>
<path d="M10 5.5V4M10 4H8M10 4H12" stroke="currentColor" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.8536 7.85356L10.3536 10.3536C10.1583 10.5488 9.84171 10.5488 9.64645 10.3536C9.45118 10.1583 9.45118 9.84172 9.64645 9.64645L12.1464 7.14645L12.8536 7.85356Z"
fill="currentColor"
/>
</svg>
),
} as const
export type CustomIconName = keyof typeof CustomIconMap

View File

@ -29,6 +29,7 @@ import {
KclSamplesManifestItem,
} from 'lib/getKclSamplesManifest'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { markOnce } from 'lib/performance'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -54,6 +55,7 @@ export const FileMachineProvider = ({
)
useEffect(() => {
markOnce('code/didLoadFile')
async function fetchKclSamples() {
setKclSamples(await getKclSamplesManifest())
}

View File

@ -96,6 +96,23 @@ export function LowerRightControls({
Report a bug
</Tooltip>
</a>
<Link
to={
location.pathname.includes(PATHS.FILE)
? filePath + PATHS.TELEMETRY + '?tab=project'
: PATHS.HOME + PATHS.TELEMETRY
}
data-testid="telemetry-link"
>
<CustomIcon
name="stopwatch"
className={`w-5 h-5 ${linkOverrideClassName}`}
/>
<span className="sr-only">Telemetry</span>
<Tooltip position="top" contentClassName="text-xs">
Telemetry
</Tooltip>
</Link>
<Link
to={
location.pathname.includes(PATHS.FILE)

View File

@ -4,7 +4,7 @@ import { Themes, getSystemTheme } from 'lib/theme'
import { useMemo, useRef } from 'react'
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
import { lineHighlightField } from 'editor/highlightextension'
import { roundOff } from 'lib/utils'
import { onMouseDragMakeANewNumber, onMouseDragRegex } from 'lib/utils'
import {
lineNumbers,
rectangularSelection,
@ -129,7 +129,9 @@ export const KclEditorPane = () => {
closeBrackets(),
highlightActiveLine(),
highlightSelectionMatches(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
syntaxHighlighting(defaultHighlightStyle, {
fallback: true,
}),
rectangularSelection(),
dropCursor(),
interact({
@ -137,29 +139,12 @@ export const KclEditorPane = () => {
// a rule for a number dragger
{
// the regexp matching the value
regexp: /-?\b\d+\.?\d*\b/g,
regexp: onMouseDragRegex,
// set cursor to "ew-resize" on hover
cursor: 'ew-resize',
// change number value based on mouse X movement on drag
onDrag: (text, setText, e) => {
const multiplier =
e.shiftKey && e.metaKey
? 0.01
: e.metaKey
? 0.1
: e.shiftKey
? 10
: 1
const delta = e.movementX * multiplier
const newVal = roundOff(
Number(text) + delta,
multiplier === 0.01 ? 2 : multiplier === 0.1 ? 1 : 0
)
if (isNaN(newVal)) return
setText(newVal.toString())
onMouseDragMakeANewNumber(text, setText, e)
},
},
],

View File

@ -0,0 +1,33 @@
import { useEffect, useState, createContext, ReactNode } from 'react'
import { useNavigation, useLocation } from 'react-router-dom'
import { PATHS } from 'lib/paths'
import { markOnce } from 'lib/performance'
export const RouteProviderContext = createContext({})
export function RouteProvider({ children }: { children: ReactNode }) {
const [first, setFirstState] = useState(true)
const navigation = useNavigation()
const location = useLocation()
useEffect(() => {
// On initialization, the react-router-dom does not send a 'loading' state event.
// it sends an idle event first.
const pathname = first ? location.pathname : navigation.location?.pathname
const isHome = pathname === PATHS.HOME
const isFile =
pathname?.includes(PATHS.FILE) &&
pathname?.substring(pathname?.length - 4) === '.kcl'
if (isHome) {
markOnce('code/willLoadHome')
} else if (isFile) {
markOnce('code/willLoadFile')
}
setFirstState(false)
}, [navigation])
return (
<RouteProviderContext.Provider value={{}}>
{children}
</RouteProviderContext.Provider>
)
}

View File

@ -1,7 +1,7 @@
import { trap } from 'lib/trap'
import { useMachine } from '@xstate/react'
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
import { PATHS } from 'lib/paths'
import { PATHS, BROWSER_PATH } from 'lib/paths'
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
import withBaseUrl from '../lib/withBaseURL'
import React, { createContext, useEffect, useState } from 'react'
@ -42,6 +42,7 @@ import { getAppSettingsFilePath } from 'lib/desktop'
import { isDesktop } from 'lib/isDesktop'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { codeManager } from 'lib/singletons'
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -288,6 +289,44 @@ export const SettingsAuthProviderBase = ({
settingsWithCommandConfigs,
])
// Due to the route provider, i've moved this to the SettingsAuthProvider instead of CommandBarProvider
// This will register the commands to route to Telemetry, Home, and Settings.
useEffect(() => {
const filePath =
PATHS.FILE +
'/' +
encodeURIComponent(loadedProject?.file?.path || BROWSER_PATH)
const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
createRouteCommands(navigate, location, filePath)
commandBarSend({
type: 'Remove commands',
data: {
commands: [
RouteTelemetryCommand,
RouteHomeCommand,
RouteSettingsCommand,
],
},
})
if (location.pathname === PATHS.HOME) {
commandBarSend({
type: 'Add commands',
data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
})
} else if (location.pathname.includes(PATHS.FILE)) {
commandBarSend({
type: 'Add commands',
data: {
commands: [
RouteTelemetryCommand,
RouteSettingsCommand,
RouteHomeCommand,
],
},
})
}
}, [location])
// Listen for changes to the system theme and update the app theme accordingly
// This is only done if the theme setting is set to 'system'.
// It can't be done in XState (in an invoked callback, for example)

View File

@ -0,0 +1,72 @@
import { getMarks } from 'lib/performance'
import {
printDeltaTotal,
printInvocationCount,
printMarkDownTable,
printRawMarks,
} from 'lib/telemetry'
export function TelemetryExplorer() {
const marks = getMarks()
const markdownTable = printMarkDownTable(marks)
const rawMarks = printRawMarks(marks)
const deltaTotalTable = printDeltaTotal(marks)
const invocationCount = printInvocationCount(marks)
// TODO data-telemetry-type
// TODO data-telemetry-name
return (
<div>
<h1 className="pb-4">Marks</h1>
<div className="max-w-xl max-h-64 overflow-auto select-all">
{marks.map((mark, index) => {
return (
<pre className="text-xs" key={index}>
<code key={index}>{JSON.stringify(mark, null, 2)}</code>
</pre>
)
})}
</div>
<h1 className="pb-4">Startup Performance</h1>
<div className="max-w-xl max-h-64 overflow-auto select-all">
{markdownTable.map((line, index) => {
return (
<pre className="text-xs" key={index}>
<code key={index}>{line}</code>
</pre>
)
})}
</div>
<h1 className="pb-4">Delta and Totals</h1>
<div className="max-w-xl max-h-64 overflow-auto select-all">
{deltaTotalTable.map((line, index) => {
return (
<pre className="text-xs" key={index}>
<code key={index}>{line}</code>
</pre>
)
})}
</div>
<h1 className="pb-4">Raw Marks</h1>
<div className="max-w-xl max-h-64 overflow-auto select-all">
{rawMarks.map((line, index) => {
return (
<pre className="text-xs" key={index}>
<code key={index}>{line}</code>
</pre>
)
})}
</div>
<h1 className="pb-4">Invocation Count</h1>
<div className="max-w-xl max-h-64 overflow-auto select-all">
{invocationCount.map((line, index) => {
return (
<pre className="text-xs" key={index}>
<code key={index}>{line}</code>
</pre>
)
})}
</div>
</div>
)
}

View File

@ -1,4 +1,5 @@
import { EditorView, ViewUpdate } from '@codemirror/view'
import { syntaxTree } from '@codemirror/language'
import { EditorSelection, Annotation, Transaction } from '@codemirror/state'
import { engineCommandManager } from 'lib/singletons'
import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine'
@ -12,6 +13,7 @@ import {
setDiagnosticsEffect,
} from '@codemirror/lint'
import { StateFrom } from 'xstate'
import { markOnce } from 'lib/performance'
const updateOutsideEditorAnnotation = Annotation.define<boolean>()
export const updateOutsideEditorEvent = updateOutsideEditorAnnotation.of(true)
@ -59,6 +61,48 @@ export default class EditorManager {
setEditorView(editorView: EditorView) {
this._editorView = editorView
this.overrideTreeHighlighterUpdateForPerformanceTracking()
}
overrideTreeHighlighterUpdateForPerformanceTracking() {
// @ts-ignore
this._editorView?.plugins.forEach((e) => {
let sawATreeDiff = false
// we cannot use <>.constructor.name since it will get destroyed
// when packaging the application.
const isTreeHighlightPlugin =
e.value.hasOwnProperty('tree') &&
e.value.hasOwnProperty('decoratedTo') &&
e.value.hasOwnProperty('decorations')
if (isTreeHighlightPlugin) {
let originalUpdate = e.value.update
// @ts-ignore
function performanceTrackingUpdate(args) {
/**
* TreeHighlighter.update will be called multiple times on start up.
* We do not want to track the highlight performance of an empty update.
* mark the syntax highlight one time when the new tree comes in with the
* initial code
*/
const treeIsDifferent =
// @ts-ignore
!sawATreeDiff && this.tree !== syntaxTree(args.state)
if (treeIsDifferent && !sawATreeDiff) {
markOnce('code/willSyntaxHighlight')
}
// Call the original function
// @ts-ignore
originalUpdate.apply(this, [args])
if (treeIsDifferent && !sawATreeDiff) {
markOnce('code/didSyntaxHighlight')
sawATreeDiff = true
}
}
e.value.update = performanceTrackingUpdate
}
})
}
get editorView(): EditorView | null {

View File

@ -8,8 +8,10 @@ import ModalContainer from 'react-modal-promise'
import { isDesktop } from 'lib/isDesktop'
import { AppStreamProvider } from 'AppState'
import { ToastUpdate } from 'components/ToastUpdate'
import { markOnce } from 'lib/performance'
import { AUTO_UPDATER_TOAST_ID } from 'lib/constants'
markOnce('code/willAuth')
// uncomment for xstate inspector
// import { DEV } from 'env'
// import { inspect } from '@xstate/inspect'

View File

@ -21,6 +21,7 @@ import {
import { getNodeFromPath } from './queryAst'
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
import { Diagnostic } from '@codemirror/lint'
import { markOnce } from 'lib/performance'
import { Node } from 'wasm-lib/kcl/bindings/Node'
interface ExecuteArgs {
@ -257,6 +258,7 @@ export class KclManager {
}
const ast = args.ast || this.ast
markOnce('code/startExecuteAst')
const currentExecutionId = args.executionId || Date.now()
this._cancelTokens.set(currentExecutionId, false)
@ -331,6 +333,7 @@ export class KclManager {
})
this._cancelTokens.delete(currentExecutionId)
markOnce('code/endExecuteAst')
}
// NOTE: this always updates the code state and editor.
// DO NOT CALL THIS from codemirror ever.

View File

@ -28,6 +28,7 @@ import {
} from 'lib/constants'
import { KclManager } from 'lang/KclSingleton'
import { reportRejection } from 'lib/trap'
import { markOnce } from 'lib/performance'
import { MachineManager } from 'components/MachineManagerProvider'
// TODO(paultag): This ought to be tweakable.
@ -330,6 +331,7 @@ class EngineConnection extends EventTarget {
token?: string
callbackOnEngineLiteConnect?: () => void
}) {
markOnce('code/startInitialEngineConnect')
super()
this.engineCommandManager = engineCommandManager
@ -785,6 +787,7 @@ class EngineConnection extends EventTarget {
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.Opened, { detail: this })
)
markOnce('code/endInitialEngineConnect')
}
this.unreliableDataChannel?.addEventListener(
'open',

View File

@ -0,0 +1,52 @@
import { Command } from '../commandTypes'
import { PATHS } from 'lib/paths'
import { NavigateFunction, Location } from 'react-router-dom'
export function createRouteCommands(
navigate: NavigateFunction,
location: Location,
filePath: string
) {
const RouteTelemetryCommand: Command = {
name: 'Go to Telemetry',
displayName: `Go to Telemetry`,
description: 'View the Telemetry metrics',
groupId: 'routes',
icon: 'settings',
needsReview: false,
onSubmit: (data) => {
const path = location.pathname.includes(PATHS.FILE)
? filePath + PATHS.TELEMETRY + '?tab=project'
: PATHS.HOME + PATHS.TELEMETRY
navigate(path)
},
}
const RouteHomeCommand: Command = {
name: 'Go to Home',
displayName: `Go to Home`,
description: 'Go to the home page',
groupId: 'routes',
icon: 'settings',
needsReview: false,
onSubmit: (data) => {
navigate(PATHS.HOME)
},
}
const RouteSettingsCommand: Command = {
name: 'Go to Settings',
displayName: `Go to Settings`,
description: 'Go to the settings page',
groupId: 'routes',
icon: 'settings',
needsReview: false,
onSubmit: (data) => {
const path = location.pathname.includes(PATHS.FILE)
? filePath + PATHS.SETTINGS + '?tab=project'
: PATHS.HOME + PATHS.SETTINGS
navigate(path)
},
}
return { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand }
}

View File

@ -69,6 +69,8 @@ export const SETTINGS_FILE_NAME = 'settings.toml'
export const TOKEN_FILE_NAME = 'token.txt'
export const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
export const COOKIE_NAME = '__Secure-next-auth.session-token'
export const TELEMETRY_FILE_NAME = 'boot.txt'
export const TELEMETRY_RAW_FILE_NAME = 'raw-metrics.txt'
/** localStorage key to determine if we're in Playwright tests */
export const PLAYWRIGHT_KEY = 'playwright'

View File

@ -12,6 +12,8 @@ import {
PROJECT_FOLDER,
PROJECT_SETTINGS_FILE_NAME,
SETTINGS_FILE_NAME,
TELEMETRY_FILE_NAME,
TELEMETRY_RAW_FILE_NAME,
TOKEN_FILE_NAME,
} from './constants'
import { DeepPartial } from './types'
@ -419,6 +421,34 @@ const getTokenFilePath = async () => {
return window.electron.path.join(fullPath, TOKEN_FILE_NAME)
}
const getTelemetryFilePath = async () => {
const appConfig = await window.electron.getPath('appData')
const fullPath = window.electron.path.join(appConfig, getAppFolderName())
try {
await window.electron.stat(fullPath)
} catch (e) {
// File/path doesn't exist
if (e === 'ENOENT') {
await window.electron.mkdir(fullPath, { recursive: true })
}
}
return window.electron.path.join(fullPath, TELEMETRY_FILE_NAME)
}
const getRawTelemetryFilePath = async () => {
const appConfig = await window.electron.getPath('appData')
const fullPath = window.electron.path.join(appConfig, getAppFolderName())
try {
await window.electron.stat(fullPath)
} catch (e) {
// File/path doesn't exist
if (e === 'ENOENT') {
await window.electron.mkdir(fullPath, { recursive: true })
}
}
return window.electron.path.join(fullPath, TELEMETRY_RAW_FILE_NAME)
}
const getProjectSettingsFilePath = async (projectPath: string) => {
try {
await window.electron.stat(projectPath)
@ -552,6 +582,18 @@ export const writeTokenFile = async (token: string) => {
return window.electron.writeFile(tokenFilePath, token)
}
export const writeTelemetryFile = async (content: string) => {
const telemetryFilePath = await getTelemetryFilePath()
if (err(content)) return Promise.reject(content)
return window.electron.writeFile(telemetryFilePath, content)
}
export const writeRawTelemetryFile = async (content: string) => {
const rawTelemetryFilePath = await getRawTelemetryFilePath()
if (err(content)) return Promise.reject(content)
return window.electron.writeFile(rawTelemetryFilePath, content)
}
let appStateStore: Project | undefined = undefined
export const getState = async (): Promise<Project | undefined> => {

View File

@ -42,6 +42,7 @@ export const PATHS = {
SETTINGS_KEYBINDINGS: `${SETTINGS}?tab=keybindings` as const,
SIGN_IN: '/signin',
ONBOARDING: prependRoutes(onboardingPaths)('/onboarding') as OnboardingPaths,
TELEMETRY: '/telemetry',
} as const
export const BROWSER_PATH = `%2F${BROWSER_PROJECT_NAME}%2F${BROWSER_FILE_NAME}${FILE_EXT}`

127
src/lib/performance.ts Normal file
View File

@ -0,0 +1,127 @@
import { isDesktop } from 'lib/isDesktop'
function isWeb(): boolean {
// Identify browser environment when following property is not present
// https://nodejs.org/dist/latest-v16.x/docs/api/perf_hooks.html#performancenodetiming
return (
typeof performance === 'object' &&
typeof performance.mark === 'function' &&
// @ts-ignore
!performance.nodeTiming
)
}
function isNode(): boolean {
// @ts-ignore
return typeof process === 'object' && performance.nodeTiming
}
function getRuntime(): string {
if (isDesktop()) {
return 'electron'
} else if (isNode()) {
return 'nodejs'
} else if (isWeb()) {
return 'web'
}
return 'runtime unknown, could not detect'
}
export interface PerformanceMarkDetail {
[key: string]: any
}
export interface PerformanceMark {
name: string
startTime: number
entryType: string
detail: null | PerformanceMarkDetail
duration?: number
}
export interface MarkHelpers {
mark(name: string, options?: PerformanceMark): void
markOnce(name: string, options?: PerformanceMark): void
getMarks(): PerformanceMark[]
}
/**
* Detect performance API environment, either Web or Node.js
*/
function detectEnvironment(): MarkHelpers {
const seenMarks: { [key: string]: boolean } = {}
if (isWeb() || isNode() || isDesktop()) {
// in a browser context, reuse performance-util
// https://developer.mozilla.org/en-US/docs/Web/API/Performance
function _mark(name: string, options?: PerformanceMark) {
const _options = {
...options,
}
// Automatically append detail data for a canonical form
if (!_options.detail) {
_options.detail = {}
}
_options.detail.runtime = getRuntime()
performance.mark(name, _options)
}
const _helpers: MarkHelpers = {
mark(name: string, options?: PerformanceMark) {
_mark(name, options)
},
markOnce(name: string, options?: PerformanceMark) {
if (seenMarks[name]) {
return
}
_mark(name, options)
seenMarks[name] = true
},
getMarks() {
let timeOrigin = performance.timeOrigin
const result: PerformanceMark[] = [
{
name: 'code/timeOrigin',
startTime: Math.round(timeOrigin),
detail: { runtime: getRuntime() },
entryType: 'mark',
},
]
for (const entry of performance.getEntriesByType('mark')) {
result.push({
name: entry.name,
// Make everything unix time
startTime: Math.round(timeOrigin + entry.startTime),
// @ts-ignore - we can assume this is just any object with [key:string]: any
detail: entry.detail,
entryType: entry.entryType,
})
}
return result
},
}
return _helpers
} else {
// This would be browsers that do not support the performance API.
// TODO: Implement a polyfill
console.error('No performance API found globally. Going to be a bad time.')
return {
mark() {
/*no op*/
},
markOnce() {
/*no op*/
},
getMarks() {
return []
},
}
}
}
const env = detectEnvironment()
export const mark = env.mark
export const getMarks = env.getMarks
export const markOnce = env.markOnce

View File

@ -44,6 +44,12 @@ export const settingsLoader: LoaderFunction = async ({
return settings
}
export const telemetryLoader: LoaderFunction = async ({
params,
}): Promise<null> => {
return null
}
// Redirect users to the appropriate onboarding page if they haven't completed it
export const onboardingRedirectLoader: ActionFunction = async (args) => {
const { settings } = await loadAndValidateSettings()

109
src/lib/telemetry.test.ts Normal file
View File

@ -0,0 +1,109 @@
import {
columnWidth,
MaxWidth,
printHeader,
printDivider,
printRow,
} from 'lib/telemetry'
describe('Telemetry', () => {
describe('columnWidth', () => {
it('should return 0', () => {
const actual = columnWidth([{ '': '' }], '')
const expected = 0
expect(actual).toBe(expected)
})
it('should return 10 from column length', () => {
const actual = columnWidth([{ thisisten_: 'dog' }], 'thisisten_')
const expected = 10
expect(actual).toBe(expected)
})
it('should return 5 from the key length', () => {
const actual = columnWidth([{ mph: 'five5' }], 'mph')
const expected = 5
expect(actual).toBe(expected)
})
it('should return 6 from multiple values', () => {
const actual = columnWidth(
[
{ mph: '555' },
{ mph: '33' },
{ mph: '789' },
{ mph: '1231' },
{ mph: '129532' },
],
'mph'
)
const expected = 6
expect(actual).toBe(expected)
})
})
describe('printHeader', () => {
it('should print a header based on MaxWidth interface with value lengths', () => {
const widths: MaxWidth = {
metricA: 7,
metricB: 8,
metricC: 9,
metricD: 10,
}
const actual = printHeader(widths)
const expected = '| metricA | metricB | metricC | metricD |'
expect(actual).toBe(expected)
})
it('should print a header based on MaxWidth interface with key lengths', () => {
const widths: MaxWidth = {
aa: 2,
bb: 2,
cc: 2,
dd: 2,
}
const actual = printHeader(widths)
const expected = '| aa | bb | cc | dd |'
expect(actual).toBe(expected)
})
})
describe('printDivider', () => {
it('should print a divider based on MaxWidth interface with value lengths', () => {
const widths: MaxWidth = {
metricA: 7,
metricB: 8,
metricC: 9,
metricD: 10,
}
const actual = printDivider(widths)
const expected = '| ------- | -------- | --------- | ---------- |'
expect(actual).toBe(expected)
})
it('should print a divider based on MaxWidth interface with key lengths', () => {
const widths: MaxWidth = {
aa: 2,
bb: 2,
cc: 2,
dd: 2,
}
const actual = printDivider(widths)
const expected = '| -- | -- | -- | -- |'
expect(actual).toBe(expected)
})
})
describe('printRow', () => {
it('should print a row', () => {
const widths: MaxWidth = {
metricA: 7,
metricB: 8,
metricC: 9,
metricD: 10,
}
const row = {
metricA: 'aa',
metricB: 'bb',
metricC: 'cc',
metricD: 'dd',
}
const actual = printRow(row, widths)
const expected = '| aa | bb | cc | dd |'
expect(actual).toBe(expected)
})
})
})

170
src/lib/telemetry.ts Normal file
View File

@ -0,0 +1,170 @@
import { PerformanceMark, getMarks } from 'lib/performance'
import { writeTelemetryFile, writeRawTelemetryFile } from 'lib/desktop'
let args: any = null
// Get the longest width of values or column name
export function columnWidth(arr: { [key: string]: any }, key: string): number {
let maxLength = key.length
// for each value of that key, check if the length is longer
arr.forEach((value: any) => {
const valueAsString = String(value[key])
maxLength =
valueAsString.length > maxLength ? valueAsString.length : maxLength
})
return maxLength
}
export function printHeader(columnWidths: MaxWidth): string {
const headers = ['|']
const padLeft = ' '
Object.keys(columnWidths).forEach((key) => {
const maxWidth = columnWidths[key]
const padLength = maxWidth - key.length
const paddingRight = ' '.repeat(padLength + 1)
headers.push(padLeft, key, paddingRight, '|')
})
return headers.join('')
}
export function printDivider(columnWidths: MaxWidth): string {
const headers = ['|']
const padLeft = ' '
Object.keys(columnWidths).forEach((key) => {
const keyMaxLength = columnWidths[key]
const dashedLines = '-'.repeat(keyMaxLength)
headers.push(padLeft, dashedLines, ' ', '|')
})
return headers.join('')
}
export function printRow(
row: { [key: string]: any },
columnWidths: MaxWidth
): string {
const _row = ['|']
const padLeft = ' '
Object.keys(row).forEach((key) => {
const value = String(row[key])
const valueLength = value && value.length ? value.length : 0
const padLength = columnWidths[key] - valueLength
const paddingRight = ' '.repeat(padLength + 1)
_row.push(padLeft, value, paddingRight, '|')
})
return _row.join('')
}
export interface MaxWidth {
[key: string]: number
}
export function printMarkDownTable(
marks: Array<{ [key: string]: any }>
): Array<string> {
if (marks.length === 0) {
return []
}
const sample = marks[0]
const columnWidths: MaxWidth = {}
Object.keys(sample).forEach((key) => {
const width = columnWidth(marks, key)
columnWidths[key] = width
})
const lines = []
lines.push(printHeader(columnWidths))
lines.push(printDivider(columnWidths))
marks.forEach((row) => {
lines.push(printRow(row, columnWidths))
})
return lines
}
export interface PerformanceDeltaTotal {
name: string
startTime: number
delta: string
total: string
}
export function computeDeltaTotal(
marks: Array<PerformanceMark>
): Array<PerformanceDeltaTotal> {
let startTime = -1
let total = 0
const deltaTotalArray: Array<PerformanceDeltaTotal> = marks.map(
(row: PerformanceMark) => {
const delta =
startTime === -1 ? 0 : Number(row.startTime) - Number(startTime)
startTime = row.startTime
total += delta
const formatted: PerformanceDeltaTotal = {
name: row.name,
startTime: row.startTime,
delta: delta.toFixed(2),
total: total.toFixed(2),
}
return formatted
}
)
return deltaTotalArray
}
export function printDeltaTotal(marks: Array<PerformanceMark>): string[] {
const deltaTotalArray = computeDeltaTotal(marks)
return printMarkDownTable(deltaTotalArray)
}
export function printRawRow(row: { [key: string]: any }): string {
const _row = ['']
Object.keys(row).forEach((key) => {
const value = String(row[key])
_row.push(value, ' ')
})
return _row.join('')
}
export function printRawMarks(marks: Array<PerformanceMark>): string[] {
const headers = ['Name', 'Timestamp', 'Delta', 'Total', 'Detail']
const lines = ['```', headers.join(' ')]
const deltaTotalArray = computeDeltaTotal(marks)
deltaTotalArray.forEach((row) => {
lines.push(printRawRow(row))
})
lines.push('```')
return lines
}
export function printInvocationCount(marks: Array<PerformanceMark>): string[] {
const counts: { [key: string]: number } = {}
marks.forEach((mark: PerformanceMark) => {
counts[mark.name] =
counts[mark.name] === undefined ? 1 : counts[mark.name] + 1
})
const formattedCounts = Object.entries(counts).map((entry) => {
return {
name: entry[0],
count: entry[1],
}
})
return printMarkDownTable(formattedCounts)
}
export async function maybeWriteToDisk() {
if (!args) {
args = await window.electron.getArgvParsed()
}
if (args.telemetry) {
setInterval(() => {
const marks = getMarks()
const deltaTotalTable = printDeltaTotal(marks)
writeTelemetryFile(deltaTotalTable.join('\n'))
.then(() => {})
.catch(() => {})
writeRawTelemetryFile(JSON.stringify(marks))
.then(() => {})
.catch(() => {})
}, 5000)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -53,11 +53,6 @@ export function isOverlap(a: SourceRange, b: SourceRange) {
return lastOfFirst >= firstOfSecond
}
export function roundOff(num: number, places: number = 2): number {
const x = Math.pow(10, places)
return Math.round(num * x) / x
}
export function getLength(a: [number, number], b: [number, number]): number {
const x = b[0] - a[0]
const y = b[1] - a[1]
@ -269,3 +264,127 @@ export function XOR(bool1: boolean, bool2: boolean): boolean {
export function getActorNextEvents(snapshot: AnyMachineSnapshot) {
return [...new Set([...snapshot._nodes.flatMap((sn) => sn.ownEvents)])]
}
export const onMouseDragRegex = /-?\.?\b\d+\.?\d*\b/g
export function simulateOnMouseDragMatch(text: string) {
return text.match(onMouseDragRegex)
}
export function roundOff(num: number, precision: number = 2): number {
const x = Math.pow(10, precision)
return Math.round(num * x) / x
}
/**
* Determine if the number as a string has any precision in the decimal places
* '1' -> 0
* '1.0' -> 1
* '1.01' -> 2
*/
function getPrecision(text: string): number {
const wholeFractionSplit = text.split('.')
const precision =
wholeFractionSplit.length === 2 ? wholeFractionSplit[1].split('').length : 0
return precision
}
/**
* Determines if a number string has a leading digit
* 0.1 -> yes
* -0.1 -> yes
* .1 -> no
* 10.1 -> no
* The text.split('.') should evaluate to ['','<decimals>']
*/
export function hasLeadingZero(text: string): boolean {
const wholeFractionSplit = text.split('.')
return wholeFractionSplit.length === 2
? wholeFractionSplit[0] === '0' || wholeFractionSplit[0] === '-0'
: false
}
export function hasDigitsLeftOfDecimal(text: string): boolean | undefined {
const wholeFractionSplit = text.split('.')
if (wholeFractionSplit.length === 2) {
const wholeNumber = wholeFractionSplit[0]
if (wholeNumber.length === 0) {
return false
} else {
return true
}
}
if (wholeFractionSplit.length === 1) {
return true
}
// What if someone passes in 1..2.3.1...1.1.43
return undefined
}
export function onDragNumberCalculation(text: string, e: MouseEvent) {
const multiplier =
e.shiftKey && e.metaKey ? 0.01 : e.metaKey ? 0.1 : e.shiftKey ? 10 : 1
const delta = e.movementX * multiplier
const hasPeriod = text.includes('.')
const leadsWithZero = hasLeadingZero(text)
const addition = Number(text) + delta
const positiveAddition = e.movementX > 0
const negativeAddition = e.movementX < 0
const containsDigitsLeftOfDecimal = hasDigitsLeftOfDecimal(text)
let precision = Math.max(
getPrecision(text),
getPrecision(multiplier.toString())
)
const newVal = roundOff(addition, precision)
if (isNaN(newVal)) {
return
}
let formattedString = newVal.toString()
if (hasPeriod && !formattedString.includes('.')) {
// If the original number included a period lets add that back to the output string
// e.g. '1.0' add +1 then we get 2, we want to send '2.0' back since the original one had a decimal place
formattedString = formattedString.toString() + '.0'
}
/**
* Whenever you add two numbers you can always remove the the leading zero the result will make sense
* 1 + -0.01 = 0.99, the code would remove the leading 0 to make it .99 but since the number has a
* digit left of the decimal to begin with I want to make it 0.99.
* negativeAddition with fractional numbers will provide a leading 0.
*/
const removeZeros =
positiveAddition ||
(negativeAddition && multiplier < 1 && !containsDigitsLeftOfDecimal)
/**
* If the original value has no leading 0
* If if the new updated value has a leading zero
* If the math operation means you can actually remove the zero.
*/
if (!leadsWithZero && hasLeadingZero(formattedString) && removeZeros) {
if (formattedString[0] === '-') {
return ['-', formattedString.split('.')[1]].join('.')
} else {
return formattedString.substring(1)
}
}
return formattedString
}
export function onMouseDragMakeANewNumber(
text: string,
setText: (t: string) => void,
e: MouseEvent
) {
const newVal = onDragNumberCalculation(text, e)
if (!newVal) return
setText(newVal)
}

View File

@ -14,6 +14,7 @@ import {
writeTokenFile,
} from 'lib/desktop'
import { COOKIE_NAME } from 'lib/constants'
import { markOnce } from 'lib/performance'
const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
@ -156,6 +157,7 @@ async function getUser(input: { token?: string }) {
LOCAL_USER.image = ''
}
markOnce('code/didAuth')
return {
user: LOCAL_USER,
token,
@ -181,6 +183,7 @@ async function getUser(input: { token?: string }) {
if ('error_code' in user) return Promise.reject(new Error(user.message))
markOnce('code/didAuth')
return {
user: user as Models['User_type'],
token,

View File

@ -1,6 +1,5 @@
// Some of the following was taken from bits and pieces of the vite-typescript
// template that ElectronJS provides.
import dotenv from 'dotenv'
import {
app,
@ -20,6 +19,7 @@ import minimist from 'minimist'
import getCurrentProjectFile from 'lib/getCurrentProjectFile'
import os from 'node:os'
import { reportRejection } from 'lib/trap'
import argvFromYargs from './commandLineArgs'
let mainWindow: BrowserWindow | null = null
@ -158,6 +158,10 @@ ipcMain.handle('shell.openExternal', (event, data) => {
return shell.openExternal(data)
})
ipcMain.handle('argv.parser', (event, data) => {
return argvFromYargs
})
ipcMain.handle('startDeviceFlow', async (_, host: string) => {
// Do an OAuth 2.0 Device Authorization Grant dance to get a token.
// We quiet ts because we are not using this in the standard way.

View File

@ -117,6 +117,10 @@ const listMachines = async (
const getMachineApiIp = async (): Promise<String | null> =>
ipcRenderer.invoke('find_machine_api')
const getArgvParsed = () => {
return ipcRenderer.invoke('argv.parser')
}
contextBridge.exposeInMainWorld('electron', {
startDeviceFlow,
loginWithDeviceFlow,
@ -184,4 +188,5 @@ contextBridge.exposeInMainWorld('electron', {
onUpdateDownloaded,
onUpdateError,
appRestart,
getArgvParsed,
})

View File

@ -20,6 +20,7 @@ import { useRefreshSettings } from 'hooks/useRefreshSettings'
import { LowerRightControls } from 'components/LowerRightControls'
import { ProjectSearchBar, useProjectSearch } from 'components/ProjectSearchBar'
import { Project } from 'lib/project'
import { markOnce } from 'lib/performance'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { useProjectsLoader } from 'hooks/useProjectsLoader'
import { useProjectsContext } from 'hooks/useProjectsContext'
@ -41,6 +42,7 @@ const Home = () => {
// Cancel all KCL executions while on the home page
useEffect(() => {
markOnce('code/didLoadHome')
kclManager.cancelAllExecutions()
}, [])

72
src/routes/Telemetry.tsx Normal file
View File

@ -0,0 +1,72 @@
import { useLocation, useNavigate } from 'react-router-dom'
import { useHotkeys } from 'react-hotkeys-hook'
import { PATHS } from 'lib/paths'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
import { Fragment } from 'react'
import { Dialog, Transition } from '@headlessui/react'
import { CustomIcon } from 'components/CustomIcon'
import { TelemetryExplorer } from 'components/TelemetryExplorer'
export const Telemetry = () => {
const navigate = useNavigate()
const close = () => navigate(location.pathname.replace(PATHS.TELEMETRY, ''))
const location = useLocation()
const dotDotSlash = useDotDotSlash()
useHotkeys('esc', () => navigate(dotDotSlash()))
return (
<Transition appear show={true} as={Fragment}>
<Dialog
as="div"
open={true}
onClose={close}
className="fixed inset-0 z-40 overflow-y-auto p-4 grid place-items-center"
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-75"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-chalkboard-110/30 dark:bg-chalkboard-110/50" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="ease-out duration-75"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="rounded relative mx-auto bg-chalkboard-10 dark:bg-chalkboard-100 border dark:border-chalkboard-70 max-w-3xl w-full max-h-[66vh] shadow-lg flex flex-col gap-8">
<div className="p-5 pb-0 flex justify-between items-center">
<h1 className="text-2xl font-bold">Telemetry</h1>
<div className="flex gap-4 items-start">
<button
onClick={close}
className="p-0 m-0 focus:ring-0 focus:outline-none border-none hover:bg-destroy-10 focus:bg-destroy-10 dark:hover:bg-destroy-80/50 dark:focus:bg-destroy-80/50"
data-testid="settings-close-button"
>
<CustomIcon name="close" className="w-5 h-5" />
</button>
</div>
</div>
<div
className="flex-1 grid items-stretch pl-4 pr-5 pb-5 gap-2 overflow-scroll"
style={{
gridTemplateColumns: 'auto 1fr',
gridTemplateRows: '1fr',
}}
>
<TelemetryExplorer />
</div>
</Dialog.Panel>
</Transition.Child>
</Dialog>
</Transition>
)
}

View File

@ -737,7 +737,7 @@ dependencies = [
[[package]]
name = "derive-docs"
version = "0.1.29"
version = "0.1.30"
dependencies = [
"Inflector",
"anyhow",
@ -1524,9 +1524,9 @@ dependencies = [
[[package]]
name = "image"
version = "0.25.3"
version = "0.25.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97eb9a8e0cd5b76afea91d7eecd5cf8338cd44ced04256cf1f800474b227c52"
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
dependencies = [
"bytemuck",
"byteorder-lite",
@ -1673,7 +1673,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.23"
version = "0.2.24"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1748,7 +1748,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.15"
version = "0.1.16"
dependencies = [
"anyhow",
"hyper 0.14.30",

View File

@ -24,7 +24,7 @@ wasm-bindgen-futures = "0.4.44"
[dev-dependencies]
anyhow = "1"
image = { version = "0.25.3", default-features = false, features = ["png"] }
image = { version = "0.25.5", default-features = false, features = ["png"] }
kittycad = { workspace = true, default-features = true }
kittycad-modeling-cmds = { workspace = true }
pretty_assertions = "1.4.1"

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.29"
version = "0.1.30"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-test-server"
description = "A test server for KCL"
version = "0.1.15"
version = "0.1.16"
edition = "2021"
license = "MIT"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.23"
version = "0.2.24"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -26,7 +26,7 @@ futures = { version = "0.3.31" }
git_rev = "0.1.0"
gltf-json = "1.4.1"
http = { workspace = true }
image = { version = "0.25.3", default-features = false, features = ["png"] }
image = { version = "0.25.5", default-features = false, features = ["png"] }
indexmap = { version = "2.6.0", features = ["serde"] }
kittycad = { workspace = true }
kittycad-modeling-cmds = { workspace = true }
@ -85,7 +85,7 @@ criterion = { version = "0.5.1", features = ["async_tokio"] }
expectorate = "1.1.0"
handlebars = "6.2.0"
iai = "0.1"
image = { version = "0.25.3", default-features = false, features = ["png"] }
image = { version = "0.25.5", default-features = false, features = ["png"] }
insta = { version = "1.41.1", features = ["json", "filters"] }
itertools = "0.13.0"
pretty_assertions = "1.4.1"

View File

@ -9954,7 +9954,7 @@ yargs@^16.0.2:
y18n "^5.0.5"
yargs-parser "^20.2.2"
yargs@^17.0.1, yargs@^17.6.2:
yargs@^17.0.1, yargs@^17.6.2, yargs@^17.7.2:
version "17.7.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==