Compare commits
12 Commits
jtran/plus
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
176774295e | |||
7e64054a8c | |||
1b02104614 | |||
66d0556389 | |||
d5d8322ae6 | |||
938eb749ad | |||
6d4127f0ef | |||
d592794ce3 | |||
2a6904d37c | |||
de33a3a854 | |||
4f4c44e7c7 | |||
1b75020686 |
@ -7,7 +7,6 @@ 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"
|
||||
|
@ -3,5 +3,4 @@ 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
|
||||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
1
interface.d.ts
vendored
@ -79,7 +79,6 @@ 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
|
||||
|
@ -994,6 +994,39 @@ 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())
|
||||
@ -1013,7 +1046,22 @@ impl Node<MemberExpression> {
|
||||
vec![self.clone().into()],
|
||||
)))
|
||||
}
|
||||
(KclValue::Object { .. }, p, _) => {
|
||||
(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());
|
||||
}
|
||||
let t = p.type_name();
|
||||
let article = article_for(t);
|
||||
Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
@ -2205,4 +2253,12 @@ 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();
|
||||
}
|
||||
}
|
||||
|
@ -921,6 +921,12 @@ 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 {
|
||||
|
@ -458,6 +458,31 @@ 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),
|
||||
|
60
rust/kcl-lib/tests/inputs/planestuff.kcl
Normal file
@ -0,0 +1,60 @@
|
||||
// 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)
|
29
src/App.tsx
@ -65,6 +65,8 @@ 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
|
||||
@ -246,15 +248,24 @@ export function App() {
|
||||
return (
|
||||
<div className="h-screen flex flex-col overflow-hidden select-none">
|
||||
<div className="relative flex flex-1 flex-col">
|
||||
<AppHeader
|
||||
className="transition-opacity transition-duration-75"
|
||||
project={{ project, file }}
|
||||
enableMenu={true}
|
||||
nativeFileMenuCreated={nativeFileMenuCreated}
|
||||
>
|
||||
<CommandBarOpenButton />
|
||||
<ShareButton />
|
||||
</AppHeader>
|
||||
<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>
|
||||
<ModalContainer />
|
||||
<ModelingSidebar />
|
||||
<EngineStream pool={pool} authToken={authToken} />
|
||||
|
@ -203,7 +203,7 @@ export function Toolbar({
|
||||
<menu
|
||||
data-current-mode={currentMode}
|
||||
data-onboarding-id="toolbar"
|
||||
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"
|
||||
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"
|
||||
>
|
||||
<ul
|
||||
{...props}
|
||||
|
@ -3,7 +3,6 @@
|
||||
in Tailwind, such as complex grid layouts.
|
||||
*/
|
||||
.header {
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
@ -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,14 +32,9 @@ export const AppHeader = ({
|
||||
<header
|
||||
id="app-header"
|
||||
data-testid="app-header"
|
||||
className={
|
||||
'w-full grid ' +
|
||||
styles.header +
|
||||
` ${
|
||||
isDesktop() ? styles.desktopApp + ' ' : ''
|
||||
}overlaid-panes sticky top-0 z-20 px-2 items-start ` +
|
||||
className
|
||||
}
|
||||
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`}
|
||||
data-native-file-menu={nativeFileMenuCreated}
|
||||
style={style}
|
||||
>
|
||||
@ -47,13 +42,9 @@ export const AppHeader = ({
|
||||
enableMenu={enableMenu}
|
||||
project={project?.project}
|
||||
file={project?.file}
|
||||
/>
|
||||
{/* 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>
|
||||
>
|
||||
{projectMenuChildren}
|
||||
</ProjectSidebarMenu>
|
||||
<div className="flex items-center gap-2 py-1 ml-auto">
|
||||
{/* If there are children, show them, otherwise show User menu */}
|
||||
{children || <CommandBarOpenButton />}
|
||||
|
@ -10,13 +10,13 @@ export function CommandBarOpenButton() {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
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"
|
||||
onClick={() => commandBarActor.send({ type: 'Open' })}
|
||||
data-testid="command-bar-open-button"
|
||||
>
|
||||
<CustomIcon name="command" className="w-5 h-5" />
|
||||
<span>Commands</span>
|
||||
<kbd className="dark:bg-chalkboard-80 font-mono rounded-sm text-primary/70 dark:text-inherit inline-block px-1">
|
||||
<kbd className="hidden sm:block 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>
|
||||
|
@ -76,6 +76,21 @@ 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"
|
||||
|
@ -131,7 +131,7 @@ export function ModelingSidebar() {
|
||||
id: 'refresh',
|
||||
title: 'Refresh app',
|
||||
sidebarName: 'Refresh app',
|
||||
icon: 'arrowRotateRight',
|
||||
icon: 'exclamationMark',
|
||||
keybinding: 'Mod + R',
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
action: async () => {
|
||||
|
@ -17,40 +17,39 @@ 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,
|
||||
}: {
|
||||
enableMenu?: boolean
|
||||
project?: IndexLoaderData['project']
|
||||
file?: IndexLoaderData['file']
|
||||
}) => {
|
||||
children,
|
||||
}: ProjectSidebarMenuProps) => {
|
||||
// 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 h-full mr-auto max-h-min min-h-12 min-w-max flex items-center gap-2 ' +
|
||||
trafficLightsOffset
|
||||
}
|
||||
>
|
||||
<div className={'!no-underline flex gap-2 ' + trafficLightsOffset}>
|
||||
<AppLogoLink project={project} file={file} />
|
||||
{enableMenu ? (
|
||||
<ProjectMenuPopover project={project} file={file} />
|
||||
) : (
|
||||
<span
|
||||
className="hidden select-none cursor-default text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
|
||||
className="hidden self-center px-2 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>
|
||||
)
|
||||
}
|
||||
@ -64,7 +63,7 @@ function AppLogoLink({
|
||||
}) {
|
||||
const { onProjectClose } = useLspContext()
|
||||
const wrapperClassName =
|
||||
"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"
|
||||
"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"
|
||||
const logoClassName = 'w-auto h-4 text-chalkboard-10'
|
||||
|
||||
return isDesktop() ? (
|
||||
@ -238,12 +237,23 @@ function ProjectMenuPopover({
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<Popover.Button
|
||||
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"
|
||||
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"
|
||||
data-testid="project-sidebar-toggle"
|
||||
>
|
||||
<div className="flex flex-col items-start py-0.5">
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
<span
|
||||
className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
|
||||
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap"
|
||||
data-testid="app-header-file-name"
|
||||
>
|
||||
{isDesktop() && file?.name
|
||||
@ -252,14 +262,6 @@ 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"
|
||||
|
@ -83,7 +83,7 @@ export const ShareButton = memo(function ShareButton() {
|
||||
billingContext.tier === undefined
|
||||
|
||||
return (
|
||||
<Popover className="relative flex">
|
||||
<Popover className="relative hidden sm:flex">
|
||||
<Popover.Button
|
||||
as="div"
|
||||
className="relative group border-0 w-fit min-w-max p-0 rounded-l-full focus-visible:outline-appForeground"
|
||||
|
50
src/components/UndoRedoButtons.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
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>
|
||||
)
|
||||
}
|
@ -178,9 +178,9 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<Popover className="relative grid">
|
||||
<Popover.Button
|
||||
className="relative group border-0 w-fit min-w-max p-0 rounded-l-full focus-visible:outline-appForeground"
|
||||
className="m-0 relative group border-0 w-fit min-w-max p-0 rounded-l-full rounded-r focus-visible:outline-appForeground"
|
||||
data-testid="user-sidebar-toggle"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
|
@ -11,7 +11,6 @@ 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
|
||||
|
@ -185,6 +185,22 @@ 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.',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,10 +1,5 @@
|
||||
import type { Models } from '@kittycad/lib'
|
||||
import {
|
||||
DEV,
|
||||
VITE_KC_API_BASE_URL,
|
||||
VITE_KC_DEV_TOKEN,
|
||||
VITE_KC_SKIP_AUTH,
|
||||
} from '@src/env'
|
||||
import { VITE_KC_API_BASE_URL, VITE_KC_DEV_TOKEN } from '@src/env'
|
||||
import { assign, fromPromise, setup } from 'xstate'
|
||||
|
||||
import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants'
|
||||
@ -21,26 +16,6 @@ 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
|
||||
@ -165,19 +140,6 @@ 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, {
|
||||
|
@ -75,7 +75,6 @@ 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
|
||||
|
||||
|
@ -292,7 +292,6 @@ 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',
|
||||
|
||||
|
@ -221,10 +221,7 @@ const Home = () => {
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col items-stretch h-screen w-screen overflow-hidden">
|
||||
<AppHeader
|
||||
nativeFileMenuCreated={nativeFileMenuCreated}
|
||||
showToolbar={false}
|
||||
/>
|
||||
<AppHeader nativeFileMenuCreated={nativeFileMenuCreated} />
|
||||
<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}
|
||||
|