Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
2a86ffc09a | |||
93903a8a47 | |||
45e85a1f81 | |||
c187989d18 | |||
47b5fa1459 | |||
d85781ef99 | |||
233f81a879 | |||
8ac0bf4953 | |||
24caeece65 | |||
f493cf11a0 | |||
594e888c12 |
@ -7,23 +7,23 @@ on:
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-criterion.yml
|
||||
- .github/workflows/cargo-bench.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-criterion.yml
|
||||
- .github/workflows/cargo-bench.yml
|
||||
workflow_dispatch:
|
||||
permissions: read-all
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
name: cargo criterion
|
||||
name: cargo bench
|
||||
jobs:
|
||||
cargocriterion:
|
||||
name: cargo criterion
|
||||
cargo-bench:
|
||||
name: Benchmark with iai
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -31,10 +31,12 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cargo install cargo-criterion
|
||||
sudo apt update
|
||||
sudo apt install -y valgrind
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.6.1
|
||||
- name: Benchmark kcl library
|
||||
shell: bash
|
||||
run: |-
|
||||
cd src/wasm-lib/kcl; cargo criterion
|
||||
cd src/wasm-lib/kcl; cargo bench -- iai
|
||||
|
@ -281,7 +281,7 @@ https://github.com/KittyCAD/modeling-app/assets/29681384/6f5e8e85-1003-4fd9-be7f
|
||||
<details>
|
||||
|
||||
<summary>
|
||||
Ps for the debug panel, the following JSON is useful for snapping the camera
|
||||
PS: for the debug panel, the following JSON is useful for snapping the camera
|
||||
</summary>
|
||||
|
||||
```JSON
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
@ -1,7 +1,10 @@
|
||||
import { browser, $, expect } from '@wdio/globals'
|
||||
import fs from 'fs/promises'
|
||||
|
||||
const defaultDir = `${process.env.HOME}/Documents/zoo-modeling-app-projects`
|
||||
const documentsDir = `${process.env.HOME}/Documents`
|
||||
const userSettingsFile = `${process.env.HOME}/.config/dev.zoo.modeling-app/user.toml`
|
||||
const defaultProjectDir = `${documentsDir}/zoo-modeling-app-projects`
|
||||
const newProjectDir = `${documentsDir}/a-different-directory`
|
||||
const userCodeDir = '/tmp/kittycad_user_code'
|
||||
|
||||
async function click(element: WebdriverIO.Element): Promise<void> {
|
||||
@ -10,12 +13,25 @@ async function click(element: WebdriverIO.Element): Promise<void> {
|
||||
await browser.execute('arguments[0].click();', element)
|
||||
}
|
||||
|
||||
/* Shoutout to @Sheap on Github for a great workaround utility:
|
||||
* https://github.com/tauri-apps/tauri/issues/6541#issue-1638944060
|
||||
*/
|
||||
async function setDatasetValue(
|
||||
field: WebdriverIO.Element,
|
||||
property: string,
|
||||
value: string
|
||||
) {
|
||||
await browser.execute(`arguments[0].dataset.${property} = "${value}"`, field)
|
||||
}
|
||||
|
||||
describe('ZMA (Tauri, Linux)', () => {
|
||||
it('opens the auth page and signs in', async () => {
|
||||
// Clean up filesystem from previous tests
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
await fs.rm(defaultDir, { force: true, recursive: true })
|
||||
await fs.rm(defaultProjectDir, { force: true, recursive: true })
|
||||
await fs.rm(userCodeDir, { force: true })
|
||||
await fs.rm(userSettingsFile, { force: true })
|
||||
await fs.mkdir(newProjectDir, { recursive: true })
|
||||
|
||||
const signInButton = await $('[data-testid="sign-in-button"]')
|
||||
expect(await signInButton.getText()).toEqual('Sign in')
|
||||
@ -65,8 +81,20 @@ describe('ZMA (Tauri, Linux)', () => {
|
||||
const settingsButton = await $('[data-testid="settings-button"]')
|
||||
await click(settingsButton)
|
||||
|
||||
const defaultDirInput = await $('[data-testid="default-directory-input"]')
|
||||
expect(await defaultDirInput.getValue()).toEqual(defaultDir)
|
||||
const projectDirInput = await $('[data-testid="project-directory-input"]')
|
||||
expect(await projectDirInput.getValue()).toEqual(defaultProjectDir)
|
||||
|
||||
/*
|
||||
* We've set up the project directory input (in initialSettings.tsx)
|
||||
* to be able to skip the folder selection dialog if data-testValue
|
||||
* has a value, allowing us to test the input otherwise works.
|
||||
*/
|
||||
await setDatasetValue(projectDirInput, 'testValue', newProjectDir)
|
||||
const projectDirButton = await $('[data-testid="project-directory-button"]')
|
||||
await click(projectDirButton)
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
// This line is broken. I need a different way to grab the toast
|
||||
await expect(await $('div*=Set project directory to')).toBeDisplayed()
|
||||
|
||||
const nameInput = await $('[data-testid="projects-defaultProjectName"]')
|
||||
expect(await nameInput.getValue()).toEqual('project-$nnn')
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.17.2",
|
||||
"version": "0.17.3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.15.0",
|
||||
|
@ -7,7 +7,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "zoo-modeling-app",
|
||||
"version": "0.17.2"
|
||||
"version": "0.17.3"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
@ -153,7 +153,7 @@ export function App() {
|
||||
<ModalContainer />
|
||||
<Resizable
|
||||
className={
|
||||
'pointer-events-none h-full flex flex-col flex-1 z-10 my-5 ml-5 pr-1 transition-opacity transition-duration-75 ' +
|
||||
'pointer-events-none h-full flex flex-col flex-1 z-10 my-2 ml-2 pr-1 transition-opacity transition-duration-75 ' +
|
||||
+paneOpacity
|
||||
}
|
||||
defaultSize={{
|
||||
@ -166,7 +166,7 @@ export function App() {
|
||||
maxHeight={'auto'}
|
||||
handleClasses={{
|
||||
right:
|
||||
'hover:bg-chalkboard-10/50 bg-transparent transition-colors duration-75 transition-ease-out delay-100 ' +
|
||||
'hover:bg-chalkboard-10 hover:dark:bg-chalkboard-110 bg-transparent transition-colors duration-75 transition-ease-out delay-100 ' +
|
||||
(buttonDownInStream || onboardingStatus.current === 'camera'
|
||||
? 'pointer-events-none '
|
||||
: 'pointer-events-auto'),
|
||||
@ -202,7 +202,7 @@ export function App() {
|
||||
theme={editorTheme}
|
||||
open={openPanes.includes('kclErrors')}
|
||||
title="KCL Errors"
|
||||
iconClassNames={{ icon: 'group-open:text-destroy-30' }}
|
||||
iconClassNames={{ bg: 'group-open:bg-destroy-70' }}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -66,10 +66,10 @@ const router = createBrowserRouter([
|
||||
<Outlet />
|
||||
<App />
|
||||
<CommandBar />
|
||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||
</ModelingMachineProvider>
|
||||
<WasmErrBanner />
|
||||
</FileMachineProvider>
|
||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||
</Auth>
|
||||
),
|
||||
children: [
|
||||
|
@ -18,8 +18,12 @@ export const Toolbar = () => {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
|
||||
const iconClassName =
|
||||
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-chalkboard-10 group-pressed:!text-chalkboard-10'
|
||||
const bgClassName =
|
||||
'group-enabled:group-hover:bg-energy-10 group-pressed:bg-energy-10 dark:group-enabled:group-hover:bg-chalkboard-80 dark:group-pressed:bg-chalkboard-80'
|
||||
'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary group-pressed:bg-primary'
|
||||
const buttonClassName =
|
||||
'bg-chalkboard-10 dark:bg-chalkboard-100 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100'
|
||||
const pathId = useMemo(() => {
|
||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
|
||||
return false
|
||||
@ -64,12 +68,14 @@ export const Toolbar = () => {
|
||||
{state.nextEvents.includes('Enter sketch') && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
|
||||
}
|
||||
icon={{
|
||||
icon: 'sketch',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
@ -81,10 +87,12 @@ export const Toolbar = () => {
|
||||
{state.nextEvents.includes('Enter sketch') && pathId && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() => send({ type: 'Enter sketch' })}
|
||||
icon={{
|
||||
icon: 'sketch',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
@ -96,10 +104,12 @@ export const Toolbar = () => {
|
||||
{state.nextEvents.includes('Cancel') && !state.matches('idle') && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() => send({ type: 'Cancel' })}
|
||||
icon={{
|
||||
icon: 'arrowLeft',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
@ -112,6 +122,7 @@ export const Toolbar = () => {
|
||||
<>
|
||||
<li className="contents" key="line-button">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
state?.matches('Sketch.Line tool')
|
||||
@ -119,9 +130,9 @@ export const Toolbar = () => {
|
||||
: send('Equip Line tool')
|
||||
}
|
||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
||||
icon={{
|
||||
icon: 'line',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={disableAllButtons}
|
||||
@ -131,6 +142,7 @@ export const Toolbar = () => {
|
||||
</li>
|
||||
<li className="contents" key="tangential-arc-button">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
state.matches('Sketch.Tangential arc to')
|
||||
@ -138,9 +150,9 @@ export const Toolbar = () => {
|
||||
: send('Equip tangential arc to')
|
||||
}
|
||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||
className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80"
|
||||
icon={{
|
||||
icon: 'arc',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
disabled={
|
||||
@ -179,8 +191,8 @@ export const Toolbar = () => {
|
||||
.map((eventName) => (
|
||||
<li className="contents" key={eventName}>
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
className="text-sm"
|
||||
key={eventName}
|
||||
onClick={() => send(eventName)}
|
||||
disabled={
|
||||
@ -191,6 +203,7 @@ export const Toolbar = () => {
|
||||
title={eventName}
|
||||
icon={{
|
||||
icon: 'line',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
>
|
||||
@ -203,8 +216,8 @@ export const Toolbar = () => {
|
||||
{state.matches('idle') && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
className="text-sm"
|
||||
onClick={() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
@ -219,6 +232,7 @@ export const Toolbar = () => {
|
||||
}
|
||||
icon={{
|
||||
icon: 'extrude',
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
>
|
||||
@ -231,14 +245,14 @@ export const Toolbar = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-full flex items-stretch rounded-l-sm rounded-r-full bg-chalkboard-10 dark:bg-chalkboard-100 relative">
|
||||
<menu className="flex-1 pl-1 pr-2 py-0 overflow-hidden rounded-l-sm whitespace-nowrap bg-chalkboard-10 dark:bg-chalkboard-100 border-solid border border-energy-10 dark:border-chalkboard-90 border-r-0">
|
||||
<div className="max-w-full flex items-stretch rounded-l-sm rounded-r-full bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative">
|
||||
<menu className="flex-1 pl-1 pr-2 py-0 overflow-hidden rounded-l-sm whitespace-nowrap border-solid border border-primary/30 dark:border-chalkboard-90 border-r-0">
|
||||
<ToolbarButtons />
|
||||
</menu>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => commandBarSend({ type: 'Open' })}
|
||||
className="rounded-r-full pr-4 self-stretch border-energy-10 hover:border-energy-10 dark:border-chalkboard-80 bg-energy-10/50 hover:bg-energy-10 dark:bg-chalkboard-80 dark:text-energy-10"
|
||||
className="rounded-r-full pr-4 self-stretch border-primary/30 hover:border-primary dark:border-chalkboard-80 dark:bg-chalkboard-80 text-primary"
|
||||
>
|
||||
{platform === 'darwin' ? '⌘K' : 'Ctrl+/'}
|
||||
</ActionButton>
|
||||
|
@ -1,11 +1,21 @@
|
||||
:root {
|
||||
--primary-hue: 264.48;
|
||||
--primary-chroma: 0.2167;
|
||||
--primary-lightness: 60%;
|
||||
--_primary: var(--primary-lightness) var(--primary-chroma)
|
||||
var(--primary-hue, 264.48);
|
||||
--primary: oklch(
|
||||
var(--primary-lightness) var(--primary-chroma) var(--primary-hue, 264.48) /
|
||||
var(--opacity, 1)
|
||||
);
|
||||
|
||||
/*
|
||||
Generated using Catmosphere Theme Builder
|
||||
by KittyCAD
|
||||
https://catmosphere-theme-builder.vercel.app/?colors=%5B%7B%22from%22:%7B%22l%22:1,%22c%22:0.01,%22h%22:78%7D,%22to%22:%7B%22l%22:0.065,%22c%22:0.05,%22h%22:182.6%7D,%22stops%22:10,%22steps%22:12%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.45,%22h%22:122.4%7D,%22to%22:%7B%22l%22:0.13,%22c%22:0.031,%22h%22:137.2%7D,%22stops%22:10,%22steps%22:12%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.13,%22h%22:176%7D,%22to%22:%7B%22l%22:0.116,%22c%22:0.097,%22h%22:213.1%7D,%22stops%22:10,%22steps%22:12%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.169,%22h%22:144.4%7D,%22to%22:%7B%22l%22:0.12,%22c%22:0.45,%22h%22:132.7%7D,%22steps%22:12%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.087,%22h%22:261.6%7D,%22to%22:%7B%22l%22:0.22,%22c%22:0.084,%22h%22:275.5%7D,%22steps%22:12,%22uuid%22:%227tpx9pf1zd6%22%7D,%7B%22from%22:%7B%22l%22:0.954,%22c%22:0.108,%22h%22:280.6%7D,%22to%22:%7B%22l%22:0.166,%22c%22:0.188,%22h%22:263.8%7D,%22steps%22:12,%22uuid%22:%22vu652mebd3%22%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.115,%22h%22:0%7D,%22to%22:%7B%22l%22:0.096,%22c%22:0.261,%22h%22:302%7D,%22steps%22:12%7D,%7B%22from%22:%7B%22l%22:1,%22c%22:0.185,%22h%22:19.8%7D,%22to%22:%7B%22l%22:0.368,%22c%22:0.45,%22h%22:9.4%7D,%22steps%22:8,%22uuid%22:%22g05inkd34l%22%7D,%7B%22from%22:%7B%22l%22:0.912,%22c%22:0.139,%22h%22:87%7D,%22to%22:%7B%22l%22:0.502,%22c%22:0.45,%22h%22:97.7%7D,%22steps%22:8,%22uuid%22:%22l892hcw4ef%22%7D,%7B%22from%22:%7B%22l%22:0.89,%22c%22:0.16,%22h%22:143.4%7D,%22to%22:%7B%22l%22:0.466,%22c%22:0.208,%22h%22:147.7%7D,%22steps%22:8,%22uuid%22:%22hkd09y9ov4h%22%7D%5D
|
||||
*/
|
||||
/* Chalkboard */
|
||||
--chalkboard-10: oklch(99.7% 0.008766 102.8deg);
|
||||
--chalkboard-10: oklch(99.9% 0.003766 102.8deg);
|
||||
--chalkboard-20: oklch(91.34% 0.009353 109deg);
|
||||
--chalkboard-30: oklch(82.99% 0.00994 115.2deg);
|
||||
--chalkboard-40: oklch(74.63% 0.01053 121.4deg);
|
||||
|
@ -30,9 +30,9 @@ export const ActionIcon = ({
|
||||
children,
|
||||
}: ActionIconProps) => {
|
||||
// By default, we reverse the icon color and background color in dark mode
|
||||
const computedIconClassName = `h-auto dark:text-energy-10 !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
|
||||
const computedIconClassName = `h-auto text-primary dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
|
||||
|
||||
const computedBgClassName = `bg-chalkboard-20 dark:bg-chalkboard-90 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
|
||||
const computedBgClassName = `bg-primary/10 dark:bg-chalkboard-90 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -33,7 +33,7 @@ export const AppHeader = ({
|
||||
className={
|
||||
'w-full grid ' +
|
||||
styles.header +
|
||||
' overlaid-panes sticky top-0 z-20 py-1 px-2 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
|
||||
' overlaid-panes sticky top-0 z-20 px-2 items-center ' +
|
||||
className
|
||||
}
|
||||
>
|
||||
@ -53,13 +53,13 @@ export const AppHeader = ({
|
||||
className="text-sm self-center flex items-center w-fit gap-3"
|
||||
>
|
||||
Command Palette{' '}
|
||||
<kbd className="bg-energy-10/50 dark:bg-chalkboard-100 dark:text-energy-10 inline-block px-1 py-0.5 border-energy-10 dark:border-chalkboard-90">
|
||||
<kbd className="bg-primary/10 dark:bg-chalkboard-100 dark:text-primary inline-block px-1 py-0.5 border-primary dark:border-chalkboard-90">
|
||||
{platform === 'darwin' ? '⌘K' : 'Ctrl+/'}
|
||||
</kbd>
|
||||
</ActionButton>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 ml-auto">
|
||||
<div className="flex items-center gap-1 py-1 ml-auto">
|
||||
{/* If there are children, show them, otherwise show User menu */}
|
||||
{children || (
|
||||
<>
|
||||
|
@ -1,13 +1,13 @@
|
||||
.button {
|
||||
@apply flex justify-between items-center gap-2 px-2 py-1 text-left border-none rounded-sm;
|
||||
@apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90;
|
||||
@apply ui-active:bg-energy-10/50 ui-active:text-inherit;
|
||||
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
|
||||
@apply transition-colors ease-out;
|
||||
}
|
||||
|
||||
:global(.dark) .button {
|
||||
@apply text-chalkboard-30;
|
||||
@apply ui-active:bg-chalkboard-80 ui-active:text-energy-10;
|
||||
@apply !text-chalkboard-30;
|
||||
@apply ui-active:bg-chalkboard-90;
|
||||
}
|
||||
|
||||
.button small {
|
||||
|
@ -30,12 +30,12 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||
className="p-1"
|
||||
size="sm"
|
||||
bgClassName={
|
||||
'bg-chalkboard-20 dark:bg-chalkboard-110 hover:bg-energy-10/50 hover:dark:bg-chalkboard-90 ui-active:bg-chalkboard-80 ui-active:dark:bg-chalkboard-90 rounded-sm'
|
||||
'!bg-transparent hover:!bg-primary/10 hover:dark:!bg-chalkboard-100 ui-active:!bg-primary/10 dark:ui-active:!bg-chalkboard-100 rounded-sm'
|
||||
}
|
||||
iconClassName={'text-chalkboard-90 dark:text-chalkboard-40'}
|
||||
iconClassName={'!text-chalkboard-90 dark:!text-chalkboard-40'}
|
||||
/>
|
||||
</Menu.Button>
|
||||
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
|
||||
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-100 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
|
||||
<Menu.Item>
|
||||
<button
|
||||
onClick={() => kclManager.format()}
|
||||
|
@ -3,6 +3,11 @@
|
||||
@apply bg-chalkboard-10/70 backdrop-blur-sm;
|
||||
}
|
||||
|
||||
.header::before,
|
||||
.header::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:global(.dark) .panel {
|
||||
@apply bg-chalkboard-110/50 backdrop-blur-0;
|
||||
}
|
||||
@ -11,7 +16,7 @@
|
||||
@apply sticky top-0 z-10 cursor-pointer;
|
||||
@apply flex items-center justify-between gap-2 w-full p-2;
|
||||
@apply font-mono text-xs font-bold select-none text-chalkboard-90;
|
||||
@apply bg-chalkboard-20;
|
||||
@apply bg-chalkboard-10;
|
||||
}
|
||||
|
||||
.header:not(:last-of-type) {
|
||||
|
@ -30,11 +30,11 @@ export const PanelHeader = ({
|
||||
className="p-1"
|
||||
size="sm"
|
||||
bgClassName={
|
||||
'dark:!bg-chalkboard-100 group-open:bg-chalkboard-80 dark:group-open:!bg-chalkboard-90 border border-transparent dark:group-open:border-chalkboard-60 rounded-sm ' +
|
||||
'dark:!bg-transparent group-open:bg-primary dark:group-open:!bg-primary rounded-sm ' +
|
||||
(iconClassNames?.bg || '')
|
||||
}
|
||||
iconClassName={
|
||||
'group-open:text-energy-10 ' + (iconClassNames?.icon || '')
|
||||
'group-open:text-chalkboard-10 ' + (iconClassNames?.icon || '')
|
||||
}
|
||||
/>
|
||||
{title}
|
||||
|
@ -141,7 +141,7 @@ function CommandArgOptionInput({
|
||||
<Combobox.Option
|
||||
key={option.name}
|
||||
value={option}
|
||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-energy-10/50 dark:ui-active:bg-chalkboard-90"
|
||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
>
|
||||
<p className="flex-grow">{option.name} </p>
|
||||
{option.value === currentOption?.value && (
|
||||
|
@ -104,7 +104,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
key={argName}
|
||||
className={`relative w-fit px-2 py-1 rounded-sm flex gap-2 items-center border ${
|
||||
argName === currentArgument?.name
|
||||
? 'disabled:bg-energy-10/50 dark:disabled:bg-energy-10/20 disabled:border-energy-10 dark:disabled:border-energy-10 disabled:text-chalkboard-100 dark:disabled:text-chalkboard-10'
|
||||
? 'disabled:bg-primary/10 dark:disabled:bg-primary/20 disabled:border-primary dark:disabled:border-primary disabled:text-chalkboard-100 dark:disabled:text-chalkboard-10'
|
||||
: 'bg-chalkboard-20/50 dark:bg-chalkboard-80/50 border-chalkboard-20 dark:border-chalkboard-80'
|
||||
}`}
|
||||
>
|
||||
@ -129,7 +129,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
)
|
||||
) : null}
|
||||
{showShortcuts && (
|
||||
<small className="absolute -top-[1px] right-full translate-x-1/2 px-0.5 rounded-sm bg-chalkboard-80 text-chalkboard-10 dark:bg-energy-10 dark:text-chalkboard-100">
|
||||
<small className="absolute -top-[1px] right-full translate-x-1/2 px-0.5 rounded-sm bg-chalkboard-80 text-chalkboard-10 dark:bg-primary dark:text-chalkboard-100">
|
||||
<span className="sr-only">Hotkey: </span>
|
||||
{i + 1}
|
||||
</small>
|
||||
@ -174,12 +174,11 @@ function ReviewingButton() {
|
||||
autoFocus
|
||||
type="submit"
|
||||
form="review-form"
|
||||
className="w-fit !p-0 rounded-sm border !border-chalkboard-100 dark:!border-energy-10 hover:shadow"
|
||||
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
||||
icon={{
|
||||
icon: 'checkmark',
|
||||
bgClassName:
|
||||
'p-1 rounded-sm !bg-chalkboard-100 hover:!bg-chalkboard-110 dark:!bg-energy-20 dark:hover:!bg-energy-10',
|
||||
iconClassName: '!text-energy-10 dark:!text-chalkboard-100',
|
||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||
iconClassName: '!text-chalkboard-10',
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Submit command</span>
|
||||
@ -193,10 +192,11 @@ function GatheringArgsButton() {
|
||||
Element="button"
|
||||
type="submit"
|
||||
form="arg-form"
|
||||
className="w-fit !p-0 rounded-sm"
|
||||
className="w-fit !p-0 rounded-sm border !border-primary hover:shadow"
|
||||
icon={{
|
||||
icon: 'arrowRight',
|
||||
bgClassName: 'p-1 rounded-sm',
|
||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||
iconClassName: '!text-chalkboard-10',
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Continue</span>
|
||||
|
@ -9,9 +9,9 @@
|
||||
.editor :global(.cm-line)::selection {
|
||||
@apply px-1;
|
||||
@apply text-chalkboard-100;
|
||||
@apply bg-energy-10/50;
|
||||
@apply bg-primary/40;
|
||||
}
|
||||
:global(.dark) .editor :global(.cm-line)::selection {
|
||||
@apply text-energy-10;
|
||||
@apply bg-energy-10/20;
|
||||
@apply text-chalkboard-10;
|
||||
@apply bg-primary/40;
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ function CommandBarKclInput({
|
||||
className={
|
||||
calcResult === 'NAN'
|
||||
? 'text-destroy-80 dark:text-destroy-40'
|
||||
: 'text-energy-60 dark:text-energy-20'
|
||||
: 'text-succeed-80 dark:text-succeed-40'
|
||||
}
|
||||
>
|
||||
{calcResult === 'NAN'
|
||||
@ -173,7 +173,7 @@ function CommandBarKclInput({
|
||||
type="text"
|
||||
id="variable-name"
|
||||
name="variable-name"
|
||||
className="flex-1 border-none bg-transparent"
|
||||
className="flex-1 border-none bg-transparent focus:outline-none"
|
||||
placeholder="Variable name"
|
||||
value={newVariableName}
|
||||
autoCapitalize="off"
|
||||
@ -196,7 +196,7 @@ function CommandBarKclInput({
|
||||
<span
|
||||
className={
|
||||
isNewVariableNameUnique
|
||||
? 'text-energy-60 dark:text-energy-20'
|
||||
? 'text-succeed-60 dark:text-succeed-40'
|
||||
: 'text-destroy-60 dark:text-destroy-40'
|
||||
}
|
||||
>
|
||||
|
@ -38,11 +38,11 @@ function CommandComboBox({
|
||||
<div className="flex items-center gap-2 px-4 pb-2 border-solid border-0 border-b border-b-chalkboard-20 dark:border-b-chalkboard-80">
|
||||
<CustomIcon
|
||||
name="search"
|
||||
className="w-5 h-5 bg-energy-10/50 dark:bg-chalkboard-90 dark:text-energy-10"
|
||||
className="w-5 h-5 bg-primary/10 text-primary"
|
||||
/>
|
||||
<Combobox.Input
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
className="w-full bg-transparent focus:outline-none selection:bg-energy-10/50 dark:selection:bg-energy-10/20 dark:focus:outline-none"
|
||||
className="w-full bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
(event.metaKey && event.key === 'k') ||
|
||||
@ -72,13 +72,10 @@ function CommandComboBox({
|
||||
<Combobox.Option
|
||||
key={option.name}
|
||||
value={option}
|
||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-energy-10/50 dark:ui-active:bg-chalkboard-90"
|
||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
>
|
||||
{'icon' in option && option.icon && (
|
||||
<CustomIcon
|
||||
name={option.icon}
|
||||
className="w-5 h-5 dark:text-energy-10"
|
||||
/>
|
||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||
)}
|
||||
<p className="flex-grow">{option.displayName || option.name} </p>
|
||||
{option.description && (
|
||||
|
@ -1,54 +1,8 @@
|
||||
export type CustomIconName =
|
||||
| 'arc'
|
||||
| 'arrowDown'
|
||||
| 'arrowLeft'
|
||||
| 'arrowRight'
|
||||
| 'arrowUp'
|
||||
| 'checkmark'
|
||||
| 'clipboardPlus'
|
||||
| 'clipboardCheckmark'
|
||||
| 'close'
|
||||
| 'equal'
|
||||
| 'exportFile'
|
||||
| 'extrude'
|
||||
| 'file'
|
||||
| 'filePlus'
|
||||
| 'folder'
|
||||
| 'folderPlus'
|
||||
| 'gear'
|
||||
| 'horizontal'
|
||||
| 'horizontalDash'
|
||||
| 'kcl'
|
||||
| 'line'
|
||||
| 'make-variable'
|
||||
| 'move'
|
||||
| 'network'
|
||||
| 'networkCrossedOut'
|
||||
| 'parallel'
|
||||
| 'person'
|
||||
| 'plus'
|
||||
| 'refresh'
|
||||
| 'search'
|
||||
| 'settings'
|
||||
| 'sketch'
|
||||
| 'three-dots'
|
||||
| 'vertical'
|
||||
import { cloneElement } from 'react'
|
||||
|
||||
export const CustomIcon = ({
|
||||
name,
|
||||
...props
|
||||
}: {
|
||||
name: CustomIconName
|
||||
} & React.SVGProps<SVGSVGElement>) => {
|
||||
switch (name) {
|
||||
case 'arc':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
const CustomIconMap = {
|
||||
arc: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -56,15 +10,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'arrowDown':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
arrowDown: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -72,15 +20,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'arrowLeft':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
arrowLeft: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -88,15 +30,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'arrowRight':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
arrowRight: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -104,15 +40,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'arrowUp':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
arrowUp: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -120,15 +50,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'checkmark':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
checkmark: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -136,15 +60,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'clipboardCheckmark':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
clipboardCheckmark: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -152,15 +70,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'clipboardPlus':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
clipboardPlus: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -168,15 +80,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'close':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
close: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -184,29 +90,17 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'equal':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
equal: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5 8.78V7H14.52V8.78H5ZM5 13.02V11.24H14.52V13.02H5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'exportFile':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
exportFile: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -214,15 +108,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'extrude':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
extrude: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -230,15 +118,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'file':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
file: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -246,15 +128,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'filePlus':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
filePlus: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -262,15 +138,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'folder':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
folder: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -278,15 +148,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'folderPlus':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
folderPlus: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -294,15 +158,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'gear':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
gear: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -310,15 +168,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'horizontal':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
horizontal: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -326,15 +178,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'horizontalDash':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
horizontalDash: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -342,15 +188,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'kcl':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
kcl: (
|
||||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -358,15 +198,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'line':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
line: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -374,15 +208,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'make-variable':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
'make-variable': (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -390,15 +218,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'move':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
move: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -406,15 +228,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'network':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
network: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -422,15 +238,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'networkCrossedOut':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
networkCrossedOut: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -438,15 +248,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'parallel':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
parallel: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -454,15 +258,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'person':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
person: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -470,15 +268,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'plus':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
plus: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -486,15 +278,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'refresh':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
refresh: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -502,15 +288,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'search':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
search: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -518,15 +298,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'settings':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
settings: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -534,15 +308,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'sketch':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
sketch: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -550,15 +318,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'three-dots':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
'three-dots': (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -566,15 +328,9 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
case 'vertical':
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
),
|
||||
vertical: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -582,6 +338,13 @@ export const CustomIcon = ({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
} as const
|
||||
|
||||
export type CustomIconName = keyof typeof CustomIconMap
|
||||
|
||||
export const CustomIcon = ({
|
||||
name,
|
||||
...props
|
||||
}: { name: CustomIconName } & React.SVGProps<SVGSVGElement>) =>
|
||||
cloneElement(CustomIconMap[name], props)
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { Dialog } from '@headlessui/react'
|
||||
import { useStore } from '../useStore'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useState } from 'react'
|
||||
|
||||
const DownloadAppBanner = () => {
|
||||
const { isBannerDismissed, setBannerDismissed } = useStore((s) => ({
|
||||
isBannerDismissed: s.isBannerDismissed,
|
||||
setBannerDismissed: s.setBannerDismissed,
|
||||
}))
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const [isBannerDismissed, setIsBannerDismissed] = useState(
|
||||
settings.context.app.dismissWebBanner.current
|
||||
)
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@ -23,7 +24,7 @@ const DownloadAppBanner = () => {
|
||||
</h2>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => setBannerDismissed(true)}
|
||||
onClick={() => setIsBannerDismissed(true)}
|
||||
icon={{
|
||||
icon: 'close',
|
||||
className: 'p-1',
|
||||
@ -51,6 +52,24 @@ const DownloadAppBanner = () => {
|
||||
</a>{' '}
|
||||
to download the app for the best experience.
|
||||
</p>
|
||||
<p className="mt-6">
|
||||
If you're on Linux and the browser is your only way to use the app,
|
||||
you can permanently dismiss this banner by{' '}
|
||||
<a
|
||||
onClick={() => {
|
||||
setIsBannerDismissed(true)
|
||||
settings.send({
|
||||
type: 'set.app.dismissWebBanner',
|
||||
data: { level: 'user', value: true },
|
||||
})
|
||||
}}
|
||||
href="/"
|
||||
className="!text-warn-80 dark:!text-warn-80 dark:hover:!text-warn-70 underline"
|
||||
>
|
||||
toggling the App > Dismiss Web Banner setting
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
|
@ -192,8 +192,8 @@ const FileTreeItem = ({
|
||||
{fileOrDir.children === undefined ? (
|
||||
<li
|
||||
className={
|
||||
'group m-0 p-0 border-solid border-0 text-energy-100 hover:text-energy-70 hover:bg-energy-10/50 dark:text-energy-30 dark:hover:!text-energy-20 dark:hover:bg-energy-90/50 focus-within:bg-energy-10/80 dark:focus-within:bg-energy-80/50 hover:focus-within:bg-energy-10/80 dark:hover:focus-within:bg-energy-80/50 ' +
|
||||
(isCurrentFile ? 'bg-energy-10/50 dark:bg-energy-90/50' : '')
|
||||
'group m-0 p-0 border-solid border-0 hover:text-primary hover:bg-primary/5 focus-within:bg-primary/5 ' +
|
||||
(isCurrentFile ? '!bg-primary/10 !text-primary' : '')
|
||||
}
|
||||
>
|
||||
{!isRenaming ? (
|
||||
@ -206,12 +206,7 @@ const FileTreeItem = ({
|
||||
>
|
||||
<CustomIcon
|
||||
name={fileOrDir.name?.endsWith(FILE_EXT) ? 'kcl' : 'file'}
|
||||
className={
|
||||
'inline-block w-3 ' +
|
||||
(isCurrentFile
|
||||
? 'text-energy-90 dark:text-energy-10'
|
||||
: 'text-energy-50 dark:text-energy-50')
|
||||
}
|
||||
className="inline-block w-3 text-current"
|
||||
/>
|
||||
{fileOrDir.name}
|
||||
</button>
|
||||
@ -230,9 +225,9 @@ const FileTreeItem = ({
|
||||
{!isRenaming ? (
|
||||
<Disclosure.Button
|
||||
className={
|
||||
' group border-none text-sm rounded-none p-0 m-0 flex items-center justify-start w-full py-0.5 text-chalkboard-70 dark:text-chalkboard-30 hover:bg-energy-10/50 dark:hover:bg-energy-90/50' +
|
||||
' group border-none text-sm rounded-none p-0 m-0 flex items-center justify-start w-full py-0.5 hover:text-primary hover:bg-primary/5' +
|
||||
(context.selectedDirectory.path.includes(fileOrDir.path)
|
||||
? ' group-focus-within:bg-chalkboard-20/50 dark:group-focus-within:bg-chalkboard-80/20 hover:group-focus-within:bg-chalkboard-20 dark:hover:group-focus-within:bg-chalkboard-80/20 group-active:bg-chalkboard-20/50 dark:group-active:bg-chalkboard-80/20 hover:group-active:bg-chalkboard-20/50 dark:hover:group-active:bg-chalkboard-80/20'
|
||||
? ' ui-open:text-primary'
|
||||
: '')
|
||||
}
|
||||
style={{ paddingInlineStart: getIndentationCSS(level) }}
|
||||
@ -353,17 +348,16 @@ export const FileTree = ({
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="flex items-center gap-1 px-4 py-1 bg-chalkboard-20/50 dark:bg-chalkboard-80/50 border-b border-b-chalkboard-30 dark:border-b-chalkboard-80">
|
||||
<div className="flex items-center gap-1 px-4 py-1 bg-chalkboard-20/40 dark:bg-chalkboard-80/50 border-b border-b-chalkboard-30 dark:border-b-chalkboard-80">
|
||||
<h2 className="flex-1 m-0 p-0 text-sm mono">Files</h2>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{
|
||||
icon: 'filePlus',
|
||||
iconClassName: '!text-energy-80 dark:!text-energy-20',
|
||||
bgClassName:
|
||||
'bg-chalkboard-20/50 hover:bg-energy-10/50 dark:hover:bg-transparent',
|
||||
iconClassName: '!text-current',
|
||||
bgClassName: 'bg-transparent',
|
||||
}}
|
||||
className="!p-0 bg-transparent !outline-none"
|
||||
className="!p-0 !bg-transparent hover:text-primary border-transparent hover:border-primary !outline-none"
|
||||
onClick={createFile}
|
||||
>
|
||||
<Tooltip position="inlineStart" delay={750}>
|
||||
@ -375,11 +369,10 @@ export const FileTree = ({
|
||||
Element="button"
|
||||
icon={{
|
||||
icon: 'folderPlus',
|
||||
iconClassName: '!text-energy-80 dark:!text-energy-20',
|
||||
bgClassName:
|
||||
'bg-chalkboard-20/50 hover:bg-energy-10/50 dark:hover:bg-transparent',
|
||||
iconClassName: '!text-current',
|
||||
bgClassName: 'bg-transparent',
|
||||
}}
|
||||
className="!p-0 bg-transparent !outline-none"
|
||||
className="!p-0 !bg-transparent hover:text-primary border-transparent hover:border-primary !outline-none"
|
||||
onClick={createFolder}
|
||||
>
|
||||
<Tooltip position="inlineStart" delay={750}>
|
||||
|
@ -15,23 +15,20 @@ const Loading = ({ children }: React.PropsWithChildren) => {
|
||||
data-testid="loading"
|
||||
>
|
||||
<svg viewBox="0 0 10 10" className="w-8 h-8">
|
||||
<circle cx="5" cy="5" r="4" stroke="var(--energy-50)" fill="none" />
|
||||
<circle
|
||||
cx="5"
|
||||
cy="5"
|
||||
r="4"
|
||||
stroke="var(--energy-10)"
|
||||
stroke="var(--primary)"
|
||||
fill="none"
|
||||
strokeDasharray="4, 4"
|
||||
className="animate-spin origin-center"
|
||||
/>
|
||||
</svg>
|
||||
<p className="text-base mt-4 text-energy-80 dark:text-energy-30">
|
||||
{children || 'Loading'}
|
||||
</p>
|
||||
<p className="text-base mt-4 text-primary">{children || 'Loading'}</p>
|
||||
<p
|
||||
className={
|
||||
'text-sm mt-4 text-energy-70 dark:text-energy-50 transition-opacity duration-500' +
|
||||
'text-sm mt-4 text-primary/60 transition-opacity duration-500' +
|
||||
(hasLongLoadTime ? ' opacity-100' : ' opacity-0')
|
||||
}
|
||||
>
|
||||
|
@ -50,7 +50,7 @@ const hasIssueToIconColors: Record<string | number | symbol, IconColorConfig> =
|
||||
bg: 'bg-chalkboard-30 dark:bg-chalkboard-70',
|
||||
},
|
||||
false: {
|
||||
icon: 'text-chalkboard-110 dark:!text-chalkboard-10',
|
||||
icon: '!text-chalkboard-110 dark:!text-chalkboard-10',
|
||||
bg: 'bg-transparent dark:bg-transparent',
|
||||
},
|
||||
}
|
||||
@ -58,8 +58,8 @@ const hasIssueToIconColors: Record<string | number | symbol, IconColorConfig> =
|
||||
const overallConnectionStateColor: Record<NetworkHealthState, IconColorConfig> =
|
||||
{
|
||||
[NetworkHealthState.Ok]: {
|
||||
icon: 'text-energy-80 dark:text-energy-10',
|
||||
bg: 'bg-energy-10/30 dark:bg-energy-80/50',
|
||||
icon: 'text-succeed-80 dark:text-succeed-10',
|
||||
bg: 'bg-succeed-10/30 dark:bg-succeed-80/50',
|
||||
},
|
||||
[NetworkHealthState.Issue]: {
|
||||
icon: 'text-destroy-80 dark:text-destroy-10',
|
||||
@ -214,7 +214,7 @@ export const NetworkHealthIndicator = () => {
|
||||
'p-0 border-none bg-transparent dark:bg-transparent relative ' +
|
||||
(hasIssues
|
||||
? 'focus-visible:outline-destroy-80'
|
||||
: 'focus-visible:outline-energy-80')
|
||||
: 'focus-visible:outline-succeed-80')
|
||||
}
|
||||
data-testid="network-toggle"
|
||||
>
|
||||
@ -227,7 +227,7 @@ export const NetworkHealthIndicator = () => {
|
||||
'rounded-sm ' + overallConnectionStateColor[overallState].bg
|
||||
}
|
||||
/>
|
||||
<Tooltip position="blockEnd" delay={750} className="ui-open:hidden">
|
||||
<Tooltip position="left" delay={750} className="ui-open:hidden">
|
||||
Network Health ({NETWORK_HEALTH_TEXT[overallState]})
|
||||
</Tooltip>
|
||||
</Popover.Button>
|
||||
|
@ -13,6 +13,7 @@ import { getPartsCount, readProject } from '../lib/tauriFS'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
import { Dialog } from '@headlessui/react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import Tooltip from './Tooltip'
|
||||
|
||||
function ProjectCard({
|
||||
project,
|
||||
@ -64,17 +65,17 @@ function ProjectCard({
|
||||
inputRef.current.focus()
|
||||
inputRef.current.select()
|
||||
}
|
||||
}, [inputRef])
|
||||
}, [inputRef.current])
|
||||
|
||||
return (
|
||||
<li
|
||||
{...props}
|
||||
className="group relative min-h-[5em] p-1 rounded-sm border border-chalkboard-20 dark:border-chalkboard-90 hover:border-energy-10 dark:hover:border-chalkboard-70 hover:bg-energy-10/20 dark:hover:bg-chalkboard-90"
|
||||
className="group relative min-h-[5em] p-1 rounded-sm border border-chalkboard-20 dark:border-chalkboard-80 hover:!border-primary"
|
||||
>
|
||||
{isEditing ? (
|
||||
<form onSubmit={handleSave} className="flex gap-2 items-center">
|
||||
<input
|
||||
className="dark:bg-chalkboard-80 dark:border-chalkboard-40 min-w-0 p-1 selection:bg-energy-10/20 focus:outline-none"
|
||||
className="dark:bg-chalkboard-80 dark:border-chalkboard-40 min-w-0 p-1 focus:outline-none"
|
||||
type="text"
|
||||
id="newProjectName"
|
||||
name="newProjectName"
|
||||
@ -87,27 +88,41 @@ function ProjectCard({
|
||||
<ActionButton
|
||||
Element="button"
|
||||
type="submit"
|
||||
icon={{ icon: faCheck, size: 'sm', className: 'p-1' }}
|
||||
icon={{
|
||||
icon: faCheck,
|
||||
size: 'sm',
|
||||
className: 'p-1',
|
||||
bgClassName: '!bg-transparent',
|
||||
}}
|
||||
className="!p-0"
|
||||
></ActionButton>
|
||||
>
|
||||
<Tooltip position="left" delay={1000}>
|
||||
Rename project
|
||||
</Tooltip>
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{
|
||||
icon: faX,
|
||||
size: 'sm',
|
||||
iconClassName: 'dark:!text-chalkboard-20',
|
||||
bgClassName: '!bg-transparent',
|
||||
className: 'p-1',
|
||||
}}
|
||||
className="!p-0"
|
||||
onClick={() => setIsEditing(false)}
|
||||
/>
|
||||
>
|
||||
<Tooltip position="left" delay={1000}>
|
||||
Cancel
|
||||
</Tooltip>
|
||||
</ActionButton>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<>
|
||||
<div className="p-1 flex flex-col h-full gap-2">
|
||||
<Link
|
||||
className="flex-1 !no-underline text-liquid-100 after:content-[''] after:absolute after:inset-0"
|
||||
className="flex-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10 after:content-[''] after:absolute after:inset-0"
|
||||
to={`${paths.FILE}/${encodeURIComponent(project.path)}`}
|
||||
data-testid="project-link"
|
||||
>
|
||||
@ -130,6 +145,7 @@ function ProjectCard({
|
||||
icon: faPenAlt,
|
||||
className: 'p-1',
|
||||
iconClassName: 'dark:!text-chalkboard-20',
|
||||
bgClassName: '!bg-transparent',
|
||||
size: 'xs',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
@ -138,15 +154,19 @@ function ProjectCard({
|
||||
setIsEditing(true)
|
||||
}}
|
||||
className="!p-0"
|
||||
/>
|
||||
>
|
||||
<Tooltip position="left" delay={1000}>
|
||||
Rename project
|
||||
</Tooltip>
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{
|
||||
icon: faTrashAlt,
|
||||
className: 'p-1',
|
||||
size: 'xs',
|
||||
bgClassName: 'bg-destroy-80',
|
||||
iconClassName: '!text-destroy-20 dark:!text-destroy-40',
|
||||
bgClassName: '!bg-transparent',
|
||||
iconClassName: '!text-destroy-70',
|
||||
}}
|
||||
className="!p-0 hover:border-destroy-40 dark:hover:border-destroy-40"
|
||||
onClick={(e) => {
|
||||
@ -154,7 +174,11 @@ function ProjectCard({
|
||||
e.nativeEvent.stopPropagation()
|
||||
setIsConfirmingDelete(true)
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Tooltip position="left" delay={1000}>
|
||||
Delete project
|
||||
</Tooltip>
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
<Dialog
|
||||
|
@ -25,15 +25,15 @@ const ProjectSidebarMenu = ({
|
||||
}) => {
|
||||
const { onProjectClose } = useLspContext()
|
||||
return (
|
||||
<div className="rounded-sm !no-underline h-9 mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center gap-2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50 dark:hover:bg-chalkboard-90">
|
||||
<div className="!no-underline h-full mr-auto max-h-min min-w-max flex items-center gap-2">
|
||||
<Link
|
||||
onClick={() => {
|
||||
onProjectClose(file || null, project?.path || null, false)
|
||||
}}
|
||||
to={paths.HOME}
|
||||
className="group"
|
||||
className="relative h-full grid place-content-center group p-1.5 before:block before:content-[''] before:absolute before:inset-0 before:bottom-2.5 before:z-[-1] before:bg-primary hover:before:brightness-110 before:rounded-b-sm"
|
||||
>
|
||||
<Logo className="w-auto h-5 text-chalkboard-120 dark:text-chalkboard-10 group-hover:text-energy-10" />
|
||||
<Logo className="w-auto h-4 text-chalkboard-10" />
|
||||
</Link>
|
||||
{renderAsLink ? (
|
||||
<>
|
||||
@ -67,13 +67,20 @@ function ProjectMenuPopover({
|
||||
project?: IndexLoaderData['project']
|
||||
file?: IndexLoaderData['file']
|
||||
}) {
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const { onProjectClose } = useLspContext()
|
||||
const exportCommandInfo = { name: 'Export', ownerMachine: 'modeling' }
|
||||
const findCommand = (obj: { name: string; ownerMachine: string }) =>
|
||||
Boolean(
|
||||
commandBarState.context.commands.find(
|
||||
(c) => c.name === obj.name && c.ownerMachine === obj.ownerMachine
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<Popover.Button
|
||||
className="rounded-sm h-9 mr-auto max-h-min min-w-max border-0 py-1 pl-0 pr-2 flex items-center focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50 dark:hover:bg-chalkboard-90"
|
||||
className="rounded-sm h-9 mr-auto max-h-min min-w-max border-0 py-1 pl-0 pr-2 flex items-center focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary dark:hover:bg-chalkboard-90"
|
||||
data-testid="project-sidebar-toggle"
|
||||
>
|
||||
<CustomIcon name="three-dots" className="w-5 h-5 rotate-90" />
|
||||
@ -119,15 +126,12 @@ function ProjectMenuPopover({
|
||||
<>
|
||||
<div className="flex items-center gap-4 px-4 py-3">
|
||||
<div>
|
||||
<p
|
||||
className="m-0 text-chalkboard-100 dark:text-energy-10 text-mono"
|
||||
data-testid="projectName"
|
||||
>
|
||||
<p className="m-0 text-mono" data-testid="projectName">
|
||||
{project?.name ? project.name : APP_NAME}
|
||||
</p>
|
||||
{project?.entrypointMetadata && (
|
||||
<p
|
||||
className="m-0 text-xs text-chalkboard-100 dark:text-energy-40"
|
||||
className="m-0 text-xs text-chalkboard-80 dark:text-chalkboard-40"
|
||||
data-testid="createdAt"
|
||||
>
|
||||
Created{' '}
|
||||
@ -143,17 +147,27 @@ function ProjectMenuPopover({
|
||||
closePanel={close}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex-1 overflow-hidden" />
|
||||
<div className="flex-1 p-4 text-sm overflow-hidden">
|
||||
<p>
|
||||
In the browser version of Modeling App you can only have one
|
||||
part, and the code is stored in your browser's storage.
|
||||
</p>
|
||||
<p className="my-6">
|
||||
Please save any code you want to keep more permanently, as
|
||||
your browser's storage is not guaranteed to be permanent.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
icon={{ icon: 'exportFile', className: 'p-1' }}
|
||||
className="border-transparent dark:border-transparent"
|
||||
disabled={!findCommand(exportCommandInfo)}
|
||||
onClick={() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Export', ownerMachine: 'modeling' },
|
||||
data: exportCommandInfo,
|
||||
})
|
||||
}
|
||||
>
|
||||
@ -166,11 +180,10 @@ function ProjectMenuPopover({
|
||||
onProjectClose(file || null, project?.path || null, true)
|
||||
}}
|
||||
icon={{
|
||||
icon: faHome,
|
||||
icon: 'arrowLeft',
|
||||
className: 'p-1',
|
||||
size: 'sm',
|
||||
}}
|
||||
className="border-transparent dark:border-transparent hover:bg-energy-10/20 dark:hover:bg-chalkboard-90"
|
||||
className="border-transparent dark:border-transparent"
|
||||
>
|
||||
Go to Home
|
||||
</ActionButton>
|
||||
|
@ -135,6 +135,7 @@ export const SettingsAuthProviderBase = ({
|
||||
: '')
|
||||
toast.success(message, {
|
||||
duration: message.split(' ').length * 100 + 1500,
|
||||
id: `${event.type}.success`,
|
||||
})
|
||||
},
|
||||
'Execute AST': () => kclManager.executeAst(),
|
||||
@ -199,6 +200,17 @@ export const SettingsAuthProviderBase = ({
|
||||
return () => matcher.removeEventListener('change', listener)
|
||||
}, [settingsState.context])
|
||||
|
||||
/**
|
||||
* Update the --primary-hue CSS variable
|
||||
* to match the setting app.themeColor.current
|
||||
*/
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty(
|
||||
`--primary-hue`,
|
||||
settingsState.context.app.themeColor.current
|
||||
)
|
||||
}, [settingsState.context.app.themeColor.current])
|
||||
|
||||
// Auth machine setup
|
||||
const [authState, authSend, authActor] = useMachine(authMachine, {
|
||||
actions: {
|
||||
|
@ -154,10 +154,9 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
icon={{
|
||||
icon: faSignOutAlt,
|
||||
className: 'p-1',
|
||||
bgClassName: 'bg-destroy-80',
|
||||
bgClassName: '!bg-transparent',
|
||||
size: 'sm',
|
||||
iconClassName:
|
||||
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||
iconClassName: '!text-destroy-70',
|
||||
}}
|
||||
className="border-transparent dark:border-transparent hover:border-destroy-40 dark:hover:border-destroy-60 hover:bg-destroy-10/20 dark:hover:bg-destroy-80/20"
|
||||
data-testid="user-sidebar-sign-out"
|
||||
|
@ -64,15 +64,15 @@ select {
|
||||
}
|
||||
|
||||
button {
|
||||
@apply border border-chalkboard-30 m-0.5 px-3 rounded text-xs focus-visible:ring-energy-10;
|
||||
@apply border border-chalkboard-30 m-0.5 px-3 rounded text-xs focus-visible:ring-primary;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
@apply border-chalkboard-40 bg-energy-10/20;
|
||||
@apply border-chalkboard-40 bg-primary/5;
|
||||
}
|
||||
|
||||
.dark button {
|
||||
@apply border-chalkboard-70 focus-visible:ring-energy-10/50;
|
||||
@apply border-chalkboard-70 focus-visible:ring-primary/50;
|
||||
}
|
||||
|
||||
.dark button:hover {
|
||||
@ -80,7 +80,7 @@ button:hover {
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
@apply cursor-not-allowed bg-chalkboard-20 text-chalkboard-60 border-chalkboard-20;
|
||||
@apply cursor-not-allowed bg-chalkboard-20/50 text-chalkboard-60 border-chalkboard-20;
|
||||
}
|
||||
|
||||
.dark button:disabled {
|
||||
@ -88,19 +88,19 @@ button:disabled {
|
||||
}
|
||||
|
||||
a:not(.action-button) {
|
||||
@apply text-energy-70 hover:text-energy-60 underline;
|
||||
@apply text-primary underline hover:hue-rotate-15;
|
||||
}
|
||||
|
||||
.dark a:not(.action-button) {
|
||||
@apply text-energy-20 hover:text-energy-10;
|
||||
@apply hover:brightness-110 hover:hue-rotate-0;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply selection:bg-energy-10/50;
|
||||
@apply selection:bg-primary/50;
|
||||
}
|
||||
|
||||
.dark input {
|
||||
@apply selection:bg-energy-10/40;
|
||||
@apply selection:bg-primary/40;
|
||||
}
|
||||
|
||||
.mono {
|
||||
|
@ -28,8 +28,8 @@ root.render(
|
||||
'bg-chalkboard-10 dark:bg-chalkboard-90 text-chalkboard-110 dark:text-chalkboard-10 rounded-sm border-chalkboard-20/50 dark:border-chalkboard-80/50',
|
||||
success: {
|
||||
iconTheme: {
|
||||
primary: 'oklch(93.31% 0.227 122.3deg)',
|
||||
secondary: 'oklch(24.49% 0.01405 158.7deg)',
|
||||
primary: 'oklch(89% 0.16 143.4deg)',
|
||||
secondary: 'oklch(48.62% 0.1654 142.5deg)',
|
||||
},
|
||||
duration: 1500,
|
||||
},
|
||||
|
@ -132,9 +132,43 @@ export function createSettings() {
|
||||
})),
|
||||
},
|
||||
}),
|
||||
themeColor: new Setting<string>({
|
||||
defaultValue: '264.5',
|
||||
description: 'The hue of the primary theme color for the app',
|
||||
validate: (v) => Number(v) >= 0 && Number(v) < 360,
|
||||
Component: ({ value, updateValue }) => (
|
||||
<div className="flex item-center gap-2 px-2">
|
||||
<input
|
||||
type="range"
|
||||
onChange={(e) => updateValue(e.currentTarget.value)}
|
||||
value={value}
|
||||
min={0}
|
||||
max={259}
|
||||
step={1}
|
||||
className="block flex-1"
|
||||
/>
|
||||
<span className="text-xs block w-[6ch] text-right">{value}º</span>
|
||||
<div
|
||||
className="w-3 h-3 rounded-full bg-primary"
|
||||
style={{
|
||||
backgroundColor: `oklch(var(--primary-lightness) var(--primary-chroma) ${value})`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
onboardingStatus: new Setting<string>({
|
||||
defaultValue: '',
|
||||
validate: (v) => typeof v === 'string',
|
||||
hideOnPlatform: 'both',
|
||||
}),
|
||||
/** Permanently dismiss the banner warning to download the desktop app. */
|
||||
dismissWebBanner: new Setting<boolean>({
|
||||
defaultValue: false,
|
||||
description:
|
||||
'Permanently dismiss the banner warning to download the desktop app.',
|
||||
validate: (v) => typeof v === 'boolean',
|
||||
hideOnPlatform: 'desktop',
|
||||
}),
|
||||
projectDirectory: new Setting<string>({
|
||||
defaultValue: '',
|
||||
@ -142,35 +176,41 @@ export function createSettings() {
|
||||
hideOnLevel: 'project',
|
||||
hideOnPlatform: 'web',
|
||||
validate: (v) => typeof v === 'string' && (v.length > 0 || !isTauri()),
|
||||
Component: ({ value, onChange }) => {
|
||||
Component: ({ value, updateValue }) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
return (
|
||||
<div className="flex gap-4 p-1 border rounded-sm border-chalkboard-30">
|
||||
<input
|
||||
className="flex-grow text-xs px-2 bg-transparent"
|
||||
value={value}
|
||||
onBlur={onChange}
|
||||
disabled
|
||||
data-testid="default-directory-input"
|
||||
data-testid="project-directory-input"
|
||||
ref={inputRef}
|
||||
/>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const newValue = await open({
|
||||
// In Tauri end-to-end tests we can't control the file picker,
|
||||
// so we seed the new directory value in the element's dataset
|
||||
const newValue =
|
||||
inputRef.current && inputRef.current.dataset.testValue
|
||||
? inputRef.current.dataset.testValue
|
||||
: await open({
|
||||
directory: true,
|
||||
recursive: true,
|
||||
defaultPath: value,
|
||||
title: 'Choose a new default directory',
|
||||
title: 'Choose a new project directory',
|
||||
})
|
||||
if (
|
||||
inputRef.current &&
|
||||
newValue &&
|
||||
newValue !== null &&
|
||||
newValue !== value &&
|
||||
!Array.isArray(newValue)
|
||||
) {
|
||||
inputRef.current.value = newValue
|
||||
updateValue(newValue)
|
||||
}
|
||||
}}
|
||||
className="p-0 m-0 border-none hover:bg-energy-10 focus:bg-energy-10 dark:hover:bg-energy-80/50 dark:focus::bg-energy-80/50"
|
||||
className="p-0 m-0 border-none hover:bg-primary/10 focus:bg-primary/10 dark:hover:bg-primary/20 dark:focus::bg-primary/20"
|
||||
data-testid="project-directory-button"
|
||||
>
|
||||
<CustomIcon name="folder" className="w-5 h-5" />
|
||||
<Tooltip position="inlineStart">Choose a folder</Tooltip>
|
||||
@ -230,13 +270,13 @@ export function createSettings() {
|
||||
],
|
||||
})),
|
||||
},
|
||||
Component: ({ value, onChange }) => (
|
||||
Component: ({ value, updateValue }) => (
|
||||
<>
|
||||
<select
|
||||
id="camera-controls"
|
||||
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onChange={(e) => updateValue(e.target.value as CameraSystem)}
|
||||
>
|
||||
{cameraSystems.map((program) => (
|
||||
<option key={program} value={program}>
|
||||
|
@ -86,7 +86,7 @@ export interface SettingProps<T = unknown> {
|
||||
* Whether to hide the setting on a certain platform.
|
||||
* This will be applied in both the settings panel and the command bar.
|
||||
*/
|
||||
hideOnPlatform?: 'web' | 'desktop'
|
||||
hideOnPlatform?: 'web' | 'desktop' | 'both'
|
||||
/**
|
||||
* A React component to use for the setting in the settings panel.
|
||||
* If this is not provided but a commandConfig is, the `inputType`
|
||||
@ -96,7 +96,7 @@ export interface SettingProps<T = unknown> {
|
||||
*/
|
||||
Component?: React.ComponentType<{
|
||||
value: T
|
||||
onChange: ChangeEventHandler
|
||||
updateValue: (newValue: T) => void
|
||||
}>
|
||||
}
|
||||
|
||||
|
@ -139,16 +139,14 @@ export function setSettingsAtLevel(
|
||||
Object.entries(newSettings).forEach(([category, settingsCategory]) => {
|
||||
const categoryKey = category as keyof typeof settings
|
||||
if (!allSettings[categoryKey]) return // ignore unrecognized categories
|
||||
Object.entries(settingsCategory).forEach(
|
||||
([settingKey, settingValue]: [string, Setting]) => {
|
||||
Object.entries(settingsCategory).forEach(([settingKey, settingValue]) => {
|
||||
// TODO: How do you get a valid type for allSettings[categoryKey][settingKey]?
|
||||
// it seems to always collapses to `never`, which is not correct
|
||||
// @ts-ignore
|
||||
if (!allSettings[categoryKey][settingKey]) return // ignore unrecognized settings
|
||||
// @ts-ignore
|
||||
allSettings[categoryKey][settingKey][level] = settingValue as unknown
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return allSettings
|
||||
@ -165,8 +163,42 @@ export function shouldHideSetting(
|
||||
) {
|
||||
return (
|
||||
setting.hideOnLevel === settingsLevel ||
|
||||
setting.hideOnPlatform === 'both' ||
|
||||
(setting.hideOnPlatform && isTauri()
|
||||
? setting.hideOnPlatform === 'desktop'
|
||||
: setting.hideOnPlatform === 'web')
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the setting meets the requirements
|
||||
* to appear in the settings modal in this context
|
||||
* based on its config, the current settings level,
|
||||
* and the current platform
|
||||
*/
|
||||
export function shouldShowSettingInput(
|
||||
setting: Setting<unknown>,
|
||||
settingsLevel: SettingsLevel
|
||||
) {
|
||||
return (
|
||||
!shouldHideSetting(setting, settingsLevel) &&
|
||||
(setting.Component ||
|
||||
['string', 'boolean'].some((t) => typeof setting.default === t) ||
|
||||
(setting.commandConfig?.inputType &&
|
||||
['string', 'options', 'boolean'].some(
|
||||
(t) => setting.commandConfig?.inputType === t
|
||||
)))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate input type to show given a
|
||||
* command's config. Highly dependent on the filtering logic from
|
||||
* shouldShowSettingInput being applied
|
||||
*/
|
||||
export function getSettingInputType(setting: Setting) {
|
||||
if (setting.Component) return 'component'
|
||||
if (setting.commandConfig)
|
||||
return setting.commandConfig.inputType as 'string' | 'options' | 'boolean'
|
||||
return typeof setting.default as 'string' | 'boolean'
|
||||
}
|
||||
|
@ -402,8 +402,8 @@ export const commandBarMachine = createMachine(
|
||||
'Initialize arguments to submit': assign({
|
||||
argumentsToSubmit: (c, e) => {
|
||||
const command =
|
||||
'command' in e.data ? e.data.command : c.selectedCommand!
|
||||
if (!command.args) return {}
|
||||
'command' in e.data ? e.data.command : c.selectedCommand
|
||||
if (!command?.args) return {}
|
||||
const args: { [x: string]: unknown } = {}
|
||||
for (const [argName, arg] of Object.entries(command.args)) {
|
||||
args[argName] =
|
||||
|
@ -32,6 +32,11 @@ export const settingsMachine = createMachine(
|
||||
internal: true,
|
||||
actions: ['setSettingAtLevel', 'persistSettings'], // No toast
|
||||
},
|
||||
'set.app.themeColor': {
|
||||
target: 'idle',
|
||||
internal: true,
|
||||
actions: ['setSettingAtLevel', 'persistSettings'], // No toast
|
||||
},
|
||||
|
||||
'set.modeling.defaultUnit': {
|
||||
target: 'idle',
|
||||
|
@ -172,8 +172,8 @@ const Home = () => {
|
||||
},
|
||||
})
|
||||
}, [
|
||||
settings.app.projectDirectory,
|
||||
settings.projects.defaultProjectName,
|
||||
settings.app.projectDirectory.current,
|
||||
settings.projects.defaultProjectName.current,
|
||||
send,
|
||||
])
|
||||
|
||||
@ -247,7 +247,7 @@ const Home = () => {
|
||||
<section data-testid="home-section">
|
||||
<p className="my-4 text-sm text-chalkboard-80 dark:text-chalkboard-30">
|
||||
Loaded from{' '}
|
||||
<span className="text-energy-70 dark:text-energy-40">
|
||||
<span className="text-chalkboard-90 dark:text-chalkboard-20">
|
||||
{settings.app.projectDirectory.current}
|
||||
</span>
|
||||
.{' '}
|
||||
|
@ -24,8 +24,7 @@ export default function CodeEditor() {
|
||||
>
|
||||
<section className="flex-1">
|
||||
<h2 className="text-3xl font-bold">
|
||||
Editing code with{' '}
|
||||
<span className="text-energy-60 dark:text-energy-20">kcl</span>
|
||||
Editing code with <span className="text-primary">kcl</span>
|
||||
</h2>
|
||||
<p className="my-4">
|
||||
kcl is our language for describing geometry. Building our own
|
||||
|
@ -137,7 +137,7 @@ export default function Introduction() {
|
||||
alt={APP_NAME}
|
||||
className="h-20 max-w-full"
|
||||
/>
|
||||
<span className="px-3 py-1 text-base rounded-full bg-energy-10 text-energy-80">
|
||||
<span className="px-3 py-1 text-base rounded-full bg-primary/10 text-primary">
|
||||
Alpha
|
||||
</span>
|
||||
</h1>
|
||||
|
@ -49,10 +49,8 @@ export default function ParametricModeling() {
|
||||
|
||||
<p className="my-4">
|
||||
We've received this sketch from a designer highlighting an{' '}
|
||||
<em className="text-energy-60 dark:text-energy-20">
|
||||
aluminum bracket
|
||||
</em>{' '}
|
||||
they need for this shelf:
|
||||
<em className="text-primary">aluminum bracket</em> they need for
|
||||
this shelf:
|
||||
</p>
|
||||
<figure className="my-4 w-2/3 mx-auto">
|
||||
<img
|
||||
@ -66,7 +64,7 @@ export default function ParametricModeling() {
|
||||
<p className="my-4">
|
||||
We are able to easily calculate the thickness of the material based
|
||||
on the width of the bracket to meet a set safety factor on{' '}
|
||||
<em className="text-energy-60 dark:text-energy-20">
|
||||
<em className="text-primary">
|
||||
line {bracketThicknessCalculationLine}
|
||||
</em>
|
||||
.
|
||||
|
@ -31,7 +31,11 @@ import { Event } from 'xstate'
|
||||
import { Dialog, RadioGroup, Transition } from '@headlessui/react'
|
||||
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { shouldHideSetting } from 'lib/settings/settingsUtils'
|
||||
import {
|
||||
getSettingInputType,
|
||||
shouldHideSetting,
|
||||
shouldShowSettingInput,
|
||||
} from 'lib/settings/settingsUtils'
|
||||
|
||||
export const Settings = () => {
|
||||
const APP_VERSION = import.meta.env.PACKAGE_VERSION || 'unknown'
|
||||
@ -235,9 +239,7 @@ export const Settings = () => {
|
||||
// Filter out settings that don't have a Component or inputType
|
||||
// or are hidden on the current level or the current platform
|
||||
(item: [string, Setting<unknown>]) =>
|
||||
!shouldHideSetting(item[1], settingsLevel) &&
|
||||
(item[1].Component ||
|
||||
item[1].commandConfig?.inputType)
|
||||
shouldShowSettingInput(item[1], settingsLevel)
|
||||
)
|
||||
.map(([settingName, s]) => {
|
||||
const setting = s as Setting
|
||||
@ -418,9 +420,7 @@ export function SettingsSection({
|
||||
className={
|
||||
'group grid grid-cols-2 gap-6 items-start ' +
|
||||
className +
|
||||
(settingHasChanged
|
||||
? ' border-0 border-l-2 -ml-0.5 border-energy-50 dark:border-energy-20'
|
||||
: '')
|
||||
(settingHasChanged ? ' border-0 border-l-2 -ml-0.5 border-primary' : '')
|
||||
}
|
||||
>
|
||||
<div className="ml-2">
|
||||
@ -484,26 +484,26 @@ function GeneratedSetting({
|
||||
)
|
||||
: []
|
||||
}, [setting, settingsLevel, context])
|
||||
const inputType = getSettingInputType(setting)
|
||||
|
||||
if (setting.Component)
|
||||
switch (inputType) {
|
||||
case 'component':
|
||||
return (
|
||||
setting.Component && (
|
||||
<setting.Component
|
||||
value={setting[settingsLevel] || setting.getFallback(settingsLevel)}
|
||||
onChange={(e) => {
|
||||
if ('value' in e.target) {
|
||||
updateValue={(newValue) => {
|
||||
send({
|
||||
type: `set.${category}.${settingName}`,
|
||||
data: {
|
||||
level: settingsLevel,
|
||||
value: e.target.value,
|
||||
value: newValue,
|
||||
},
|
||||
} as unknown as Event<WildcardSetEvent>)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
switch (setting.commandConfig?.inputType) {
|
||||
)
|
||||
case 'boolean':
|
||||
return (
|
||||
<Toggle
|
||||
@ -603,17 +603,14 @@ function SettingsTabButton(props: SettingsTabButtonProps) {
|
||||
<div
|
||||
className={`cursor-pointer select-none flex items-center gap-1 p-1 pr-2 -mb-[1px] border-0 border-b ${
|
||||
checked
|
||||
? 'border-energy-10 dark:border-energy-20'
|
||||
: 'border-chalkboard-20 dark:border-chalkboard-30 hover:bg-energy-10/50 dark:hover:bg-energy-90/50'
|
||||
? 'border-primary'
|
||||
: 'border-chalkboard-20 dark:border-chalkboard-30 hover:bg-primary/20 dark:hover:bg-primary/50'
|
||||
}`}
|
||||
>
|
||||
<CustomIcon
|
||||
name={icon}
|
||||
className={
|
||||
'w-5 h-5 ' +
|
||||
(checked
|
||||
? 'bg-energy-10 dark:bg-energy-20 dark:text-chalkboard-110'
|
||||
: '')
|
||||
'w-5 h-5 ' + (checked ? 'bg-primary !text-chalkboard-10' : '')
|
||||
}
|
||||
/>
|
||||
<span>{text}</span>
|
||||
|
@ -84,8 +84,6 @@ export interface StoreState {
|
||||
|
||||
showHomeMenu: boolean
|
||||
setHomeShowMenu: (showMenu: boolean) => void
|
||||
isBannerDismissed: boolean
|
||||
setBannerDismissed: (isBannerDismissed: boolean) => void
|
||||
openPanes: PaneType[]
|
||||
setOpenPanes: (panes: PaneType[]) => void
|
||||
homeMenuItems: {
|
||||
@ -150,8 +148,6 @@ export const useStore = create<StoreState>()(
|
||||
defaultDir: {
|
||||
dir: '',
|
||||
},
|
||||
isBannerDismissed: false,
|
||||
setBannerDismissed: (isBannerDismissed) => set({ isBannerDismissed }),
|
||||
openPanes: ['code'],
|
||||
setOpenPanes: (openPanes) => set({ openPanes }),
|
||||
showHomeMenu: true,
|
||||
|
7
src/wasm-lib/Cargo.lock
generated
@ -1646,6 +1646,12 @@ dependencies = [
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iai"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71a816c97c42258aa5834d07590b718b4c9a598944cd39a52dc25b351185d678"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.60"
|
||||
@ -1877,6 +1883,7 @@ dependencies = [
|
||||
"expectorate",
|
||||
"futures",
|
||||
"gltf-json",
|
||||
"iai",
|
||||
"image",
|
||||
"insta",
|
||||
"itertools 0.12.1",
|
||||
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
@ -71,6 +71,7 @@ base64 = "0.22.0"
|
||||
convert_case = "0.6.0"
|
||||
criterion = "0.5.1"
|
||||
expectorate = "1.1.0"
|
||||
iai = "0.1"
|
||||
image = "0.24.9"
|
||||
insta = { version = "1.38.0", features = ["json"] }
|
||||
itertools = "0.12.1"
|
||||
@ -79,5 +80,9 @@ tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
twenty-twenty = "0.7.0"
|
||||
|
||||
[[bench]]
|
||||
name = "compiler_benchmark"
|
||||
name = "compiler_benchmark_criterion"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "compiler_benchmark_iai"
|
||||
harness = false
|
||||
|
50
src/wasm-lib/kcl/benches/compiler_benchmark_iai.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use iai::black_box;
|
||||
|
||||
pub fn parse(program: &str) {
|
||||
let tokens = kcl_lib::token::lexer(program);
|
||||
let tok = tokens.clone();
|
||||
let parser = kcl_lib::parser::Parser::new(tok.clone());
|
||||
black_box(parser.ast().unwrap());
|
||||
}
|
||||
|
||||
fn lex_kitt() {
|
||||
black_box(kcl_lib::token::lexer(KITT_PROGRAM));
|
||||
}
|
||||
fn lex_pipes() {
|
||||
black_box(kcl_lib::token::lexer(PIPES_PROGRAM));
|
||||
}
|
||||
fn lex_cube() {
|
||||
black_box(kcl_lib::token::lexer(CUBE_PROGRAM));
|
||||
}
|
||||
fn lex_math() {
|
||||
black_box(kcl_lib::token::lexer(MATH_PROGRAM));
|
||||
}
|
||||
|
||||
fn parse_kitt() {
|
||||
parse(KITT_PROGRAM)
|
||||
}
|
||||
fn parse_pipes() {
|
||||
parse(PIPES_PROGRAM)
|
||||
}
|
||||
fn parse_cube() {
|
||||
parse(CUBE_PROGRAM)
|
||||
}
|
||||
fn parse_math() {
|
||||
parse(MATH_PROGRAM)
|
||||
}
|
||||
|
||||
iai::main! {
|
||||
lex_kitt,
|
||||
lex_pipes,
|
||||
lex_cube,
|
||||
lex_math,
|
||||
parse_kitt,
|
||||
parse_pipes,
|
||||
parse_cube,
|
||||
parse_math,
|
||||
}
|
||||
|
||||
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
|
||||
const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
|
||||
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
|
||||
const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl");
|
@ -89,6 +89,16 @@ impl EngineConnection {
|
||||
};
|
||||
let _ = request_sent.send(res);
|
||||
}
|
||||
let _ = Self::inner_close_engine(&mut tcp_write).await;
|
||||
}
|
||||
|
||||
/// Send the given `request` to the engine via the WebSocket connection `tcp_write`.
|
||||
async fn inner_close_engine(tcp_write: &mut WebSocketTcpWrite) -> Result<()> {
|
||||
tcp_write
|
||||
.send(WsMsg::Close(None))
|
||||
.await
|
||||
.map_err(|e| anyhow!("could not send close over websocket: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send the given `request` to the engine via the WebSocket connection `tcp_write`.
|
||||
@ -135,6 +145,9 @@ impl EngineConnection {
|
||||
loop {
|
||||
match tcp_read.read().await {
|
||||
Ok(ws_resp) => {
|
||||
for e in ws_resp.errors.iter().flatten() {
|
||||
println!("got error message: {e}");
|
||||
}
|
||||
if let Some(id) = ws_resp.request_id {
|
||||
responses_clone.insert(id, ws_resp.clone());
|
||||
}
|
||||
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 109 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 98 KiB |