diff --git a/package.json b/package.json
index bdb562ac4..102b9ece4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "untitled-app",
- "version": "0.0.4",
+ "version": "0.0.3",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.2",
@@ -33,7 +33,6 @@
"react-router-dom": "^6.14.2",
"sketch-helpers": "^0.0.4",
"swr": "^2.0.4",
- "tauri-plugin-fs-extra-api": "https://github.com/tauri-apps/tauri-plugin-fs-extra#v1",
"toml": "^3.0.0",
"ts-node": "^10.9.1",
"typescript": "^4.4.2",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index ac81a9e97..c485d137b 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -81,7 +81,6 @@ dependencies = [
"serde_json",
"tauri",
"tauri-build",
- "tauri-plugin-fs-extra",
"tokio",
"toml 0.6.0",
]
@@ -3131,18 +3130,6 @@ dependencies = [
"tauri-utils",
]
-[[package]]
-name = "tauri-plugin-fs-extra"
-version = "0.0.0"
-source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#7e58dc8502f654b99d51c087421f84ccc0e03119"
-dependencies = [
- "log",
- "serde",
- "serde_json",
- "tauri",
- "thiserror",
-]
-
[[package]]
name = "tauri-runtime"
version = "0.13.0"
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 364786f91..35b020801 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -19,10 +19,9 @@ anyhow = "1"
oauth2 = "4.4.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
-tauri = { version = "1.3.0", features = [ "updater", "path-all", "dialog-all", "fs-all", "http-request", "shell-open", "shell-open-api"] }
+tauri = { version = "1.3.0", features = [ "updater", "dialog-all", "fs-all", "http-request", "shell-open", "shell-open-api"] }
tokio = { version = "1.29.1", features = ["time"] }
toml = "0.6.0"
-tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 760b302a6..f7581221e 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -98,7 +98,6 @@ fn main() {
Ok(())
})
.invoke_handler(tauri::generate_handler![login, read_toml, read_txt_file])
- .plugin(tauri_plugin_fs_extra::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 83235b0e9..4e6b8e2c6 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -8,7 +8,7 @@
},
"package": {
"productName": "KittyCAD Modeling",
- "version": "0.0.4"
+ "version": "0.0.3"
},
"tauri": {
"allowlist": {
@@ -38,9 +38,6 @@
},
"shell": {
"open": true
- },
- "path": {
- "all": true
}
},
"bundle": {
diff --git a/src/App.test.tsx b/src/App.test.tsx
index 9548e99b7..324db079e 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -1,6 +1,5 @@
import { render, screen } from '@testing-library/react'
import { App } from './App'
-import { describe, test, vi } from 'vitest'
import { BrowserRouter } from 'react-router-dom'
let listener: ((rect: any) => void) | undefined = undefined
@@ -13,27 +12,12 @@ let listener: ((rect: any) => void) | undefined = undefined
disconnect() {}
}
-describe('App tests', () => {
- test('Renders the modeling app screen, including "Variables" pane.', () => {
- vi.mock('react-router-dom', async () => {
- const actual = (await vi.importActual('react-router-dom')) as Record<
- string,
- any
- >
- return {
- ...actual,
- useParams: () => ({ id: 'new' }),
- useLoaderData: () => ({ code: null }),
- }
- })
- render(
-
-
-
- )
- const linkElement = screen.getByText(/Variables/i)
- expect(linkElement).toBeInTheDocument()
-
- vi.restoreAllMocks()
- })
+test('renders learn react link', () => {
+ render(
+
+
+
+ )
+ const linkElement = screen.getByText(/Variables/i)
+ expect(linkElement).toBeInTheDocument()
})
diff --git a/src/App.tsx b/src/App.tsx
index 1ec9a9679..5535bbdcd 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -43,16 +43,8 @@ import { useHotkeys } from 'react-hotkeys-hook'
import { TEST } from './env'
import { getNormalisedCoordinates } from './lib/utils'
import { getSystemTheme } from './lib/getSystemTheme'
-import { isTauri } from './lib/isTauri'
-import { useLoaderData, useParams } from 'react-router-dom'
-import { writeTextFile } from '@tauri-apps/api/fs'
-import { FILE_EXT, PROJECT_ENTRYPOINT } from './lib/tauriFS'
-import { IndexLoaderData } from './Router'
-import { toast } from 'react-hot-toast'
export function App() {
- const { code: loadedCode } = useLoaderData() as IndexLoaderData
- const pathParams = useParams()
const streamRef = useRef(null)
useHotKeyListener()
const {
@@ -159,34 +151,9 @@ export function App() {
? 'opacity-40'
: ''
- // Use file code loaded from disk
- // on mount, and overwrite any locally-stored code
- useEffect(() => {
- if (isTauri() && loadedCode !== null) {
- setCode(loadedCode)
- }
- return () => {
- // Clear code on unmount if in desktop app
- if (isTauri()) {
- setCode('')
- }
- }
- }, [loadedCode, setCode])
-
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => {
setCode(value)
- if (isTauri() && pathParams.id) {
- // Save the file to disk
- // Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
- writeTextFile(pathParams.id + '/' + PROJECT_ENTRYPOINT, value).catch(
- (err) => {
- // TODO: add Sentry per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
- console.error('error saving file', err)
- toast.error('Error saving file, please check file permissions')
- }
- )
- }
if (editorView) {
editorView?.dispatch({ effects: addLineHighlight.of([0, 0]) })
}
@@ -446,11 +413,6 @@ export function App() {
paneOpacity +
(isMouseDownInStream ? ' pointer-events-none' : '')
}
- filename={
- pathParams.id
- ?.slice(pathParams.id.lastIndexOf('/') + 1)
- .replace(FILE_EXT, '') || ''
- }
/>
) => (prepend: string) => {
@@ -36,34 +26,16 @@ const prependRoutes =
export const paths = {
INDEX: '/',
- HOME: '/home',
- FILE: '/file',
SETTINGS: '/settings',
SIGN_IN: '/signin',
ONBOARDING: prependRoutes(onboardingPaths)(
- '/onboarding'
+ '/onboarding/'
) as typeof onboardingPaths,
}
-export type IndexLoaderData = {
- code: string | null
-}
-
-export type ProjectWithEntryPointMetadata = FileEntry & {
- entrypoint_metadata: Metadata
-}
-export type HomeLoaderData = {
- projects: ProjectWithEntryPointMetadata[]
-}
-
const router = createBrowserRouter([
{
path: paths.INDEX,
- loader: () =>
- isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
- },
- {
- path: paths.FILE + '/:id',
element: (
@@ -71,10 +43,7 @@ const router = createBrowserRouter([
),
errorElement: ,
- loader: async ({
- request,
- params,
- }): Promise => {
+ loader: ({ request }) => {
const store = localStorage.getItem('store')
if (store === null) {
return redirect(paths.ONBOARDING.INDEX)
@@ -91,72 +60,23 @@ const router = createBrowserRouter([
notEnRouteToOnboarding && hasValidOnboardingStatus
if (shouldRedirectToOnboarding) {
- return redirect(makeUrlPathRelative(paths.ONBOARDING.INDEX) + status)
+ return redirect(paths.ONBOARDING.INDEX + status)
}
}
-
- if (params.id && params.id !== 'new') {
- // Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
- const code = await readTextFile(params.id + '/' + PROJECT_ENTRYPOINT)
-
- return {
- code,
- }
- }
-
- return {
- code: '',
- }
+ return null
},
children: [
{
- path: makeUrlPathRelative(paths.SETTINGS),
+ path: paths.SETTINGS,
element: ,
},
{
- path: makeUrlPathRelative(paths.ONBOARDING.INDEX),
+ path: paths.ONBOARDING.INDEX,
element: ,
children: onboardingRoutes,
},
],
},
- {
- path: paths.HOME,
- element: (
-
-
-
-
- ),
- loader: async () => {
- if (!isTauri()) {
- return redirect(paths.FILE + '/new')
- }
-
- const projectDir = await initializeProjectDirectory()
- const projectsNoMeta = (await readDir(projectDir.dir)).filter(
- isProjectDirectory
- )
- const projects = await Promise.all(
- projectsNoMeta.map(async (p) => ({
- entrypoint_metadata: await metadata(
- p.path + '/' + PROJECT_ENTRYPOINT
- ),
- ...p,
- }))
- )
-
- return {
- projects,
- }
- },
- children: [
- {
- path: makeUrlPathRelative(paths.SETTINGS),
- element: ,
- },
- ],
- },
{
path: paths.SIGN_IN,
element: ,
diff --git a/src/components/ActionButton.tsx b/src/components/ActionButton.tsx
index be0dc45f4..e1f0f8eb2 100644
--- a/src/components/ActionButton.tsx
+++ b/src/components/ActionButton.tsx
@@ -1,92 +1,52 @@
+import { Link } from 'react-router-dom'
import { ActionIcon, ActionIconProps } from './ActionIcon'
import React from 'react'
import { paths } from '../Router'
-import { Link } from 'react-router-dom'
-import type { LinkProps } from 'react-router-dom'
-interface BaseActionButtonProps {
+interface ActionButtonProps extends React.PropsWithChildren {
icon?: ActionIconProps
className?: string
+ onClick?: () => void
+ to?: string
+ Element?:
+ | 'button'
+ | 'link'
+ | React.ComponentType>
}
-type ActionButtonAsButton = BaseActionButtonProps &
- Omit<
- React.ButtonHTMLAttributes,
- keyof BaseActionButtonProps
- > & {
- Element: 'button'
- }
-
-type ActionButtonAsLink = BaseActionButtonProps &
- Omit & {
- Element: 'link'
- }
-
-type ActionButtonAsExternal = BaseActionButtonProps &
- Omit<
- React.AnchorHTMLAttributes,
- keyof BaseActionButtonProps
- > & {
- Element: 'externalLink'
- }
-
-type ActionButtonAsElement = BaseActionButtonProps &
- Omit, keyof BaseActionButtonProps> & {
- Element: React.ComponentType>
- }
-
-type ActionButtonProps =
- | ActionButtonAsButton
- | ActionButtonAsLink
- | ActionButtonAsExternal
- | ActionButtonAsElement
-
-export const ActionButton = (props: ActionButtonProps) => {
+export const ActionButton = ({
+ icon,
+ className,
+ onClick,
+ to = paths.INDEX,
+ Element = 'button',
+ children,
+ ...props
+}: ActionButtonProps) => {
const classNames = `group mono text-base flex items-center gap-2 rounded-sm border border-chalkboard-40 dark:border-chalkboard-60 hover:border-liquid-40 dark:hover:bg-chalkboard-90 p-[3px] text-chalkboard-110 dark:text-chalkboard-10 hover:text-chalkboard-110 hover:dark:text-chalkboard-10 ${
- props.icon ? 'pr-2' : 'px-2'
- } ${props.className || ''}`
+ icon ? 'pr-2' : 'px-2'
+ } ${className}`
- switch (props.Element) {
- case 'button': {
- // Note we have to destructure 'className' and 'Element' out of props
- // because we don't want to pass them to the button element;
- // the same is true for the other cases below.
- const { Element, icon, children, className, ...rest } = props
- return (
-
- )
- }
- case 'link': {
- const { Element, to, icon, children, className, ...rest } = props
- return (
-
- {icon && }
- {children}
-
- )
- }
- case 'externalLink': {
- const { Element, icon, children, className, ...rest } = props
- return (
-
- {icon && }
- {children}
-
- )
- }
- default: {
- const { Element, icon, children, className, ...rest } = props
- if (!Element) throw new Error('Element is required')
-
- return (
-
- {props.icon && }
- {children}
-
- )
- }
+ if (Element === 'button') {
+ return (
+
+ )
+ } else if (Element === 'link') {
+ return (
+
+ {icon && }
+ {children}
+
+ )
+ } else {
+ return (
+
+ {icon && }
+ {children}
+
+ )
}
}
diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx
index 85295d5c5..f1174cf01 100644
--- a/src/components/AppHeader.tsx
+++ b/src/components/AppHeader.tsx
@@ -3,17 +3,14 @@ import { Toolbar } from '../Toolbar'
import { useStore } from '../useStore'
import UserSidebarMenu from './UserSidebarMenu'
import { paths } from '../Router'
-import { isTauri } from '../lib/isTauri'
interface AppHeaderProps extends React.PropsWithChildren {
showToolbar?: boolean
- filename?: string
className?: string
}
export const AppHeader = ({
showToolbar = true,
- filename = '',
children,
className = '',
}: AppHeaderProps) => {
@@ -28,18 +25,13 @@ export const AppHeader = ({
className
}
>
-
+
-
- {isTauri() && filename ? filename : 'KittyCAD Modeling App'}
-
+ KittyCAD App
{/* Toolbar if the context deems it */}
{showToolbar && (
diff --git a/src/components/DebugPanel.tsx b/src/components/DebugPanel.tsx
index 3cbc2cc7c..3987556d3 100644
--- a/src/components/DebugPanel.tsx
+++ b/src/components/DebugPanel.tsx
@@ -73,7 +73,6 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
/>
{
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
diff --git a/src/components/ExportButton.tsx b/src/components/ExportButton.tsx
index af150b828..2d6a5d2e1 100644
--- a/src/components/ExportButton.tsx
+++ b/src/components/ExportButton.tsx
@@ -166,7 +166,6 @@ export const ExportButton = () => {
,
- f: ProjectWithEntryPointMetadata
- ) => Promise
- handleDeleteProject: (f: ProjectWithEntryPointMetadata) => Promise
-}) {
- useHotkeys('esc', () => setIsEditing(false))
- const [isEditing, setIsEditing] = useState(false)
- const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
-
- function handleSave(e: FormEvent) {
- e.preventDefault()
- handleRenameProject(e, project).then(() => setIsEditing(false))
- }
-
- function getDisplayedTime(date: Date) {
- const startOfToday = new Date()
- startOfToday.setHours(0, 0, 0, 0)
- return date.getTime() < startOfToday.getTime()
- ? date.toLocaleDateString()
- : date.toLocaleTimeString()
- }
-
- return (
-
- {isEditing ? (
-
- ) : (
- <>
-
-
- {project.name?.replace(FILE_EXT, '')}
-
-
- Edited {getDisplayedTime(project.entrypoint_metadata.modifiedAt)}
-
-
-
setIsEditing(true)}
- className="!p-0"
- />
- setIsConfirmingDelete(true)}
- />
-
-
-
- >
- )}
-
- )
-}
-
-export default ProjectCard
diff --git a/src/components/UserSidebarMenu.test.tsx b/src/components/UserSidebarMenu.test.tsx
index 014df2925..176e53e18 100644
--- a/src/components/UserSidebarMenu.test.tsx
+++ b/src/components/UserSidebarMenu.test.tsx
@@ -3,66 +3,64 @@ import { User } from '../useStore'
import UserSidebarMenu from './UserSidebarMenu'
import { BrowserRouter } from 'react-router-dom'
-describe('UserSidebarMenu tests', () => {
- test("Renders user's name and email if available", () => {
- const userWellFormed: User = {
- id: '8675309',
- name: 'Test User',
- email: 'kittycad.sidebar.test@example.com',
- image: 'https://placekitten.com/200/200',
- created_at: 'yesteryear',
- updated_at: 'today',
- }
+it("Renders user's name and email if available", () => {
+ const userWellFormed: User = {
+ id: '8675309',
+ name: 'Test User',
+ email: 'kittycad.sidebar.test@example.com',
+ image: 'https://placekitten.com/200/200',
+ created_at: 'yesteryear',
+ updated_at: 'today',
+ }
- render(
-
-
-
- )
+ render(
+
+
+
+ )
- fireEvent.click(screen.getByTestId('user-sidebar-toggle'))
+ fireEvent.click(screen.getByTestId('user-sidebar-toggle'))
- expect(screen.getByTestId('username')).toHaveTextContent(
- userWellFormed.name || ''
- )
- expect(screen.getByTestId('email')).toHaveTextContent(userWellFormed.email)
- })
-
- test("Renders just the user's email if no name is available", () => {
- const userNoName: User = {
- id: '8675309',
- email: 'kittycad.sidebar.test@example.com',
- image: 'https://placekitten.com/200/200',
- created_at: 'yesteryear',
- updated_at: 'today',
- }
-
- render(
-
-
-
- )
-
- fireEvent.click(screen.getByTestId('user-sidebar-toggle'))
-
- expect(screen.getByTestId('username')).toHaveTextContent(userNoName.email)
- })
-
- test('Renders a menu button if no user avatar is available', () => {
- const userNoAvatar: User = {
- id: '8675309',
- name: 'Test User',
- email: 'kittycad.sidebar.test@example.com',
- created_at: 'yesteryear',
- updated_at: 'today',
- }
-
- render(
-
-
-
- )
-
- expect(screen.getByTestId('user-sidebar-toggle')).toHaveTextContent('Menu')
- })
+ expect(screen.getByTestId('username')).toHaveTextContent(
+ userWellFormed.name || ''
+ )
+ expect(screen.getByTestId('email')).toHaveTextContent(userWellFormed.email)
+})
+
+it("Renders just the user's email if no name is available", () => {
+ const userNoName: User = {
+ id: '8675309',
+ email: 'kittycad.sidebar.test@example.com',
+ image: 'https://placekitten.com/200/200',
+ created_at: 'yesteryear',
+ updated_at: 'today',
+ }
+
+ render(
+
+
+
+ )
+
+ fireEvent.click(screen.getByTestId('user-sidebar-toggle'))
+
+ expect(screen.getByTestId('username')).toHaveTextContent(userNoName.email)
+})
+
+it('Renders a menu button if no user avatar is available', () => {
+ const userNoAvatar: User = {
+ id: '8675309',
+ name: 'Test User',
+ email: 'kittycad.sidebar.test@example.com',
+ created_at: 'yesteryear',
+ updated_at: 'today',
+ }
+
+ render(
+
+
+
+ )
+
+ expect(screen.getByTestId('user-sidebar-toggle')).toHaveTextContent('Menu')
})
diff --git a/src/components/UserSidebarMenu.tsx b/src/components/UserSidebarMenu.tsx
index 75c7952a3..4fe258728 100644
--- a/src/components/UserSidebarMenu.tsx
+++ b/src/components/UserSidebarMenu.tsx
@@ -6,7 +6,6 @@ import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { useNavigate } from 'react-router-dom'
import { useState } from 'react'
import { paths } from '../Router'
-import makeUrlPathRelative from '../lib/makeUrlPathRelative'
const UserSidebarMenu = ({ user }: { user?: User }) => {
const displayedName = getDisplayName(user)
@@ -97,14 +96,13 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
)}
{
// since /settings is a nested route the sidebar doesn't close
// automatically when navigating to it
close()
- navigate(makeUrlPathRelative(paths.SETTINGS))
+ navigate(paths.SETTINGS)
}}
>
Settings
diff --git a/src/index.css b/src/index.css
index 87cdc8cd1..f2f8b3ec9 100644
--- a/src/index.css
+++ b/src/index.css
@@ -32,7 +32,7 @@ body.dark {
}
::-webkit-scrollbar {
- @apply w-2 h-2 rounded-sm;
+ @apply w-2 rounded-sm;
@apply bg-chalkboard-20;
}
diff --git a/src/lib/makeUrlPathRelative.ts b/src/lib/makeUrlPathRelative.ts
deleted file mode 100644
index 90f04efd6..000000000
--- a/src/lib/makeUrlPathRelative.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function makeUrlPathRelative(path: string) {
- return path.replace(/^\//, '')
-}
diff --git a/src/lib/tauriFS.test.ts b/src/lib/tauriFS.test.ts
deleted file mode 100644
index 60b268686..000000000
--- a/src/lib/tauriFS.test.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import {
- MAX_PADDING,
- getNextProjectIndex,
- interpolateProjectNameWithIndex,
-} from './tauriFS'
-
-describe('Test file utility functions', () => {
- it('interpolates a project name without an index', () => {
- expect(interpolateProjectNameWithIndex('test', 1)).toBe('test')
- })
-
- it('interpolates a project name with an index and no padding', () => {
- expect(interpolateProjectNameWithIndex('test-$n', 2)).toBe('test-2')
- })
-
- it('interpolates a project name with an index and padding', () => {
- expect(interpolateProjectNameWithIndex('test-$nnn', 12)).toBe('test-012')
- })
-
- it('interpolates a project name with an index and max padding', () => {
- expect(interpolateProjectNameWithIndex('test-$nnnnnnnnnnn', 3)).toBe(
- `test-${'0'.repeat(MAX_PADDING)}3`
- )
- })
-
- const testFiles = [
- {
- name: 'new-project-04.kcl',
- path: '/projects/new-project-04.kcl',
- },
- {
- name: 'new-project-007.kcl',
- path: '/projects/new-project-007.kcl',
- },
- {
- name: 'new-project-05.kcl',
- path: '/projects/new-project-05.kcl',
- },
- {
- name: 'new-project-0.kcl',
- path: '/projects/new-project-0.kcl',
- },
- ]
-
- it('gets the correct next project index', () => {
- expect(getNextProjectIndex('new-project-$n', testFiles)).toBe(8)
- })
-})
diff --git a/src/lib/tauriFS.ts b/src/lib/tauriFS.ts
deleted file mode 100644
index 2f47d7fee..000000000
--- a/src/lib/tauriFS.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import { FileEntry, createDir, exists, writeTextFile } from '@tauri-apps/api/fs'
-import { documentDir } from '@tauri-apps/api/path'
-import { useStore } from '../useStore'
-import { isTauri } from './isTauri'
-import { ProjectWithEntryPointMetadata } from '../Router'
-import { metadata } from 'tauri-plugin-fs-extra-api'
-
-const PROJECT_FOLDER = 'kittycad-modeling-projects'
-export const FILE_EXT = '.kcl'
-export const PROJECT_ENTRYPOINT = 'main' + FILE_EXT
-const INDEX_IDENTIFIER = '$n' // $nn.. will pad the number with 0s
-export const MAX_PADDING = 7
-
-// Initializes the project directory and returns the path
-export async function initializeProjectDirectory() {
- if (!isTauri()) {
- throw new Error(
- 'initializeProjectDirectory() can only be called from a Tauri app'
- )
- }
- const { defaultDir: projectDir, setDefaultDir } = useStore.getState()
-
- if (projectDir && projectDir.dir.length > 0) {
- const dirExists = await exists(projectDir.dir)
- if (!dirExists) {
- await createDir(projectDir.dir, { recursive: true })
- }
- return projectDir
- }
-
- const appData = await documentDir()
-
- const INITIAL_DEFAULT_DIR = {
- dir: appData + PROJECT_FOLDER,
- }
-
- const defaultDirExists = await exists(INITIAL_DEFAULT_DIR.dir)
-
- if (!defaultDirExists) {
- await createDir(INITIAL_DEFAULT_DIR.dir, { recursive: true })
- }
-
- setDefaultDir(INITIAL_DEFAULT_DIR)
- return INITIAL_DEFAULT_DIR
-}
-
-export function isProjectDirectory(fileOrDir: Partial) {
- return (
- fileOrDir.children?.length &&
- fileOrDir.children.some((child) => child.name === PROJECT_ENTRYPOINT)
- )
-}
-
-// Creates a new file in the default directory with the default project name
-// Returns the path to the new file
-export async function createNewProject(
- path: string
-): Promise {
- if (!isTauri) {
- throw new Error('createNewProject() can only be called from a Tauri app')
- }
-
- const dirExists = await exists(path)
- if (!dirExists) {
- await createDir(path, { recursive: true }).catch((err) => {
- console.error('Error creating new directory:', err)
- throw err
- })
- }
-
- await writeTextFile(path + '/' + PROJECT_ENTRYPOINT, '').catch((err) => {
- console.error('Error creating new file:', err)
- throw err
- })
-
- const m = await metadata(path)
-
- return {
- name: path.slice(path.lastIndexOf('/') + 1),
- path: path,
- entrypoint_metadata: m,
- children: [
- {
- name: PROJECT_ENTRYPOINT,
- path: path + '/' + PROJECT_ENTRYPOINT,
- children: [],
- },
- ],
- }
-}
-
-// create a regex to match the project name
-// replacing any instances of "$n" with a regex to match any number
-function interpolateProjectName(projectName: string) {
- const regex = new RegExp(
- projectName.replace(getPaddedIdentifierRegExp(), '([0-9]+)')
- )
- return regex
-}
-
-// Returns the next available index for a project name
-export function getNextProjectIndex(projectName: string, files: FileEntry[]) {
- const regex = interpolateProjectName(projectName)
- const matches = files.map((file) => file.name?.match(regex))
- const indices = matches
- .filter(Boolean)
- .map((match) => match![1])
- .map(Number)
- const maxIndex = Math.max(...indices, -1)
- return maxIndex + 1
-}
-
-// Interpolates the project name with the next available index,
-// padding the index with 0s if necessary
-export function interpolateProjectNameWithIndex(
- projectName: string,
- index: number
-) {
- const regex = getPaddedIdentifierRegExp()
-
- const matches = projectName.match(regex)
- const padStartLength = Math.min(
- matches !== null ? matches[1]?.length || 0 : 0,
- MAX_PADDING
- )
- return projectName.replace(
- regex,
- index.toString().padStart(padStartLength + 1, '0')
- )
-}
-
-export function doesProjectNameNeedInterpolated(projectName: string) {
- return projectName.includes(INDEX_IDENTIFIER)
-}
-
-function escapeRegExpChars(string: string) {
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
-}
-
-function getPaddedIdentifierRegExp() {
- const escapedIdentifier = escapeRegExpChars(INDEX_IDENTIFIER)
- return new RegExp(`${escapedIdentifier}(${escapedIdentifier.slice(-1)}*)`)
-}
diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx
deleted file mode 100644
index 11146fb60..000000000
--- a/src/routes/Home.tsx
+++ /dev/null
@@ -1,258 +0,0 @@
-import { FormEvent, useCallback, useEffect, useState } from 'react'
-import { readDir, removeDir, renameFile } from '@tauri-apps/api/fs'
-import {
- createNewProject,
- getNextProjectIndex,
- interpolateProjectNameWithIndex,
- doesProjectNameNeedInterpolated,
- isProjectDirectory,
- PROJECT_ENTRYPOINT,
-} from '../lib/tauriFS'
-import { ActionButton } from '../components/ActionButton'
-import {
- faArrowDown,
- faArrowUp,
- faCircleDot,
- faPlus,
-} from '@fortawesome/free-solid-svg-icons'
-import { useStore } from '../useStore'
-import { toast } from 'react-hot-toast'
-import { AppHeader } from '../components/AppHeader'
-import ProjectCard from '../components/ProjectCard'
-import { useLoaderData, useSearchParams } from 'react-router-dom'
-import { Link } from 'react-router-dom'
-import { ProjectWithEntryPointMetadata, HomeLoaderData } from '../Router'
-import Loading from '../components/Loading'
-import { metadata } from 'tauri-plugin-fs-extra-api'
-
-const DESC = ':desc'
-
-// This route only opens in the Tauri desktop context for now,
-// as defined in Router.tsx, so we can use the Tauri APIs and types.
-const Home = () => {
- const [searchParams, setSearchParams] = useSearchParams()
- const sort = searchParams.get('sort_by') ?? 'modified:desc'
- const { projects: loadedProjects } = useLoaderData() as HomeLoaderData
- const [isLoading, setIsLoading] = useState(true)
- const [projects, setProjects] = useState(loadedProjects || [])
- const { defaultDir, defaultProjectName } = useStore((s) => ({
- defaultDir: s.defaultDir,
- defaultProjectName: s.defaultProjectName,
- }))
-
- const refreshProjects = useCallback(
- async (projectDir = defaultDir) => {
- const readProjects = (
- await readDir(projectDir.dir, {
- recursive: true,
- })
- ).filter(isProjectDirectory)
-
- const projectsWithMetadata = await Promise.all(
- readProjects.map(async (p) => ({
- entrypoint_metadata: await metadata(
- p.path + '/' + PROJECT_ENTRYPOINT
- ),
- ...p,
- }))
- )
-
- setProjects(projectsWithMetadata)
- },
- [defaultDir, setProjects]
- )
-
- useEffect(() => {
- refreshProjects(defaultDir).then(() => {
- setIsLoading(false)
- })
- }, [setIsLoading, refreshProjects, defaultDir])
-
- async function handleNewProject() {
- let projectName = defaultProjectName
- if (doesProjectNameNeedInterpolated(projectName)) {
- const nextIndex = await getNextProjectIndex(defaultProjectName, projects)
- projectName = interpolateProjectNameWithIndex(
- defaultProjectName,
- nextIndex
- )
- }
-
- await createNewProject(defaultDir.dir + '/' + projectName).catch((err) => {
- console.error('Error creating project:', err)
- toast.error('Error creating project')
- })
-
- await refreshProjects()
- toast.success('Project created')
- }
-
- async function handleRenameProject(
- e: FormEvent,
- project: ProjectWithEntryPointMetadata
- ) {
- const { newProjectName } = Object.fromEntries(
- new FormData(e.target as HTMLFormElement)
- )
- if (newProjectName && project.name && newProjectName !== project.name) {
- const dir = project.path?.slice(0, project.path?.lastIndexOf('/'))
- await renameFile(project.path, dir + '/' + newProjectName).catch(
- (err) => {
- console.error('Error renaming project:', err)
- toast.error('Error renaming project')
- }
- )
-
- await refreshProjects()
- toast.success('Project renamed')
- }
- }
-
- async function handleDeleteProject(project: ProjectWithEntryPointMetadata) {
- if (project.path) {
- await removeDir(project.path, { recursive: true }).catch((err) => {
- console.error('Error deleting project:', err)
- toast.error('Error deleting project')
- })
-
- await refreshProjects()
- toast.success('Project deleted')
- }
- }
-
- function getSortIcon(sortBy: string) {
- if (sort === sortBy) {
- return faArrowUp
- } else if (sort === sortBy + DESC) {
- return faArrowDown
- }
- return faCircleDot
- }
-
- function getNextSearchParams(sortBy: string) {
- if (sort === null || !sort)
- return { sort_by: sortBy + (sortBy !== 'modified' ? DESC : '') }
- if (sort.includes(sortBy) && !sort.includes(DESC)) return { sort_by: '' }
- return {
- sort_by: sortBy + (sort.includes(DESC) ? '' : DESC),
- }
- }
-
- function getSortFunction(sortBy: string) {
- const sortByName = (
- a: ProjectWithEntryPointMetadata,
- b: ProjectWithEntryPointMetadata
- ) => {
- if (a.name && b.name) {
- return sortBy.includes('desc')
- ? a.name.localeCompare(b.name)
- : b.name.localeCompare(a.name)
- }
- return 0
- }
-
- const sortByModified = (
- a: ProjectWithEntryPointMetadata,
- b: ProjectWithEntryPointMetadata
- ) => {
- if (
- a.entrypoint_metadata?.modifiedAt &&
- b.entrypoint_metadata?.modifiedAt
- ) {
- return !sortBy || sortBy.includes('desc')
- ? b.entrypoint_metadata.modifiedAt.getTime() -
- a.entrypoint_metadata.modifiedAt.getTime()
- : a.entrypoint_metadata.modifiedAt.getTime() -
- b.entrypoint_metadata.modifiedAt.getTime()
- }
- return 0
- }
-
- if (sortBy?.includes('name')) {
- return sortByName
- } else {
- return sortByModified
- }
- }
-
- return (
-
-
-
-
- Your Projects
-
-
setSearchParams(getNextSearchParams('name'))}
- icon={{
- icon: getSortIcon('name'),
- bgClassName: !sort?.includes('name')
- ? 'bg-liquid-30 dark:bg-liquid-70'
- : '',
- }}
- >
- Name
-
-
setSearchParams(getNextSearchParams('modified'))}
- icon={{
- icon: sort ? getSortIcon('modified') : faArrowDown,
- bgClassName: !(
- sort?.includes('modified') ||
- !sort ||
- sort === null
- )
- ? 'bg-liquid-30 dark:bg-liquid-70'
- : '',
- }}
- >
- Last Modified
-
-
-
-
-
- Are being saved at{' '}
-
- {defaultDir.dir}
-
- , which you can change in your Settings.
-
- {isLoading ? (
- Loading your Projects...
- ) : (
- <>
- {projects.length > 0 ? (
-
- {projects.sort(getSortFunction(sort)).map((project) => (
-
- ))}
-
- ) : (
-
- No Projects found, ready to make your first one?
-
- )}
-
- New file
-
- >
- )}
-
-
-
- )
-}
-
-export default Home
diff --git a/src/routes/Onboarding/Camera.tsx b/src/routes/Onboarding/Camera.tsx
index 39a8ecdd4..0f4ae0cb1 100644
--- a/src/routes/Onboarding/Camera.tsx
+++ b/src/routes/Onboarding/Camera.tsx
@@ -1,14 +1,14 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
-import { onboardingPaths, useDismiss, useNextClick } from '.'
+import { useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
-export default function Units() {
+const Units = () => {
const { isMouseDownInStream } = useStore((s) => ({
isMouseDownInStream: s.isMouseDownInStream,
}))
const dismiss = useDismiss()
- const next = useNextClick(onboardingPaths.SKETCHING)
+ const next = useNextClick('sketching')
return (
@@ -26,7 +26,6 @@ export default function Units() {
Dismiss
-
+
Next: Sketching
@@ -50,3 +45,5 @@ export default function Units() {
)
}
+
+export default Units
diff --git a/src/routes/Onboarding/Introduction.tsx b/src/routes/Onboarding/Introduction.tsx
index c0fc20339..948214f5c 100644
--- a/src/routes/Onboarding/Introduction.tsx
+++ b/src/routes/Onboarding/Introduction.tsx
@@ -1,10 +1,10 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
-import { onboardingPaths, useDismiss, useNextClick } from '.'
+import { useDismiss, useNextClick } from '.'
-export default function Introduction() {
+const Introduction = () => {
const dismiss = useDismiss()
- const next = useNextClick(onboardingPaths.UNITS)
+ const next = useNextClick('units')
return (
@@ -22,7 +22,6 @@ export default function Introduction() {
@@ -46,3 +41,5 @@ export default function Introduction() {
)
}
+
+export default Introduction
diff --git a/src/routes/Onboarding/Sketching.tsx b/src/routes/Onboarding/Sketching.tsx
index 3c33357ff..fbf8a5b79 100644
--- a/src/routes/Onboarding/Sketching.tsx
+++ b/src/routes/Onboarding/Sketching.tsx
@@ -2,7 +2,7 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { useDismiss } from '.'
-export default function Sketching() {
+const Sketching = () => {
const dismiss = useDismiss()
return (
@@ -14,7 +14,6 @@ export default function Sketching() {
@@ -38,3 +33,5 @@ export default function Sketching() {
)
}
+
+export default Sketching
diff --git a/src/routes/Onboarding/Units.tsx b/src/routes/Onboarding/Units.tsx
index e659362c5..c0e20eb9d 100644
--- a/src/routes/Onboarding/Units.tsx
+++ b/src/routes/Onboarding/Units.tsx
@@ -4,11 +4,11 @@ import { ActionButton } from '../../components/ActionButton'
import { SettingsSection } from '../Settings'
import { Toggle } from '../../components/Toggle/Toggle'
import { useState } from 'react'
-import { onboardingPaths, useDismiss, useNextClick } from '.'
+import { useDismiss, useNextClick } from '.'
-export default function Units() {
+const Units = () => {
const dismiss = useDismiss()
- const next = useNextClick(onboardingPaths.CAMERA)
+ const next = useNextClick('camera')
const {
defaultUnitSystem: ogDefaultUnitSystem,
setDefaultUnitSystem: saveDefaultUnitSystem,
@@ -67,7 +67,6 @@ export default function Units() {
@@ -91,3 +86,5 @@ export default function Units() {
)
}
+
+export default Units
diff --git a/src/routes/Onboarding/index.tsx b/src/routes/Onboarding/index.tsx
index 14996d4d9..fa5f73a37 100644
--- a/src/routes/Onboarding/index.tsx
+++ b/src/routes/Onboarding/index.tsx
@@ -8,13 +8,12 @@ import Camera from './Camera'
import Sketching from './Sketching'
import { useCallback } from 'react'
import { paths } from '../../Router'
-import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
export const onboardingPaths = {
- INDEX: '/',
- UNITS: '/units',
- CAMERA: '/camera',
- SKETCHING: '/sketching',
+ INDEX: '',
+ UNITS: 'units',
+ CAMERA: 'camera',
+ SKETCHING: 'sketching',
}
export const onboardingRoutes = [
@@ -23,15 +22,15 @@ export const onboardingRoutes = [
element: ,
},
{
- path: makeUrlPathRelative(onboardingPaths.UNITS),
+ path: onboardingPaths.UNITS,
element: ,
},
{
- path: makeUrlPathRelative(onboardingPaths.CAMERA),
+ path: onboardingPaths.CAMERA,
element: ,
},
{
- path: makeUrlPathRelative(onboardingPaths.SKETCHING),
+ path: onboardingPaths.SKETCHING,
element: ,
},
]
@@ -44,7 +43,7 @@ export function useNextClick(newStatus: string) {
return useCallback(() => {
setOnboardingStatus(newStatus)
- navigate((newStatus !== onboardingPaths.UNITS ? '..' : '.') + newStatus)
+ navigate('/onboarding/' + newStatus)
}, [newStatus, setOnboardingStatus, navigate])
}
diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx
index 8d187b3fa..b828aafa4 100644
--- a/src/routes/Settings.tsx
+++ b/src/routes/Settings.tsx
@@ -16,7 +16,7 @@ import { paths } from '../Router'
export const Settings = () => {
const navigate = useNavigate()
- useHotkeys('esc', () => navigate('../'))
+ useHotkeys('esc', () => navigate(paths.INDEX))
const {
defaultDir,
setDefaultDir,
@@ -46,7 +46,6 @@ export const Settings = () => {
theme: s.theme,
setTheme: s.setTheme,
}))
- const ogDefaultDir = useRef(defaultDir)
const ogDefaultProjectName = useRef(defaultProjectName)
async function handleDirectorySelection() {
@@ -66,7 +65,7 @@ export const Settings = () => {
{
base: defaultDir.base,
dir: e.target.value,
})
- }}
- onBlur={() => {
- ogDefaultDir.current.dir !== defaultDir.dir &&
- toast.success('Default directory updated')
- ogDefaultDir.current.dir = defaultDir.dir
+ toast.success('Default directory updated')
}}
/>
{
onBlur={() => {
ogDefaultProjectName.current !== defaultProjectName &&
toast.success('Default project name updated')
- ogDefaultProjectName.current = defaultProjectName
}}
/>
@@ -228,10 +222,9 @@ export const Settings = () => {
description="Replay the onboarding process"
>
{
setOnboardingStatus('')
- navigate('..' + paths.ONBOARDING.INDEX)
+ navigate(paths.ONBOARDING.INDEX)
}}
icon={{ icon: faArrowRotateBack }}
>
diff --git a/src/routes/SignIn.tsx b/src/routes/SignIn.tsx
index de78b346f..289ecc60c 100644
--- a/src/routes/SignIn.tsx
+++ b/src/routes/SignIn.tsx
@@ -61,7 +61,6 @@ const SignIn = () => {
{isTauri() ? (
()(
// tauri specific app settings
defaultDir: {
- dir: '',
+ dir: '~/Documents/',
},
setDefaultDir: (dir) => set({ defaultDir: dir }),
- defaultProjectName: 'new-project-$nnn',
+ defaultProjectName: 'new-project-$n',
setDefaultProjectName: (defaultProjectName) =>
set({ defaultProjectName }),
defaultUnitSystem: 'imperial',
diff --git a/yarn.lock b/yarn.lock
index 0331de8eb..136892f28 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1986,7 +1986,7 @@
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
-"@tauri-apps/api@1.4.0", "@tauri-apps/api@^1.3.0":
+"@tauri-apps/api@^1.3.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.4.0.tgz#b4013ca3d17b853f7df29fe14079ebb4d52dbffa"
integrity sha512-Jd6HPoTM1PZSFIzq7FB8VmMu3qSSyo/3lSwLpoapW+lQ41CL5Dow2KryLg+gyazA/58DRWI9vu/XpEeHK4uMdw==
@@ -5620,12 +5620,6 @@ tar@^6.1.11:
mkdirp "^1.0.3"
yallist "^4.0.0"
-"tauri-plugin-fs-extra-api@https://github.com/tauri-apps/tauri-plugin-fs-extra#v1":
- version "0.0.0"
- resolved "https://github.com/tauri-apps/tauri-plugin-fs-extra#1344db48a39b44fe46e9943bf7cddca2fa00caaf"
- dependencies:
- "@tauri-apps/api" "1.4.0"
-
test-exclude@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"