diff --git a/.gitignore b/.gitignore index 27704f29d..529226d5f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +tsconfig.json # testing /coverage diff --git a/interface.d.ts b/interface.d.ts index 8db226161..a2cbab1ea 100644 --- a/interface.d.ts +++ b/interface.d.ts @@ -12,21 +12,24 @@ export interface IElectronAPI { arch: typeof process.env.arch version: typeof process.env.version readFile: (path: string) => ReturnType - writeFile: (path: string, data: string | Uint8Array) => ReturnType + writeFile: ( + path: string, + data: string | Uint8Array + ) => ReturnType readdir: (path: string) => ReturnType getPath: (name: string) => Promise rm: typeof fs.rm stat: (path: string) => ReturnType statIsDirectory: (path: string) => Promise - path: typeof path, - mkdir: typeof fs.mkdir, - rename: (prev: string, next: string) => typeof fs.rename, + path: typeof path + mkdir: typeof fs.mkdir + rename: (prev: string, next: string) => typeof fs.rename packageJson: { - name: string, - }, + name: string + } process: { env: { - BASE_URL: (value?: string) => string, + BASE_URL: (value?: string) => string } } } diff --git a/package.json b/package.json index 703454439..4f4820c8a 100644 --- a/package.json +++ b/package.json @@ -145,11 +145,6 @@ "@typescript-eslint/parser": "^5.0.0", "@vitejs/plugin-react": "^4.3.0", "@vitest/web-worker": "^1.5.0", - "@wdio/cli": "^8.24.3", - "@wdio/globals": "^8.36.0", - "@wdio/local-runner": "^8.36.0", - "@wdio/mocha-framework": "^8.36.0", - "@wdio/spec-reporter": "^8.36.0", "@xstate/cli": "^0.5.17", "autoprefixer": "^10.4.19", "electron": "^31.2.1", @@ -170,7 +165,7 @@ "setimmediate": "^1.0.5", "tailwindcss": "^3.4.1", "ts-node": "^10.0.0", - "typescript": "^4.5.4", + "typescript": "^5.0.0", "vite": "^5.0.12", "vite-plugin-eslint": "^1.8.1", "vite-plugin-package-version": "^1.1.0", diff --git a/src/components/ActionButton.tsx b/src/components/ActionButton.tsx index c20c9959a..e03b0fe86 100644 --- a/src/components/ActionButton.tsx +++ b/src/components/ActionButton.tsx @@ -108,7 +108,7 @@ export const ActionButton = forwardRef((props: ActionButtonProps, ref) => { ref={ref as ForwardedRef} to={to || paths.INDEX} className={classNames} - onClick={openExternalBrowserIfDesktop(to)} + onClick={openExternalBrowserIfDesktop(to as string)} {...rest} target="_blank" > diff --git a/src/components/FileMachineProvider.tsx b/src/components/FileMachineProvider.tsx index f849d5bbe..ef5cfb834 100644 --- a/src/components/FileMachineProvider.tsx +++ b/src/components/FileMachineProvider.tsx @@ -135,7 +135,9 @@ export const FileMachineProvider = ({ const newPath = newDirPath + (name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT) - await window.electron.rename(oldPath, newPath, {}) + await window.electron.rename(oldPath, newPath) + + if (!file) { return Promise.reject(new Error('file is not defined')) } const currentFilePath = window.electron.path.join(file.path, file.name) if (oldPath === currentFilePath && project?.path) { diff --git a/src/components/HelpMenu.tsx b/src/components/HelpMenu.tsx index 3a5318ed7..777f9a1ac 100644 --- a/src/components/HelpMenu.tsx +++ b/src/components/HelpMenu.tsx @@ -142,7 +142,7 @@ function HelpMenuItem({ {as === 'a' ? ( )} - onClick={openExternalBrowserIfDesktop(props.href)} + onClick={openExternalBrowserIfDesktop((props as React.ComponentProps<'a'>).href)} className={`no-underline text-inherit ${baseClassName} ${className}`} > {children} diff --git a/src/components/LowerRightControls.tsx b/src/components/LowerRightControls.tsx index 33e144100..2c4958bcd 100644 --- a/src/components/LowerRightControls.tsx +++ b/src/components/LowerRightControls.tsx @@ -26,7 +26,7 @@ export function LowerRightControls({ const isPlayWright = window?.localStorage.getItem('playwright') === 'true' - async function reportbug(event: { preventDefault: () => void }) { + async function reportbug(event: { preventDefault: () => void, stopPropagation: () => void }) { event?.preventDefault() event?.stopPropagation() diff --git a/src/components/ProjectCard/ProjectCard.tsx b/src/components/ProjectCard/ProjectCard.tsx index a5eaebf37..20ebfee92 100644 --- a/src/components/ProjectCard/ProjectCard.tsx +++ b/src/components/ProjectCard/ProjectCard.tsx @@ -113,8 +113,8 @@ function ProjectCard({ Edited{' '} - {project.metadata && project.metadata.mtimeMs - ? getDisplayedTime(project.metadata.mtimeMs) + {project.metadata && project.metadata.modified + ? getDisplayedTime(project.metadata.modified.toString()) : 'never'} diff --git a/src/components/Settings/AllSettingsFields.tsx b/src/components/Settings/AllSettingsFields.tsx index 2234c920f..a82214bf0 100644 --- a/src/components/Settings/AllSettingsFields.tsx +++ b/src/components/Settings/AllSettingsFields.tsx @@ -195,7 +195,9 @@ export const AllSettingsFields = forwardRef( const paths = await getSettingsFolderPaths( projectPath ? decodeURIComponent(projectPath) : undefined ) - window.electron.showInFolder(paths[searchParamTab]) + const finalPath = paths[searchParamTab] + if (!finalPath) { return new Error('finalPath undefined') } + window.electron.showInFolder(finalPath) }} iconStart={{ icon: 'folder', diff --git a/src/components/SettingsAuthProvider.tsx b/src/components/SettingsAuthProvider.tsx index 0517db492..746090b30 100644 --- a/src/components/SettingsAuthProvider.tsx +++ b/src/components/SettingsAuthProvider.tsx @@ -209,7 +209,7 @@ export const SettingsAuthProviderBase = ({ }, services: { 'Persist settings': (context) => - saveSettings(context, loadedProject?.project.path), + saveSettings(context, loadedProject?.project?.path), }, } ) diff --git a/src/index.tsx b/src/index.tsx index 80d7e069c..4ed2a255a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,8 +7,6 @@ import { HotkeysProvider } from 'react-hotkeys-hook' import ModalContainer from 'react-modal-promise' import { UpdaterModal, createUpdaterModal } from 'components/UpdaterModal' import { isDesktop } from 'lib/isDesktop' -import { relaunch } from '@tauri-apps/plugin-process' -import { check } from '@tauri-apps/plugin-updater' import { UpdaterRestartModal, createUpdaterRestartModal, @@ -59,32 +57,4 @@ root.render( // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals() -const runTauriUpdater = async () => { - console.log('STUB: @pierre') - return - - try { - const update = await check() - if (update && update.available) { - const { date, version, body } = update - const modal = createUpdaterModal(UpdaterModal) - const { wantUpdate } = await modal({ date, version, body }) - if (wantUpdate) { - await update.downloadAndInstall() - // On macOS and Linux, the restart needs to be manually triggered - const isNotWindows = navigator.userAgent.indexOf('Win') === -1 - if (isNotWindows) { - const relaunchModal = createUpdaterRestartModal(UpdaterRestartModal) - const { wantRestart } = await relaunchModal({ version }) - if (wantRestart) { - await relaunch() - } - } - } - } - } catch (error) { - console.error(error) - } -} - -isDesktop() && runTauriUpdater() +isDesktop() diff --git a/src/lang/std/fileSystemManager.ts b/src/lang/std/fileSystemManager.ts index 1196d6b69..0bb71ed1a 100644 --- a/src/lang/std/fileSystemManager.ts +++ b/src/lang/std/fileSystemManager.ts @@ -28,13 +28,9 @@ class FileSystemManager { ) } - return this.join(this.dir, path) - .catch((error) => { - return Promise.reject(new Error(`Error reading file: ${error}`)) - }) - .then((filePath) => { - return window.electron.readFile(filePath) - }) + return this.join(this.dir, path).then((filePath) => { + return window.electron.readFile(filePath) + }) } async exists(path: string): Promise { @@ -47,20 +43,16 @@ class FileSystemManager { ) } - return this.join(this.dir, path) - .catch((error) => { - return Promise.reject(new Error(`Error checking file exists: ${error}`)) - }) - .then(async (file) => { - try { - await window.electron.stat(file) - } catch (e) { - if (e === 'ENOENT') { - return false - } + return this.join(this.dir, path).then(async (file) => { + try { + await window.electron.stat(file) + } catch (e) { + if (e === 'ENOENT') { + return false } - return true - }) + } + return true + }) } async getAllFiles(path: string): Promise { @@ -73,20 +65,16 @@ class FileSystemManager { ) } - return this.join(this.dir, path) - .catch((error) => { - return Promise.reject(new Error(`Error joining dir: ${error}`)) - }) - .then((filepath) => { - return window.electron - .readdir(filepath) - .catch((error) => { - return Promise.reject(new Error(`Error reading dir: ${error}`)) - }) - .then((files) => { - return files.map((file) => file.path) - }) - }) + return this.join(this.dir, path).then((filepath) => { + return window.electron + .readdir(filepath) + .catch((error: Error) => { + return Promise.reject(new Error(`Error reading dir: ${error}`)) + }) + .then((files: string[]) => { + return files.map((filePath) => filePath) + }) + }) } } diff --git a/src/lang/wasm.ts b/src/lang/wasm.ts index 76bb773de..c8a7f54c8 100644 --- a/src/lang/wasm.ts +++ b/src/lang/wasm.ts @@ -22,8 +22,10 @@ import init, { import { configurationToSettingsPayload, projectConfigurationToSettingsPayload, - SaveSettingsPayload, } from 'lib/settings/settingsUtils' +import { + SaveSettingsPayload, +} from 'lib/settings/settingsTypes' import { KCLError } from './errors' import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' import { EngineCommandManager } from './std/engineConnection' diff --git a/src/lib/desktop.ts b/src/lib/desktop.ts index 8c83ca532..997570830 100644 --- a/src/lib/desktop.ts +++ b/src/lib/desktop.ts @@ -1,3 +1,4 @@ +import { err } from 'lib/trap' import { Models } from '@kittycad/lib' import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration' import { Project } from 'wasm-lib/kcl/bindings/Project' @@ -77,7 +78,8 @@ export async function renameProjectDirectory( export async function ensureProjectDirectoryExists( config: Partial ): Promise { - const projectDir = config.app.projectDirectory + const projectDir = config.app?.projectDirectory + if (!projectDir) { return Promise.reject(new Error('projectDir is falsey')) } try { await window.electron.stat(projectDir) } catch (e) { @@ -104,6 +106,7 @@ export async function createNewProjectDirectory( return Promise.reject('Project name cannot be empty.') } + if (!mainDir) { return Promise.reject(new Error('mainDir is falsey')) } const projectDir = window.electron.path.join(mainDir, projectName) try { @@ -142,6 +145,8 @@ export async function listProjects( } const projectDir = await ensureProjectDirectoryExists(configuration) const projects = [] + if (!projectDir) return Promise.reject(new Error('projectDir was falsey')) + const entries = await window.electron.readdir(projectDir) for (let entry of entries) { const projectPath = window.electron.path.join(projectDir, entry) @@ -231,7 +236,7 @@ const collectAllFilesRecursiveFrom = async (path: string) => { return entry } -const getDefaultKclFileForDir = async (projectDir, file) => { +const getDefaultKclFileForDir = async (projectDir: string, file: FileEntry) => { // Make sure the dir is a directory. const isFileEntryDir = await window.electron.statIsDirectory(projectDir) if (!isFileEntryDir) { @@ -305,7 +310,7 @@ export async function getProjectInfo(projectPath: string): Promise { } catch (e) { if (e === 'ENOENT') { return Promise.reject( - new Error(`Project directory does not exist: ${project_path}`) + new Error(`Project directory does not exist: ${projectPath}`) ) } } @@ -314,7 +319,7 @@ export async function getProjectInfo(projectPath: string): Promise { const projectPathIsDir = await window.electron.statIsDirectory(projectPath) if (!projectPathIsDir) { return Promise.reject( - new Error(`Project path is not a directory: ${project_path}`) + new Error(`Project path is not a directory: ${projectPath}`) ) } @@ -322,9 +327,10 @@ export async function getProjectInfo(projectPath: string): Promise { let default_file = await getDefaultKclFileForDir(projectPath, walked) const metadata = await window.electron.stat(projectPath) - let project = /* FileEntry */ { + let project = { ...walked, - metadata, + // We need to map from node fs.Stats to FileMetadata + metadata: metadata.map((data: { mtimeMs: number }) => ({ modified: data.mtimeMs })), kcl_file_count: 0, directory_count: 0, default_file, @@ -349,6 +355,7 @@ export async function writeProjectSettingsFile({ }): Promise { const projectSettingsFilePath = await getProjectSettingsFilePath(projectPath) const tomlStr = tomlStringify(configuration) + if (err(tomlStr)) return Promise.reject(tomlStr) return window.electron.writeFile(projectSettingsFilePath, tomlStr) } @@ -387,7 +394,7 @@ export const getInitialDefaultDir = async () => { export const readProjectSettingsFile = async ( projectPath: string -): Promise => { +): Promise> => { let settingsPath = await getProjectSettingsFilePath(projectPath) // Check if this file exists. @@ -426,6 +433,7 @@ export const writeAppSettingsFile = async ( ) => { const appSettingsFilePath = await getAppSettingsFilePath() const tomlStr = tomlStringify(config) + if (err(tomlStr)) return Promise.reject(tomlStr) return window.electron.writeFile(appSettingsFilePath, tomlStr) } diff --git a/src/lib/electron.ts b/src/lib/electron.ts index 8ab37e0f1..5e2c85e1c 100644 --- a/src/lib/electron.ts +++ b/src/lib/electron.ts @@ -8,7 +8,8 @@ const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args) const openExternal = (url: any) => ipcRenderer.invoke('shell.openExternal', url) const showInFolder = (path: string) => ipcRenderer.invoke('shell.showItemInFolder', path) -const login = (host: string): Promise => ipcRenderer.invoke('login', host) +const login = (host: string): Promise => + ipcRenderer.invoke('login', host) const readFile = (path: string) => fs.readFile(path, 'utf-8') const rename = (prev: string, next: string) => fs.rename(prev, next) diff --git a/src/lib/openWindow.ts b/src/lib/openWindow.ts index bb0302200..5f2689ee3 100644 --- a/src/lib/openWindow.ts +++ b/src/lib/openWindow.ts @@ -1,7 +1,8 @@ +import { MouseEventHandler } from 'react' import { isDesktop } from 'lib/isDesktop' export const openExternalBrowserIfDesktop = (to?: string) => - function (e: Event) { + function (e) { if (isDesktop()) { // Ignoring because currentTarget could be a few different things // @ts-ignore @@ -10,7 +11,7 @@ export const openExternalBrowserIfDesktop = (to?: string) => e.stopPropagation() return false } - } + } as MouseEventHandler // Open a new browser window desktop style or browser style. export default async function openWindow(url: string) { diff --git a/src/lib/routeLoaders.ts b/src/lib/routeLoaders.ts index 7f371d839..c19725eed 100644 --- a/src/lib/routeLoaders.ts +++ b/src/lib/routeLoaders.ts @@ -117,19 +117,21 @@ export const fileLoader: LoaderFunction = async ( // So that WASM gets an updated path for operations fileSystemManager.dir = project_path - const projectData: IndexLoaderData = { - code, - project: isDesktop() - ? await getProjectInfo(project_path) - : { - name: project_name, + const defaultProjectData = { + name: project_name || 'unnamed', path: project_path, children: [], kcl_file_count: 0, directory_count: 0, metadata: null, default_file: project_path, - }, + } + + const projectData: IndexLoaderData = { + code, + project: isDesktop() + ? (await getProjectInfo(project_path)) ?? defaultProjectData + : defaultProjectData, file: { name: current_file_name || '', path: current_file_path?.split('/').slice(0, -1).join('/') ?? '', diff --git a/src/lib/settings/initialSettings.tsx b/src/lib/settings/initialSettings.tsx index 296fbcd94..6bf192166 100644 --- a/src/lib/settings/initialSettings.tsx +++ b/src/lib/settings/initialSettings.tsx @@ -63,7 +63,9 @@ export class Setting { return this._user } set user(v: T | undefined) { - this._user = this.validate(v) ? v : this._user + this._user = v !== undefined + ? this.validate(v) ? v : this._user + : v this.current = this.resolve() } /** @@ -72,8 +74,10 @@ export class Setting { get project(): T | undefined { return this._project } - set project(v: T) { - this._project = this.validate(v) ? v : this._project + set project(v: T | undefined) { + this._project = v !== undefined + ? this.validate(v) ? v : this._project + : v this.current = this.resolve() } /** diff --git a/src/lib/settings/settingsUtils.ts b/src/lib/settings/settingsUtils.ts index ef73679f5..d05c126fd 100644 --- a/src/lib/settings/settingsUtils.ts +++ b/src/lib/settings/settingsUtils.ts @@ -129,7 +129,7 @@ export function readLocalStorageAppSettingsFile(): Configuration | Error { } } -function readLocalStorageProjectSettingsFile(): ProjectConfiguration | Error { +function readLocalStorageProjectSettingsFile(): Partial | Error { // TODO: Remove backwards compatibility after a few releases. let stored = localStorage.getItem(localStorageProjectSettingsPath()) ?? '' diff --git a/src/lib/sorting.ts b/src/lib/sorting.ts index a988caaa0..64c52b264 100644 --- a/src/lib/sorting.ts +++ b/src/lib/sorting.ts @@ -36,9 +36,9 @@ export function getSortFunction(sortBy: string) { } const sortByModified = (a: Project, b: Project) => { - if (a.metadata?.mtimeMs && b.metadata?.mtimeMs) { - const aDate = new Date(a.metadata.mtimeMs) - const bDate = new Date(b.metadata.mtimeMs) + if (a.metadata?.modified && b.metadata?.modified) { + const aDate = new Date(a.metadata.modified) + const bDate = new Date(b.metadata.modified) return !sortBy || sortBy.includes('desc') ? bDate.getTime() - aDate.getTime() : aDate.getTime() - bDate.getTime() diff --git a/src/machines/authMachine.ts b/src/machines/authMachine.ts index a4832be5b..9f38df1f6 100644 --- a/src/machines/authMachine.ts +++ b/src/machines/authMachine.ts @@ -7,6 +7,7 @@ import { getUser as getUserDesktop } from 'lib/desktop' const SKIP_AUTH = import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV + const LOCAL_USER: Models['User_type'] = { id: '8675309', name: 'Test User', diff --git a/tsconfig.json b/tsconfig.desktop.json similarity index 100% rename from tsconfig.json rename to tsconfig.desktop.json diff --git a/wdio.conf.ts b/wdio.conf.ts deleted file mode 100644 index 0173937b2..000000000 --- a/wdio.conf.ts +++ /dev/null @@ -1,42 +0,0 @@ -import os from 'os' -import path from 'path' -import { spawn, ChildProcess } from 'child_process' - -let tauriDriver: ChildProcess - -const application = - process.env.E2E_APPLICATION || `./src-tauri/target/release/zoo-modeling-app` - -export const config = { - hostname: '127.0.0.1', - port: 4444, - specs: ['./e2e/tauri/specs/**/*.ts'], - maxInstances: 1, - capabilities: [ - { - maxInstances: 1, - 'tauri:options': { - application, - webviewOptions: {}, // Windows only - }, - }, - ], - reporters: ['spec'], - framework: 'mocha', - mochaOpts: { - bail: true, - ui: 'bdd', - timeout: 600000, - }, - - // ensure we are running `tauri-driver` before the session starts so that we can proxy the webdriver requests - beforeSession: () => - (tauriDriver = spawn( - path.resolve(os.homedir(), '.cargo', 'bin', 'tauri-driver'), - [], - { stdio: [null, process.stdout, process.stderr] } - )), - - // clean up the `tauri-driver` process we spawned at the start of the session - afterSession: () => tauriDriver.kill(), -}