Compare commits

...

16 Commits

Author SHA1 Message Date
f5ee346408 Release derive-docs 0.1.13 (#2044) 2024-04-09 17:02:16 +00:00
544a7565e3 Migrate to tauri v2 (#1400)
* Fix vite build (tauri build still broken)

* Fix yarn builds with a couple of shortcuts

* Fix file creation

* Fix documentDir behavior

* Got stream with default file

* Clean up

* Clean up

* Use 'unstable'; fix devtools callsite

The API changed a bit here, which forces us to use the unstable crate
feature. The call to open devtools is also new; it's now on the
webview not window.

Signed-off-by: Paul R. Tagliamonte <paul@kittycad.io>

* Bring back read_dir_recursive from v1

* Fix dates

* More fixes, incl. conf files

* cargo fmt

* Add Updater plugin

* Fix types

* Fix isTauri detection and updater bootup

* Schemas

* Clean up

* Disable devtools

* Attempt at fixing builds

* WIP Ubuntu dep

* WIP Ubuntu dep

* WIP keys in debug

* Enable updater only on release builds

* Reenable webtools on debug

* No linux bundles

* Typo

* Attemp at fixing --bundles none

* Manual tauri debug build

* Empty commit to trigger the CI

* Fix settings

* Empty commit to trigger the CI

* Merge branch 'main' into pierremtb/issue1349

* Add allow-create perm

* tauri-driver no cap

* Empty commit to trigger the CI

* Clean up

* Clean up

* Migrate to tauri v2
Fixes #1349

* Fix fmt

* Merge branch 'main' into pierremtb/issue1349

* Force BUILD_RELEASE: true

* Bump tauri to new beta

* Merge branch 'main' into pierremtb/issue1349

* Fix linux tests

* Fix types

* Add --verbose to tauri-action

* Move --verbose to front

* Back to tauri-driver@0.1.3 and single \ for win

* Back to latest driver, and windows wip

* Disable release conf temporarily

* Rollback to 2.0.0-beta.2

* Rollback to 2.0.0-beta.1

* Move bundle to root for src-tauri/tauri.release.conf.json

* All packages to latest (add http and shell to package.json)

* Testing latest commit for tauri-action

* Remove tauri action

* Add cat

* WIP

* Update ci.yml

* Disable release conf

* Disable rust cache

* Add tauri-action back for release builds

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

* Update .codespellrc

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

* Trigger CI

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

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

* Fix type

* Clean up

* More clean up

* Fix path concatenation with join

* Attempt at fixing linux tests

* Config clean up

* Downgrade to tauri-driver@0.1.3

* Looks like tauri v2 is actually doing better with linux package names ah!

* Change Linux apt packages

* Increase wdio connectionRetryTimeout

* Revert connectionRetryTimeout and bump tauri packages

* Back to latest tauri-driver

* Disable linux e2e tests

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

* Trigger CI

* Clean up

* Update snapshots

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

* Trigger CI

* Remove @sentry/react

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

* Rename migrated.json to desktop.json

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

* Trigger CI

* Change wasm url to http on Windows

---------

Signed-off-by: Paul R. Tagliamonte <paul@kittycad.io>
Co-authored-by: Paul R. Tagliamonte <paul@kittycad.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
2024-04-09 08:04:36 -04:00
979046f7e6 Clean up batch code (#2041)
* Clean up batch code

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

* Remove 'flush_batch: bool' from send_modeling_cmd

It was always being set with false, and it was
bugged for true. If true was set, the cmd would
never actually be run.

* Fix derive-docs

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-09 00:18:35 -05:00
07ae5106b9 Update rust dep 'h2' (#2037) 2024-04-08 18:25:47 -05:00
e9ae484332 Regenerate docs (#2040)
Rebuild KCL docs
2024-04-08 22:28:54 +00:00
2a86ffc09a Cut release v0.17.3 (#2032)
* Cut release v0.17.3

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

* Rerun CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-05 18:21:30 -04:00
93903a8a47 update 20/20 (#2028) 2024-04-05 16:24:30 +00:00
45e85a1f81 refactor CustomIcon component (#2026) 2024-04-05 12:04:49 -04:00
c187989d18 Send a WsMsg::Close before we bail normally. (#2027)
Send a WsMsg::Close before we bail normally.

This will hopefully trigger engine-manager to release the engine back to
the pool faster, allowing us to increase the number of threads we can
run the modeling-app tests with.

Signed-off-by: Paul R. Tagliamonte <paul@zoo.dev>
2024-04-05 15:37:46 +00:00
47b5fa1459 Franknoirot/bugfix directory picker (#2025)
* Fix project directory setting input

* Remove unused imports

* Almost working Tauri test

* Finish Tauri e2e test

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

* fmt

* Try a different Webriver selector

* Update themeColor component to use new updateValue API

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

* Rerun CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-05 05:48:12 +00:00
d85781ef99 Swap out primary UI color for Zoo brand blue, add theme color setting to control its hue (#2017)
* Add a setting for themeColor

* Add primary-color to Tailwind, driven by themeColor setting

* Get rid of most uses of "energy" colors

* Change out the rest of the energy colors

* Tweak NetworkHealthIndicator light mode checkmarks

* Handful of other CSS tweaks while I'm here:
- remove the AppHeader bg and border
- pane margins
- better dark mode button styles

* Make Zoo logomark a badge

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

* Re-run CI post-snapshots

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

* Retrigger CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-05 00:59:02 -04:00
233f81a879 Make it possible to permanently dismiss the web banner from the settings (#2021)
* Make it possible to include a setting only on the Settings dialog, not also in the command bar.

* Add web-only setting to permanently dismiss banner

* Honor the dismiss web banner setting

* Remove unused state from useStore

* Make the banner only appear in production builds again
2024-04-05 00:30:11 -04:00
8ac0bf4953 Don't assume a command is found in "Find and select command" command bar action (#2019) 2024-04-04 19:15:26 -04:00
24caeece65 Update snapshots (#2020)
Engine changed rendering again, so, gotta update snapshots
2024-04-04 16:15:39 -05:00
f493cf11a0 Print WebSocket errors when we get them (#2018)
* Print WebSocket errors when we get them

Previously, we eat them and ignore them, but now we'll seek out
and actively print error messages to stderr. We'd previously get a
websocket closed error, but we'll usually get an Error message over the
WebSocket before its closed on us.

Here's some example output during a crash

```
got ws error: WebSocket protocol error: Connection reset without closing handshake

Caused by:
    Connection reset without closing handshake
thread 'serial_test_cube_mm' panicked at tests/executor/main.rs:1136:10:
called `Result::unwrap()` on an `Err` value: engine: KclErrorDetails { source_ranges: [SourceRange([180, 188])], message: "Modeling command failed: websocket closed early" }
test serial_test_cube_mm ... FAILED
got error message: {
  "error_code": "bad_request",
  "message": "Too many active connections, only 2 allowed per user."
}
```
2024-04-04 18:47:47 +00:00
594e888c12 Benchmark rust in CI with iai, not criterion (#1937)
* Rename cargo-criterion to cargo-bench

* Use iai not criterion in CI

We want to benchmark the KCL parser and tokenizer to make sure we don't
accidentally slow them down. Generally Rust projects use Criterion to
benchmark code. Criterion runs your functions a few thousand times to
get reliable wall-clock measurements.

This is good for locally benchmarking but bad for benchmarking in CI.
Why? Because in CI, you're running a container on some shared VM, so
wall-clock time might have a lot of interference from noisy neighbours.
Also, your benchmarks take a long time to run and eat up paid CI minutes.

A better approach for benchmarking in CI is to just count the number of
CPU instructions executed. This correlates with wall-clock time, but it
only needs to run the function once, so it takes much less time. It also
isn't changed by any noisy neighbours running on the same VM or hardware.

This PR adds a new benchmark suite which counts instructions using `iai`,
from the creator of criterion. He says iai and criterion complement each
other nicely. We can run criterion locally and run iai in CI.

* Update image in markdown docs
2024-04-04 09:50:34 -05:00
309 changed files with 3214 additions and 2051 deletions

View File

@ -1,3 +1,3 @@
[codespell]
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,./src-tauri/gen/schemas

View File

@ -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

View File

@ -125,6 +125,9 @@ jobs:
fail-fast: false
matrix:
os: [macos-14, ubuntu-latest, windows-latest]
env:
TAURI_ARGS_MACOS: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
TAURI_ARGS_UBUNTU: ${{ matrix.os == 'ubuntu-latest' && '--bundles' || '' }}
steps:
- uses: actions/checkout@v4
@ -144,10 +147,12 @@ jobs:
sudo apt-get update &&
sudo apt-get install -y
libgtk-3-dev
libgtksourceview-3.0-dev
webkit2gtk-4.0
libappindicator3-dev
libayatana-appindicator3-dev
webkit2gtk-driver
libsoup-3.0-dev
libjavascriptcoregtk-4.1-dev
libwebkit2gtk-4.1-dev
at-spi2-core
xvfb
- name: Sync node version and setup cache
@ -161,7 +166,9 @@ jobs:
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
# TODO: re-enable for Windows builds, see https://github.com/tauri-apps/tauri/issues/9045
- name: Setup Rust cache
if: matrix.os != 'windows-latest'
uses: swatinem/rust-cache@v2
with:
workspaces: './src-tauri -> target'
@ -224,14 +231,14 @@ jobs:
with:
includeRelease: false
includeDebug: true
args: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }}
args: "${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
- name: Build the app (release) and sign
uses: tauri-apps/tauri-action@v0
if: ${{ env.BUILD_RELEASE == 'true' }}
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
@ -240,7 +247,7 @@ jobs:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
TAURI_CONF_ARGS: "--config ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}"
with:
args: "${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}"
args: "${{ env.TAURI_CONF_ARGS }} ${{ env.TAURI_ARGS_MACOS }} ${{ env.TAURI_ARGS_UBUNTU }}"
- uses: actions/upload-artifact@v3
if: matrix.os != 'ubuntu-latest'
@ -250,10 +257,11 @@ jobs:
with:
path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*"
# TODO: re-enable linux e2e tests when possible
- name: Run e2e tests (linux only)
if: matrix.os == 'ubuntu-latest'
if: false
run: |
cargo install tauri-driver@0.1.3
cargo install tauri-driver
source .env.${{ env.BUILD_RELEASE == 'true' && 'production' || 'development' }}
export VITE_KC_API_BASE_URL
xvfb-run yarn test:e2e:tauri

1
.gitignore vendored
View File

@ -51,5 +51,6 @@ e2e/playwright/export-snapshots/*
## generated files
src/**/*.typegen.ts
src-tauri/gen
src/wasm-lib/grackle/stdlib_cube_partial.json

View File

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -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')

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.17.2",
"version": "0.17.3",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.15.0",
@ -16,7 +16,12 @@
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
"@replit/codemirror-interact": "^6.3.0",
"@tauri-apps/api": "^1.5.3",
"@tauri-apps/api": "^2.0.0-beta.7",
"@tauri-apps/plugin-dialog": "^2.0.0-beta.2",
"@tauri-apps/plugin-fs": "^2.0.0-beta.2",
"@tauri-apps/plugin-http": "^2.0.0-beta.2",
"@tauri-apps/plugin-os": "^2.0.0-beta.2",
"@tauri-apps/plugin-shell": "^2.0.0-beta.2",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.2",
@ -48,7 +53,6 @@
"react-router-dom": "^6.22.3",
"sketch-helpers": "^0.0.4",
"swr": "^2.2.2",
"tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
"three": "^0.160.0",
"toml": "^3.0.0",
"ts-node": "^10.9.2",
@ -86,7 +90,7 @@
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
"lint": "eslint --fix src",
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
"postinstall": "yarn xstate:typegen",
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\""
},
@ -112,7 +116,7 @@
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.24.3",
"@playwright/test": "^1.39.0",
"@tauri-apps/cli": "^1.5.11",
"@tauri-apps/cli": "^2.0.0-beta.12",
"@types/crypto-js": "^4.2.2",
"@types/debounce-promise": "^3.1.9",
"@types/pixelmatch": "^5.2.6",

2545
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,12 +7,12 @@ license = ""
repository = "https://github.com/KittyCAD/modeling-app"
default-run = "app"
edition = "2021"
rust-version = "1.60"
rust-version = "1.70"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.5.1", features = [] }
tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies]
anyhow = "1"
@ -20,8 +20,13 @@ kittycad = "0.2.63"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "1.6.1", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "devtools"] }
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tauri = { version = "2.0.0-beta", features = [ "devtools", "unstable"] }
tauri-plugin-dialog = { version = "2.0.0-beta.0" }
tauri-plugin-fs = { version = "2.0.0-beta.0" }
tauri-plugin-http = { version = "2.0.0-beta.0" }
tauri-plugin-os = { version = "2.0.0-beta.0" }
tauri-plugin-shell = { version = "2.0.0-beta.0" }
tauri-plugin-updater = { version = "2.0.0-beta.0" }
tokio = { version = "1.37.0", features = ["time"] }
toml = "0.8.2"

View File

@ -0,0 +1,87 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"description": "Capability for the main window",
"context": "local",
"windows": [
"main"
],
"permissions": [
"path:default",
"event:default",
"window:default",
"app:default",
"resources:default",
"menu:default",
"tray:default",
"fs:allow-create",
"fs:allow-read-file",
"fs:allow-read-text-file",
"fs:allow-write-file",
"fs:allow-write-text-file",
"fs:allow-read-dir",
"fs:allow-copy-file",
"fs:allow-mkdir",
"fs:allow-remove",
"fs:allow-remove",
"fs:allow-rename",
"fs:allow-exists",
"fs:allow-stat",
{
"identifier": "fs:scope",
"allow": [
{
"path": "$HOME/**/*"
},
{
"path": "$HOME/.config"
},
{
"path": "$HOME/.config/**/*"
},
{
"path": "$APPCONFIG"
},
{
"path": "$APPCONFIG/**/*"
},
{
"path": "$DOCUMENT"
},
{
"path": "$DOCUMENT/**/*"
}
]
},
"shell:allow-open",
"dialog:allow-open",
"dialog:allow-save",
"dialog:allow-message",
"dialog:allow-ask",
"dialog:allow-confirm",
{
"identifier": "http:default",
"allow": [
"https://dev.kittycad.io/*",
"https://dev.zoo.dev/*",
"https://kittycad.io/*",
"https://zoo.dev/*",
"https://api.dev.kittycad.io/*",
"https://api.dev.zoo.dev/*"
]
},
"os:allow-platform",
"os:allow-version",
"os:allow-os-type",
"os:allow-family",
"os:allow-arch",
"os:allow-exe-extension",
"os:allow-locale",
"os:allow-hostname"
],
"platforms": [
"linux",
"macOS",
"windows"
]
}

View File

@ -4,11 +4,15 @@
use std::env;
use std::fs;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use anyhow::Result;
use oauth2::TokenResponse;
use serde::Serialize;
use std::process::Command;
use tauri::{InvokeError, Manager};
use tauri::ipc::InvokeError;
use tauri_plugin_shell::ShellExt;
const DEFAULT_HOST: &str = "https://api.kittycad.io";
/// This command returns the a json string parse from a toml file at the path.
@ -24,6 +28,56 @@ fn read_toml(path: &str) -> Result<String, InvokeError> {
Ok(value)
}
/// From https://github.com/tauri-apps/tauri/blob/1.x/core/tauri/src/api/dir.rs#L51
/// Removed from tauri v2
#[derive(Debug, Serialize)]
pub struct DiskEntry {
/// The path to the entry.
pub path: PathBuf,
/// The name of the entry (file name with extension or directory name).
pub name: Option<String>,
/// The children of this entry if it's a directory.
#[serde(skip_serializing_if = "Option::is_none")]
pub children: Option<Vec<DiskEntry>>,
}
/// From https://github.com/tauri-apps/tauri/blob/1.x/core/tauri/src/api/dir.rs#L51
/// Removed from tauri v2
fn is_dir<P: AsRef<Path>>(path: P) -> Result<bool> {
std::fs::metadata(path)
.map(|md| md.is_dir())
.map_err(Into::into)
}
/// From https://github.com/tauri-apps/tauri/blob/1.x/core/tauri/src/api/dir.rs#L51
/// Removed from tauri v2
#[tauri::command]
fn read_dir_recursive(path: &str) -> Result<Vec<DiskEntry>, InvokeError> {
let mut files_and_dirs: Vec<DiskEntry> = vec![];
// let path = path.as_ref();
for entry in fs::read_dir(path).map_err(|e| InvokeError::from_anyhow(e.into()))? {
let path = entry
.map_err(|e| InvokeError::from_anyhow(e.into()))?
.path();
if let Ok(flag) = is_dir(&path) {
files_and_dirs.push(DiskEntry {
path: path.clone(),
children: if flag {
Some(read_dir_recursive(path.to_str().expect("No path"))?)
} else {
None
},
name: path
.file_name()
.map(|name| name.to_string_lossy())
.map(|name| name.to_string()),
});
}
}
Ok(files_and_dirs)
}
/// This command returns a string that is the contents of a file at the path.
#[tauri::command]
fn read_txt_file(path: &str) -> Result<String, InvokeError> {
@ -85,7 +139,8 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
fs::write("/tmp/kittycad_user_code", details.user_code().secret())
.expect("Unable to write /tmp/kittycad_user_code file");
} else {
tauri::api::shell::open(&app.shell_scope(), auth_uri.secret(), None)
app.shell()
.open(auth_uri.secret(), None)
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
}
@ -165,12 +220,15 @@ fn show_in_folder(path: String) {
fn main() {
tauri::Builder::default()
.setup(|_app| {
#[cfg(debug_assertions)] // only include this code on debug builds
#[cfg(debug_assertions)]
{
let window = _app.get_window("main").unwrap();
// comment out the below if you don't devtools to open everytime.
// it's useful because otherwise devtools shuts everytime rust code changes.
window.open_devtools();
use tauri::Manager;
_app.get_webview("main").unwrap().open_devtools();
}
#[cfg(not(debug_assertions))]
{
_app.handle()
.plugin(tauri_plugin_updater::Builder::new().build())?;
}
Ok(())
})
@ -179,9 +237,14 @@ fn main() {
login,
read_toml,
read_txt_file,
read_dir_recursive,
show_in_folder,
])
.plugin(tauri_plugin_fs_extra::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_shell::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@ -1,63 +1,28 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"build": {
"beforeDevCommand": "yarn start",
"devPath": "http://localhost:3000",
"distDir": "../build"
"app": {
"security": {
"csp": null
},
"package": {
"productName": "zoo-modeling-app",
"version": "0.17.2"
},
"tauri": {
"allowlist": {
"all": false,
"dialog": {
"all": true,
"ask": true,
"confirm": true,
"message": true,
"open": true,
"save": true
},
"fs": {
"scope": [
"$HOME/**/*",
"$APPCONFIG",
"$APPCONFIG/**/*",
"$DOCUMENT",
"$DOCUMENT/**/*"
],
"all": true
},
"http": {
"request": true,
"scope": [
"https://dev.kittycad.io/*",
"https://dev.zoo.dev/*",
"https://kittycad.io/*",
"https://zoo.dev/*",
"https://api.dev.kittycad.io/*",
"https://api.dev.zoo.dev/*"
"windows": [
{
"fullscreen": false,
"height": 1200,
"resizable": true,
"title": "Zoo Modeling App",
"width": 1800
}
]
},
"os": {
"all": true
},
"shell": {
"open": true
},
"path": {
"all": true
}
"build": {
"beforeDevCommand": "yarn start",
"devUrl": "http://localhost:3000",
"frontendDist": "../build"
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [
"icons/32x32.png",
@ -66,7 +31,11 @@
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "dev.zoo.modeling-app",
"linux": {
"deb": {
"depends": []
}
},
"longDescription": "",
"macOS": {
"entitlements": null,
@ -79,20 +48,12 @@
"shortDescription": "",
"targets": "all"
},
"security": {
"csp": null
"identifier": "dev.zoo.modeling-app",
"plugins": {
"shell": {
"open": true
}
},
"updater": {
"active": false
},
"windows": [
{
"fullscreen": false,
"height": 1200,
"resizable": true,
"title": "Zoo Modeling App",
"width": 1800
}
]
}
"productName": "Zoo Modeling App",
"version": "0.17.3"
}

View File

@ -1,6 +0,0 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"package": {
"productName": "Zoo Modeling App"
}
}

View File

@ -1,6 +1,13 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"tauri": {
"bundle": {
"windows": {
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com"
}
},
"plugins": {
"updater": {
"active": true,
"endpoints": [
@ -8,14 +15,6 @@
],
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEUzNzA4MjBEQjFBRTY4NzYKUldSMmFLNnhEWUp3NCtsT21Jd05wQktOaGVkOVp6MUFma0hNTDRDSnI2RkJJTEZOWG1ncFhqcU8K"
},
"bundle": {
"identifier": "io.kittycad.modeling-app",
"windows": {
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com"
}
}
}
}

View File

@ -1,6 +0,0 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"package": {
"productName": "Zoo Modeling App"
}
}

View File

@ -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>

View File

@ -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: [

View File

@ -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,16 +245,16 @@ 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+/'}
{platform === 'macos' ? '⌘K' : 'Ctrl+/'}
</ActionButton>
</div>
)

View File

@ -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);

View File

@ -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

View File

@ -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">
{platform === 'darwin' ? '⌘K' : 'Ctrl+/'}
<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 === 'macos' ? '⌘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 || (
<>

View File

@ -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 {

View File

@ -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()}

View File

@ -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) {

View File

@ -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}

View File

@ -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 && (

View File

@ -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>

View File

@ -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;
}

View File

@ -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'
}
>

View File

@ -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 && (

View File

@ -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)

View File

@ -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 &gt; Dismiss Web Banner setting
</a>
.
</p>
</div>
</Dialog.Panel>
</Dialog>

View File

@ -14,16 +14,10 @@ import {
} from 'xstate'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { fileMachine } from 'machines/fileMachine'
import {
createDir,
removeDir,
removeFile,
renameFile,
writeFile,
} from '@tauri-apps/api/fs'
import { mkdir, remove, rename, create } from '@tauri-apps/plugin-fs'
import { readProject } from 'lib/tauriFS'
import { isTauri } from 'lib/isTauri'
import { sep } from '@tauri-apps/api/path'
import { join, sep } from '@tauri-apps/api/path'
import { DEFAULT_FILE_NAME, FILE_EXT } from 'lib/constants'
type MachineContext<T extends AnyStateMachine> = {
@ -56,7 +50,7 @@ export const FileMachineProvider = ({
commandBarSend({ type: 'Close' })
navigate(
`${paths.FILE}/${encodeURIComponent(
context.selectedDirectory + sep + event.data.name
context.selectedDirectory + sep() + event.data.name
)}`
)
}
@ -79,14 +73,13 @@ export const FileMachineProvider = ({
let name = event.data.name.trim() || DEFAULT_FILE_NAME
if (event.data.makeDir) {
await createDir(context.selectedDirectory.path + sep + name)
await mkdir(await join(context.selectedDirectory.path, name))
} else {
await writeFile(
await create(
context.selectedDirectory.path +
sep +
sep() +
name +
(name.endsWith(FILE_EXT) ? '' : FILE_EXT),
''
(name.endsWith(FILE_EXT) ? '' : FILE_EXT)
)
}
@ -99,12 +92,11 @@ export const FileMachineProvider = ({
const { oldName, newName, isDir } = event.data
let name = newName ? newName : DEFAULT_FILE_NAME
await renameFile(
context.selectedDirectory.path + sep + oldName,
context.selectedDirectory.path +
sep +
name +
(name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT)
await rename(
await join(context.selectedDirectory.path, oldName),
(await join(context.selectedDirectory.path, name)) +
(name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT),
{}
)
return (
oldName !== name && `Successfully renamed "${oldName}" to "${name}"`
@ -117,11 +109,11 @@ export const FileMachineProvider = ({
const isDir = !!event.data.children
if (isDir) {
await removeDir(event.data.path, {
await remove(event.data.path, {
recursive: true,
}).catch((e) => console.error('Error deleting directory', e))
} else {
await removeFile(event.data.path).catch((e) =>
await remove(event.data.path).catch((e) =>
console.error('Error deleting file', e)
)
}

View File

@ -1,8 +1,7 @@
import { type IndexLoaderData } from 'lib/types'
import type { FileEntry, IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths'
import { ActionButton } from './ActionButton'
import Tooltip from './Tooltip'
import { FileEntry } from '@tauri-apps/api/fs'
import { Dispatch, useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Dialog, Disclosure } from '@headlessui/react'
@ -192,8 +191,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 +205,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 +224,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 +347,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 +368,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}>

View File

@ -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')
}
>

View File

@ -13,7 +13,7 @@ import { Extension } from '@codemirror/state'
import { LanguageSupport } from '@codemirror/language'
import { useNavigate } from 'react-router-dom'
import { paths } from 'lib/paths'
import { FileEntry } from '@tauri-apps/api/fs'
import { FileEntry } from 'lib/types'
const DEFAULT_FILE_NAME: string = 'main.kcl'

View File

@ -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>

View File

@ -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"
>
@ -121,7 +136,10 @@ function ProjectCard({
}`}
</span>
<span className="text-chalkboard-60 text-xs">
Edited {getDisplayedTime(project.entrypointMetadata.modifiedAt)}
Edited{' '}
{project.entrypointMetadata.mtime
? getDisplayedTime(project.entrypointMetadata.mtime)
: 'never'}
</span>
<div className="absolute z-10 bottom-2 right-2 flex gap-1 items-center opacity-0 group-hover:opacity-100 group-focus-within:opacity-100">
<ActionButton
@ -130,6 +148,7 @@ function ProjectCard({
icon: faPenAlt,
className: 'p-1',
iconClassName: 'dark:!text-chalkboard-20',
bgClassName: '!bg-transparent',
size: 'xs',
}}
onClick={(e) => {
@ -138,15 +157,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 +177,11 @@ function ProjectCard({
e.nativeEvent.stopPropagation()
setIsConfirmingDelete(true)
}}
/>
>
<Tooltip position="left" delay={1000}>
Delete project
</Tooltip>
</ActionButton>
</div>
</div>
<Dialog

View File

@ -17,23 +17,24 @@ const projectWellFormed = {
},
],
entrypointMetadata: {
accessedAt: now,
atime: now,
blksize: 32,
blocks: 32,
createdAt: now,
birthtime: now,
dev: 1,
gid: 1,
ino: 1,
isDir: false,
isDirectory: false,
isFile: true,
isSymlink: false,
mode: 1,
modifiedAt: now,
mtime: now,
nlink: 1,
permissions: { readonly: false, mode: 1 },
readonly: false,
rdev: 1,
size: 32,
uid: 1,
fileAttributes: null,
},
} satisfies ProjectWithEntryPointMetadata

View File

@ -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,20 +67,27 @@ 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" />
<div className="flex flex-col items-start py-0.5">
<span className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block">
{isTauri() && file?.name
? file.name.slice(file.name.lastIndexOf(sep) + 1)
? file.name.slice(file.name.lastIndexOf(sep()) + 1)
: APP_NAME}
</span>
{isTauri() && project?.name && (
@ -119,19 +126,16 @@ 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{' '}
{project.entrypointMetadata.createdAt.toLocaleDateString()}
{project.entrypointMetadata.birthtime?.toLocaleDateString()}
</p>
)}
</div>
@ -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>

View File

@ -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: {

View File

@ -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"

View File

@ -1,4 +1,4 @@
import { Platform, platform } from '@tauri-apps/api/os'
import { Platform, platform } from '@tauri-apps/plugin-os'
import { isTauri } from 'lib/isTauri'
import { useEffect, useState } from 'react'
@ -14,9 +14,9 @@ export default function usePlatform() {
void getPlatform()
} else {
if (navigator.userAgent.indexOf('Mac') !== -1) {
setPlatformName('darwin')
setPlatformName('macos')
} else if (navigator.userAgent.indexOf('Win') !== -1) {
setPlatformName('win32')
setPlatformName('windows')
} else if (navigator.userAgent.indexOf('Linux') !== -1) {
setPlatformName('linux')
}

View File

@ -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 {

Some files were not shown because too many files have changed in this diff Show More