Compare commits

..

1 Commits

Author SHA1 Message Date
4de0b57ea4 Add array concatenation using the plus operator 2025-07-01 19:58:27 -04:00
55 changed files with 491 additions and 311 deletions

View File

@ -7,6 +7,7 @@ VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000
#VITE_WASM_URL="optional way of overriding the wasm url, particular for unit tests which need this if you running not on the default 3000 port"
#VITE_KC_DEV_TOKEN="optional token to skip auth in the app"

View File

@ -3,4 +3,5 @@ VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.zoo.dev
VITE_KC_SITE_BASE_URL=https://zoo.dev
VITE_KC_SITE_APP_URL=https://app.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=15000

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 44 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: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 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: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

1
interface.d.ts vendored
View File

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

View File

@ -994,39 +994,6 @@ impl Node<MemberExpression> {
// Check the property and object match -- e.g. ints for arrays, strs for objects.
match (object, property, self.computed) {
(KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
"yAxis" => {
let (p, u) = plane.info.y_axis.as_3_dims();
Ok(KclValue::array_from_point3d(
p,
NumericType::Known(crate::exec::UnitType::Length(u)),
vec![meta],
))
}
"xAxis" => {
let (p, u) = plane.info.x_axis.as_3_dims();
Ok(KclValue::array_from_point3d(
p,
NumericType::Known(crate::exec::UnitType::Length(u)),
vec![meta],
))
}
"origin" => {
let (p, u) = plane.info.origin.as_3_dims();
Ok(KclValue::array_from_point3d(
p,
NumericType::Known(crate::exec::UnitType::Length(u)),
vec![meta],
))
}
other => Err(KclError::new_undefined_value(
KclErrorDetails::new(
format!("Property '{other}' not found in plane"),
vec![self.clone().into()],
),
None,
)),
},
(KclValue::Object { value: map, meta: _ }, Property::String(property), false) => {
if let Some(value) = map.get(&property) {
Ok(value.to_owned())
@ -1046,22 +1013,7 @@ impl Node<MemberExpression> {
vec![self.clone().into()],
)))
}
(KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
if i == 0
&& let Some(value) = map.get("x")
{
return Ok(value.to_owned());
}
if i == 1
&& let Some(value) = map.get("y")
{
return Ok(value.to_owned());
}
if i == 2
&& let Some(value) = map.get("z")
{
return Ok(value.to_owned());
}
(KclValue::Object { .. }, p, _) => {
let t = p.type_name();
let article = article_for(t);
Err(KclError::new_semantic(KclErrorDetails::new(
@ -1134,6 +1086,32 @@ impl Node<MemberExpression> {
}
}
fn concat(left: &[KclValue], left_el_ty: &RuntimeType, right: &[KclValue], right_el_ty: &RuntimeType) -> KclValue {
if left.is_empty() {
return KclValue::HomArray {
value: right.to_vec(),
ty: right_el_ty.clone(),
};
}
if right.is_empty() {
return KclValue::HomArray {
value: left.to_vec(),
ty: left_el_ty.clone(),
};
}
let mut new = left.to_vec();
new.extend_from_slice(right);
// Propagate the element type if we can.
let ty = if right_el_ty.subtype(left_el_ty) {
left_el_ty.clone()
} else if left_el_ty.subtype(right_el_ty) {
right_el_ty.clone()
} else {
RuntimeType::any()
};
KclValue::HomArray { value: new, ty }
}
impl Node<BinaryExpression> {
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
@ -1152,6 +1130,50 @@ impl Node<BinaryExpression> {
meta,
});
}
// Array plus is concatenation.
match (&left_value, &right_value) {
(
KclValue::HomArray {
value: left,
ty: left_el_ty,
..
},
KclValue::HomArray {
value: right,
ty: right_el_ty,
..
},
) => {
return Ok(concat(left, left_el_ty, right, right_el_ty));
}
(
KclValue::HomArray {
value: left,
ty: left_el_ty,
..
},
_,
) => {
// Any single value can be coerced to an array.
let right = vec![right_value.clone()];
let right_el_ty = RuntimeType::any();
return Ok(concat(left, left_el_ty, &right, &right_el_ty));
}
(
_,
KclValue::HomArray {
value: right,
ty: right_el_ty,
..
},
) => {
// Any single value can be coerced to an array.
let left = vec![left_value.clone()];
let left_el_ty = RuntimeType::any();
return Ok(concat(&left, &left_el_ty, right, right_el_ty));
}
_ => {}
}
}
// Then check if we have solids.
@ -2253,12 +2275,4 @@ y = x[0mm + 1]
"#;
parse_execute(ast).await.unwrap_err();
}
#[tokio::test(flavor = "multi_thread")]
async fn getting_property_of_plane() {
// let ast = include_str!("../../tests/inputs/planestuff.kcl");
let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
parse_execute(&ast).await.unwrap();
}
}

View File

@ -921,12 +921,6 @@ impl Point3d {
units: UnitLen::Unknown,
}
}
pub fn as_3_dims(&self) -> ([f64; 3], UnitLen) {
let p = [self.x, self.y, self.z];
let u = self.units;
(p, u)
}
}
impl From<[TyF64; 3]> for Point3d {

View File

@ -458,31 +458,6 @@ impl KclValue {
}
}
/// Put the point into a KCL point.
pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
let [x, y, z] = p;
Self::HomArray {
value: vec![
Self::Number {
value: x,
meta: meta.clone(),
ty,
},
Self::Number {
value: y,
meta: meta.clone(),
ty,
},
Self::Number {
value: z,
meta: meta.clone(),
ty,
},
],
ty: ty.into(),
}
}
pub(crate) fn as_usize(&self) -> Option<usize> {
match self {
KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),

View File

@ -246,7 +246,7 @@ impl RuntimeType {
}
// Subtype with no coercion, including refining numeric types.
fn subtype(&self, sup: &RuntimeType) -> bool {
pub(super) fn subtype(&self, sup: &RuntimeType) -> bool {
use RuntimeType::*;
match (self, sup) {

View File

@ -14,7 +14,7 @@ description: Result of parsing add_arrays.kcl
"commentStart": 0,
"end": 0,
"moduleId": 0,
"name": "answer",
"name": "a",
"start": 0,
"type": "Identifier"
},
@ -96,6 +96,170 @@ description: Result of parsing add_arrays.kcl
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"commentStart": 0,
"declaration": {
"commentStart": 0,
"end": 0,
"id": {
"commentStart": 0,
"end": 0,
"moduleId": 0,
"name": "b",
"start": 0,
"type": "Identifier"
},
"init": {
"commentStart": 0,
"end": 0,
"left": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "1",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
}
],
"end": 0,
"moduleId": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"moduleId": 0,
"operator": "+",
"right": {
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"suffix": "None"
}
},
"start": 0,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"moduleId": 0,
"start": 0,
"type": "VariableDeclarator"
},
"end": 0,
"kind": "const",
"moduleId": 0,
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"commentStart": 0,
"declaration": {
"commentStart": 0,
"end": 0,
"id": {
"commentStart": 0,
"end": 0,
"moduleId": 0,
"name": "c",
"start": 0,
"type": "Identifier"
},
"init": {
"commentStart": 0,
"end": 0,
"left": {
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
"moduleId": 0,
"operator": "+",
"right": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "1",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"moduleId": 0,
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"suffix": "None"
}
}
],
"end": 0,
"moduleId": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"start": 0,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"moduleId": 0,
"start": 0,
"type": "VariableDeclarator"
},
"end": 0,
"kind": "const",
"moduleId": 0,
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"commentStart": 0,

View File

@ -1,12 +0,0 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Error from executing add_arrays.kcl
---
KCL Semantic error
× semantic: Expected a number, but found an array of `number`, `number`
╭────
1 │ answer = [0, 1] + [2]
· ───┬──
· ╰── tests/add_arrays/input.kcl
╰────

View File

@ -1 +1,3 @@
answer = [0, 1] + [2]
a = [0, 1] + [2]
b = [0, 1] + 2
c = 0 + [1, 2]

View File

@ -0,0 +1,138 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Variables in memory after executing add_arrays.kcl
---
{
"a": {
"type": "HomArray",
"value": [
{
"type": "Number",
"value": 0.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 1.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 2.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
}
]
},
"b": {
"type": "HomArray",
"value": [
{
"type": "Number",
"value": 0.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 1.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 2.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
}
]
},
"c": {
"type": "HomArray",
"value": [
{
"type": "Number",
"value": 0.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 1.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
{
"type": "Number",
"value": 2.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
}
]
}
}

View File

@ -2,4 +2,6 @@
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing add_arrays.kcl
---
answer = [0, 1] + [2]
a = [0, 1] + [2]
b = [0, 1] + 2
c = 0 + [1, 2]

View File

@ -1,60 +0,0 @@
// There are 3 ways to define a plane in KCL, according to https://zoo.dev/docs/kcl-std/types/std-types-Plane
// - A default plane
// - Modifying a default plane e.g. via offsetPlane
// - Defining your own struct
// This file tests they all work equivalently.
// Define a plane using struct representation.
myPlane = {
origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
}
// Prove we can get its axes and origin.
ax = myPlane.xAxis
assert(ax[0], isEqualTo = 1)
assert(ax[1], isEqualTo = 0)
assert(ax[2], isEqualTo = 0)
ay = myPlane.yAxis
assert(ay[0], isEqualTo = 0)
assert(ay[1], isEqualTo = 1)
assert(ay[2], isEqualTo = 0)
aorigin = myPlane.origin
assert(aorigin[0], isEqualTo = 0)
assert(aorigin[1], isEqualTo = 0)
assert(aorigin[2], isEqualTo = 0)
// Define a plane using standard planes.
myOtherPlane = XY
// Prove we can get its axes and origin.
axOther = myOtherPlane.xAxis
assert(axOther[0], isEqualTo = 1)
assert(axOther[1], isEqualTo = 0)
assert(axOther[2], isEqualTo = 0)
ayOther = myOtherPlane.yAxis
assert(ayOther[0], isEqualTo = 0)
assert(ayOther[1], isEqualTo = 1)
assert(ayOther[2], isEqualTo = 0)
aoriginOther = myOtherPlane.origin
assert(aoriginOther[0], isEqualTo = 0)
assert(aoriginOther[1], isEqualTo = 0)
assert(aoriginOther[2], isEqualTo = 0)
// Define a plane using a plane-modifying function like offsetPlane.
myAlternatePlane = offsetPlane(XY, offset = 0)
// Prove we can get its axes and origin.
axAlternate = myAlternatePlane.xAxis
assert(axAlternate[0], isEqualTo = 1)
assert(axAlternate[1], isEqualTo = 0)
assert(axAlternate[2], isEqualTo = 0)
ayAlternate = myAlternatePlane.yAxis
assert(ayAlternate[0], isEqualTo = 0)
assert(ayAlternate[1], isEqualTo = 1)
assert(ayAlternate[2], isEqualTo = 0)
aoriginAlternate = myAlternatePlane.origin
assert(aoriginAlternate[0], isEqualTo = 0)
assert(aoriginAlternate[1], isEqualTo = 0)
assert(aoriginAlternate[2], isEqualTo = 0)

View File

@ -65,8 +65,6 @@ import { useModelingContext } from '@src/hooks/useModelingContext'
import { xStateValueToString } from '@src/lib/xStateValueToString'
import { getSelectionTypeDisplayText } from '@src/lib/selections'
import type { StatusBarItemType } from '@src/components/StatusBar/statusBarTypes'
import { UndoRedoButtons } from '@src/components/UndoRedoButtons'
import { Toolbar } from '@src/Toolbar'
// CYCLIC REF
sceneInfra.camControls.engineStreamActor = engineStreamActor
@ -248,24 +246,15 @@ export function App() {
return (
<div className="h-screen flex flex-col overflow-hidden select-none">
<div className="relative flex flex-1 flex-col">
<div className="relative flex items-center flex-col">
<AppHeader
className="transition-opacity transition-duration-75"
project={{ project, file }}
enableMenu={true}
nativeFileMenuCreated={nativeFileMenuCreated}
projectMenuChildren={
<UndoRedoButtons
editorManager={editorManager}
className="flex items-center px-2 border-x border-chalkboard-30 dark:border-chalkboard-80"
/>
}
>
<CommandBarOpenButton />
<ShareButton />
</AppHeader>
<Toolbar />
</div>
<AppHeader
className="transition-opacity transition-duration-75"
project={{ project, file }}
enableMenu={true}
nativeFileMenuCreated={nativeFileMenuCreated}
>
<CommandBarOpenButton />
<ShareButton />
</AppHeader>
<ModalContainer />
<ModelingSidebar />
<EngineStream pool={pool} authToken={authToken} />

View File

@ -203,7 +203,7 @@ export function Toolbar({
<menu
data-current-mode={currentMode}
data-onboarding-id="toolbar"
className="z-[19] max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-30 dark:border-chalkboard-80 border-t-0 shadow-sm"
className="max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-30 dark:border-chalkboard-80 border-t-0 shadow-sm"
>
<ul
{...props}

View File

@ -3,6 +3,7 @@
in Tailwind, such as complex grid layouts.
*/
.header {
grid-template-columns: 1fr auto 1fr;
user-select: none;
-webkit-user-select: none;
}

View File

@ -1,30 +1,30 @@
import { Toolbar } from '@src/Toolbar'
import { CommandBarOpenButton } from '@src/components/CommandBarOpenButton'
import ProjectSidebarMenu from '@src/components/ProjectSidebarMenu'
import UserSidebarMenu from '@src/components/UserSidebarMenu'
import { isDesktop } from '@src/lib/isDesktop'
import type { IndexLoaderData } from '@src/lib/types'
import { type IndexLoaderData } from '@src/lib/types'
import { useUser } from '@src/lib/singletons'
import styles from './AppHeader.module.css'
import type { ReactNode } from 'react'
interface AppHeaderProps extends React.PropsWithChildren {
showToolbar?: boolean
project?: Omit<IndexLoaderData, 'code'>
className?: string
enableMenu?: boolean
style?: React.CSSProperties
nativeFileMenuCreated: boolean
projectMenuChildren?: ReactNode
}
export const AppHeader = ({
showToolbar = true,
project,
children,
className = '',
style,
enableMenu = false,
nativeFileMenuCreated,
projectMenuChildren,
}: AppHeaderProps) => {
const user = useUser()
@ -32,9 +32,14 @@ export const AppHeader = ({
<header
id="app-header"
data-testid="app-header"
className={`w-full flex ${styles.header || ''} ${
isDesktop() ? styles.desktopApp : ''
} overlaid-panes sticky top-0 z-20 px-2 justify-between ${className || ''} bg-chalkboard-10 dark:bg-chalkboard-90 border-b border-chalkboard-30 dark:border-chalkboard-70`}
className={
'w-full grid ' +
styles.header +
` ${
isDesktop() ? styles.desktopApp + ' ' : ''
}overlaid-panes sticky top-0 z-20 px-2 items-start ` +
className
}
data-native-file-menu={nativeFileMenuCreated}
style={style}
>
@ -42,9 +47,13 @@ export const AppHeader = ({
enableMenu={enableMenu}
project={project?.project}
file={project?.file}
>
{projectMenuChildren}
</ProjectSidebarMenu>
/>
{/* Toolbar if the context deems it */}
<div className="flex flex-col items-center gap-2">
<div className="flex-grow flex justify-center max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
{showToolbar && <Toolbar />}
</div>
</div>
<div className="flex items-center gap-2 py-1 ml-auto">
{/* If there are children, show them, otherwise show User menu */}
{children || <CommandBarOpenButton />}

View File

@ -10,13 +10,13 @@ export function CommandBarOpenButton() {
return (
<button
type="button"
className="flex gap-1 items-center py-0 pl-0.5 pr-1 sm:pr-0.5 m-0 text-primary dark:text-inherit bg-chalkboard-10/80 dark:bg-chalkboard-100/50 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100 border border-solid border-primary/50 hover:border-primary active:border-primary"
className="flex gap-1 items-center py-0 px-0.5 m-0 text-primary dark:text-inherit bg-chalkboard-10/80 dark:bg-chalkboard-100/50 hover:bg-chalkboard-10 dark:hover:bg-chalkboard-100 border border-solid border-primary/50 hover:border-primary active:border-primary"
onClick={() => commandBarActor.send({ type: 'Open' })}
data-testid="command-bar-open-button"
>
<CustomIcon name="command" className="w-5 h-5" />
<span>Commands</span>
<kbd className="hidden sm:block dark:bg-chalkboard-80 font-mono rounded-sm text-primary/70 dark:text-inherit inline-block px-1">
<kbd className="dark:bg-chalkboard-80 font-mono rounded-sm text-primary/70 dark:text-inherit inline-block px-1">
{hotkeyDisplay(COMMAND_PALETTE_HOTKEY, platform)}
</kbd>
</button>

View File

@ -76,21 +76,6 @@ const CustomIconMap = {
/>
</svg>
),
arrowRotateLeft: (
<svg
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="arrow rotate left"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.83087 7.59684L4.83087 8.09684L5.33087 8.09684L9.5378 8.09684L9.5378 7.09684L6.56183 7.09684C7.02568 6.54751 7.61622 6.11526 8.28567 5.83941C9.11759 5.49662 10.0332 5.41109 10.9142 5.59387C11.7952 5.77666 12.6012 6.21935 13.2281 6.8648C13.855 7.51025 14.274 8.32881 14.431 9.21478C14.588 10.1008 14.4758 11.0134 14.1089 11.835C13.742 12.6566 13.1373 13.3493 12.3727 13.8237C11.6082 14.2981 10.719 14.5325 9.8199 14.4964C8.92084 14.4604 8.05323 14.1557 7.32909 13.6216L6.73554 14.4264C7.6206 15.0792 8.68102 15.4516 9.77986 15.4956C10.8787 15.5397 11.9655 15.2533 12.9 14.6734C13.8344 14.0936 14.5736 13.2469 15.022 12.2428C15.4705 11.2386 15.6076 10.1231 15.4157 9.04027C15.2238 7.95742 14.7116 6.95696 13.9454 6.16808C13.1792 5.3792 12.1941 4.83812 11.1173 4.61472C10.0405 4.39132 8.92149 4.49586 7.9047 4.91483C7.10231 5.24545 6.39268 5.7599 5.83087 6.41286L5.83087 3.38998L4.83087 3.38998L4.83087 7.59684Z"
fill="currentColor"
/>
</svg>
),
arrowRotateRight: (
<svg
viewBox="0 0 20 20"

View File

@ -131,7 +131,7 @@ export function ModelingSidebar() {
id: 'refresh',
title: 'Refresh app',
sidebarName: 'Refresh app',
icon: 'exclamationMark',
icon: 'arrowRotateRight',
keybinding: 'Mod + R',
// eslint-disable-next-line @typescript-eslint/no-misused-promises
action: async () => {

View File

@ -17,39 +17,40 @@ import { APP_NAME } from '@src/lib/constants'
import { isDesktop } from '@src/lib/isDesktop'
import { PATHS } from '@src/lib/paths'
import { engineCommandManager, kclManager } from '@src/lib/singletons'
import type { IndexLoaderData } from '@src/lib/types'
import { type IndexLoaderData } from '@src/lib/types'
import { commandBarActor } from '@src/lib/singletons'
interface ProjectSidebarMenuProps extends React.PropsWithChildren {
enableMenu?: boolean
project?: IndexLoaderData['project']
file?: IndexLoaderData['file']
}
const ProjectSidebarMenu = ({
project,
file,
enableMenu = false,
children,
}: ProjectSidebarMenuProps) => {
}: {
enableMenu?: boolean
project?: IndexLoaderData['project']
file?: IndexLoaderData['file']
}) => {
// Make room for traffic lights on desktop left side.
// TODO: make sure this doesn't look like shit on Linux or Windows
const trafficLightsOffset =
isDesktop() && window.electron.os.isMac ? 'ml-20' : ''
return (
<div className={'!no-underline flex gap-2 ' + trafficLightsOffset}>
<div
className={
'!no-underline h-full mr-auto max-h-min min-h-12 min-w-max flex items-center gap-2 ' +
trafficLightsOffset
}
>
<AppLogoLink project={project} file={file} />
{enableMenu ? (
<ProjectMenuPopover project={project} file={file} />
) : (
<span
className="hidden self-center px-2 select-none cursor-default text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
className="hidden select-none cursor-default text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
data-testid="project-name"
>
{project?.name ? project.name : APP_NAME}
</span>
)}
{children}
</div>
)
}
@ -63,7 +64,7 @@ function AppLogoLink({
}) {
const { onProjectClose } = useLspContext()
const wrapperClassName =
"relative h-full grid flex-none place-content-center group p-1.5 before:block before:content-[''] before:absolute before:inset-0 before:bottom-1 before:z-[-1] before:bg-primary before:rounded-b-sm"
"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 before:rounded-b-sm"
const logoClassName = 'w-auto h-4 text-chalkboard-10'
return isDesktop() ? (
@ -237,23 +238,12 @@ function ProjectMenuPopover({
return (
<Popover className="relative">
<Popover.Button
className="gap-1 rounded-sm mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center focus-visible:outline-appForeground dark:hover:bg-chalkboard-90"
className="gap-1 rounded-sm h-9 mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center focus-visible:outline-appForeground dark:hover:bg-chalkboard-90"
data-testid="project-sidebar-toggle"
>
<div className="flex items-baseline py-0.5 text-sm gap-1">
{isDesktop() && project?.name && (
<>
<span
className="hidden whitespace-nowrap md:block"
data-testid="app-header-project-name"
>
{project.name}
</span>
<span className="hidden md:block">/</span>
</>
)}
<div className="flex flex-col items-start py-0.5">
<span
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap"
className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
data-testid="app-header-file-name"
>
{isDesktop() && file?.name
@ -262,6 +252,14 @@ function ProjectMenuPopover({
)
: APP_NAME}
</span>
{isDesktop() && project?.name && (
<span
className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block"
data-testid="app-header-project-name"
>
{project.name}
</span>
)}
</div>
<CustomIcon
name="caretDown"

View File

@ -83,7 +83,7 @@ export const ShareButton = memo(function ShareButton() {
billingContext.tier === undefined
return (
<Popover className="relative hidden sm:flex">
<Popover className="relative flex">
<Popover.Button
as="div"
className="relative group border-0 w-fit min-w-max p-0 rounded-l-full focus-visible:outline-appForeground"

View File

@ -1,50 +0,0 @@
import type EditorManager from '@src/editor/manager'
import usePlatform from '@src/hooks/usePlatform'
import type { HTMLProps } from 'react'
import { CustomIcon } from '@src/components/CustomIcon'
import Tooltip from '@src/components/Tooltip'
import { hotkeyDisplay } from '@src/lib/hotkeyWrapper'
export function UndoRedoButtons({
editorManager,
...props
}: HTMLProps<HTMLDivElement> & { editorManager: EditorManager }) {
const platform = usePlatform()
return (
<div {...props}>
<button
type="button"
onClick={() => editorManager.undo()}
className="p-0 m-0 border-transparent dark:border-transparent focus-visible:border-chalkboard-100"
>
<CustomIcon name="arrowRotateLeft" className="w-6 h-6" />
<Tooltip
position="bottom"
contentClassName="text-sm max-w-none flex items-center gap-4"
>
<span>Undo</span>
<kbd className="hotkey capitalize">
{hotkeyDisplay('mod+z', platform)}
</kbd>
</Tooltip>
</button>
<button
type="button"
onClick={() => editorManager.redo()}
className="p-0 m-0 border-transparent dark:border-transparent focus-visible:border-chalkboard-100"
>
<CustomIcon name="arrowRotateRight" className="w-6 h-6" />
<Tooltip
position="bottom"
contentClassName="text-sm max-w-none flex items-center gap-4"
>
<span>Redo</span>
<kbd className="hotkey capitalize">
{hotkeyDisplay('mod+shift+z', platform)}
</kbd>
</Tooltip>
</button>
</div>
)
}

View File

@ -178,9 +178,9 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
}
return (
<Popover className="relative grid">
<Popover className="relative">
<Popover.Button
className="m-0 relative group border-0 w-fit min-w-max p-0 rounded-l-full rounded-r focus-visible:outline-appForeground"
className="relative group border-0 w-fit min-w-max p-0 rounded-l-full focus-visible:outline-appForeground"
data-testid="user-sidebar-toggle"
>
<div className="flex items-center">

View File

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

View File

@ -185,22 +185,6 @@ export const interactionMap: Record<
'Available when a file or folder is selected in the file tree.',
},
],
Miscellaneous: [
{
name: 'undo',
sequence: `${PRIMARY}+Z`,
title: 'Undo',
description:
'Available while modeling and writing code. Currently only steps back in modeling history, or code history.',
},
{
name: 'redo',
sequence: `${PRIMARY}+Shift+Z`,
title: 'Redo',
description:
'Available while modeling and writing code. Currently only steps forward in modeling history, or code history.',
},
],
}
/**

View File

@ -1,5 +1,10 @@
import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL, VITE_KC_DEV_TOKEN } from '@src/env'
import {
DEV,
VITE_KC_API_BASE_URL,
VITE_KC_DEV_TOKEN,
VITE_KC_SKIP_AUTH,
} from '@src/env'
import { assign, fromPromise, setup } from 'xstate'
import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants'
@ -16,6 +21,26 @@ import {
} from '@src/lib/withBaseURL'
import { ACTOR_IDS } from '@src/machines/machineConstants'
const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
const LOCAL_USER: Models['User_type'] = {
id: '8675309',
name: 'Test User',
email: 'kittycad.sidebar.test@example.com',
image: 'https://placekitten.com/200/200',
created_at: 'yesteryear',
updated_at: 'today',
company: 'Test Company',
discord: 'Test User#1234',
github: 'testuser',
phone: '555-555-5555',
first_name: 'Test',
last_name: 'User',
can_train_on_data: false,
is_service_account: false,
deletion_scheduled: false,
}
export interface UserContext {
user?: Models['User_type']
token: string
@ -140,6 +165,19 @@ async function getUser(input: { token?: string }) {
if (!token && isDesktop()) return Promise.reject(new Error('No token found'))
if (token) headers['Authorization'] = `Bearer ${token}`
if (SKIP_AUTH) {
// For local tests
if (localStorage.getItem('FORCE_NO_IMAGE')) {
LOCAL_USER.image = ''
}
markOnce('code/didAuth')
return {
user: LOCAL_USER,
token,
}
}
const userPromise = isDesktop()
? getUserDesktop(token, VITE_KC_API_BASE_URL)
: fetch(url, {

View File

@ -75,6 +75,7 @@ process.env.VITE_KC_API_WS_MODELING_URL ??= viteEnv.VITE_KC_API_WS_MODELING_URL
process.env.VITE_KC_API_BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL
process.env.VITE_KC_SITE_BASE_URL ??= viteEnv.VITE_KC_SITE_BASE_URL
process.env.VITE_KC_SITE_APP_URL ??= viteEnv.VITE_KC_SITE_APP_URL
process.env.VITE_KC_SKIP_AUTH ??= viteEnv.VITE_KC_SKIP_AUTH
process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??=
viteEnv.VITE_KC_CONNECTION_TIMEOUT_MS

View File

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

View File

@ -221,7 +221,10 @@ const Home = () => {
return (
<div className="relative flex flex-col items-stretch h-screen w-screen overflow-hidden">
<AppHeader nativeFileMenuCreated={nativeFileMenuCreated} />
<AppHeader
nativeFileMenuCreated={nativeFileMenuCreated}
showToolbar={false}
/>
<div className="overflow-hidden self-stretch w-full flex-1 home-layout max-w-4xl lg:max-w-5xl xl:max-w-7xl px-4 mx-auto mt-8 lg:mt-24 lg:px-0">
<HomeHeader
setQuery={setQuery}