Stream comes up!

This commit is contained in:
49lf
2024-07-26 15:52:35 -04:00
parent c678515c15
commit 397eb9bf5a
19 changed files with 462 additions and 221 deletions

View File

@ -51,7 +51,7 @@ export const FileMachineProvider = ({
commandBarSend({ type: 'Close' })
navigate(
`${paths.FILE}/${encodeURIComponent(
context.selectedDirectory + sep() + event.data.name
context.selectedDirectory + window.electron.path.sep + event.data.name
)}`
)
} else if (
@ -87,7 +87,7 @@ export const FileMachineProvider = ({
services: {
readFiles: async (context: ContextFrom<typeof fileMachine>) => {
const newFiles = isDesktop()
? (await getProjectInfo(context.project.path)).children
? (await getProjectInfo(context.project.file.path)).children
: []
return {
...context.project,
@ -104,7 +104,7 @@ export const FileMachineProvider = ({
} else {
createdPath =
context.selectedDirectory.path +
sep() +
window.electron.path.sep +
createdName +
(createdName.endsWith(FILE_EXT) ? '' : FILE_EXT)
await create(createdPath)
@ -169,7 +169,7 @@ export const FileMachineProvider = ({
file?.path.includes(event.data.path)) &&
project?.path
) {
navigate(paths.FILE + '/' + encodeURIComponent(project.path))
navigate(paths.FILE + '/' + encodeURIComponent(project.file.path))
}
return `Successfully deleted ${isDir ? 'folder' : 'file'} "${

View File

@ -171,7 +171,7 @@ const FileTreeItem = ({
// Import non-kcl files
// We want to update both the state and editor here.
codeManager.updateCodeStateEditor(
`import("${fileOrDir.path.replace(project.path, '.')}")\n` +
`import("${fileOrDir.path.replace(project.file.path, '.')}")\n` +
codeManager.code
)
codeManager.writeToFile()

View File

@ -52,7 +52,7 @@ function ProjectCard({
}
// async function setupImageUrl() {
// const projectImagePath = await join(project.path, PROJECT_IMAGE_NAME)
// const projectImagePath = await join(project.file.path, PROJECT_IMAGE_NAME)
// if (await exists(projectImagePath)) {
// const imageData = await readFile(projectImagePath)
// const blob = new Blob([imageData], { type: 'image/jpg' })
@ -103,7 +103,7 @@ function ProjectCard({
/>
) : (
<h3 className="font-sans relative z-0 p-2">
{project.name?.replace(FILE_EXT, '')}
{project.file.name?.replace(FILE_EXT, '')}
</h3>
)}
<span className="px-2 text-chalkboard-60 text-xs">
@ -169,11 +169,11 @@ function ProjectCard({
onDismiss={() => setIsConfirmingDelete(false)}
>
<p className="my-4">
This will permanently delete "{project.name || 'this file'}
This will permanently delete "{project.file.name || 'this file'}
".
</p>
<p className="my-4">
Are you sure you want to delete "{project.name || 'this file'}
Are you sure you want to delete "{project.file.name || 'this file'}
"? This action cannot be undone.
</p>
</DeleteConfirmationDialog>

View File

@ -24,7 +24,7 @@ export const ProjectCardRenameForm = forwardRef(
required
autoCorrect="off"
autoCapitalize="off"
defaultValue={project.name}
defaultValue={project.file.name}
ref={ref}
onKeyDown={(e) => {
if (e.key === 'Escape') {

View File

@ -5,7 +5,6 @@ import { paths } from 'lib/paths'
import { isDesktop } from '../lib/isDesktop'
import { Link, useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useMemo } from 'react'
import { sep } from '@tauri-apps/api/path'
import { Logo } from './Logo'
import { APP_NAME } from 'lib/constants'
import { useCommandsContext } from 'hooks/useCommandsContext'
@ -36,7 +35,7 @@ const ProjectSidebarMenu = ({
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}
{project?.name ? project.file.name : APP_NAME}
</span>
)}
</div>
@ -208,12 +207,12 @@ function ProjectMenuPopover({
<div className="flex flex-col items-start py-0.5">
<span className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block">
{isDesktop() && file?.name
? file.name.slice(file.name.lastIndexOf(sep()) + 1)
? file.name.slice(file.name.lastIndexOf(window.electron.path.sep) + 1)
: APP_NAME}
</span>
{isDesktop() && project?.name && (
<span className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block">
{project.name}
{project.file.name}
</span>
)}
</div>

View File

@ -46,7 +46,7 @@ export const AllSettingsFields = forwardRef(
location.pathname
.replace(paths.FILE + '/', '')
.replace(paths.SETTINGS, '')
.slice(0, decodeURI(location.pathname).lastIndexOf(sep()))
.slice(0, decodeURI(location.pathname).lastIndexOf(window.electron.path.sep))
)
: undefined

View File

@ -1,13 +1,14 @@
import { Platform, platform } from '@tauri-apps/plugin-os'
import { isDesktop } from 'lib/isDesktop'
import { useEffect, useState } from 'react'
export type Platform = 'macos' | 'windows' | 'linux'
export default function usePlatform() {
const [platformName, setPlatformName] = useState<Platform | ''>('')
useEffect(() => {
async function getPlatform() {
setPlatformName(await platform())
setPlatformName(window.electron.platform)
}
if (isDesktop()) {

View File

@ -3,7 +3,6 @@
// This prevents re-renders of the codemirror editor, when typing.
import { bracket } from 'lib/exampleKcl'
import { isDesktop } from 'lib/isDesktop'
import { writeTextFile } from '@tauri-apps/plugin-fs'
import toast from 'react-hot-toast'
import { editorManager } from 'lib/singletons'
import { Annotation, Transaction } from '@codemirror/state'
@ -120,7 +119,7 @@ export default class CodeManager {
// Wait one event loop to give a chance for params to be set
// Save the file to disk
this._currentFilePath &&
writeTextFile(this._currentFilePath, this.code).catch((err) => {
window.electron.writeFile(this._currentFilePath, this.code ?? '').catch((err) => {
// TODO: add tracing 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')

View File

@ -15,7 +15,7 @@ class FileSystemManager {
}
async join(dir: string, path: string): Promise<string> {
return window.electron.ipcRenderer.invoke('join', [dir, path])
return Promise.resolve(window.electron.path.join(dir, path))
}
async readFile(path: string): Promise<Uint8Array | void> {
@ -33,7 +33,7 @@ class FileSystemManager {
return Promise.reject(new Error(`Error reading file: ${error}`))
})
.then((file) => {
return window.electron.ipcRenderer.invoke('readFile', [filepath])
return window.electron.readFile(filepath)
})
}
@ -51,8 +51,14 @@ class FileSystemManager {
.catch((error) => {
return Promise.reject(new Error(`Error checking file exists: ${error}`))
})
.then((file) => {
return window.electron.ipcRenderer.invoke('exists', [file])
.then(async (file) => {
try { await window.electron.stat(file) }
catch (e) {
if (e === 'ENOENT') {
return false
}
}
return true
})
}
@ -71,8 +77,7 @@ class FileSystemManager {
return Promise.reject(new Error(`Error joining dir: ${error}`))
})
.then((filepath) => {
return window.electron.ipcRenderer
.invoke('readdir', [filepath])
return window.electron.readdir(filepath)
.catch((error) => {
return Promise.reject(new Error(`Error reading dir: ${error}`))
})

View File

@ -19,6 +19,11 @@ import init, {
parse_project_route,
serialize_project_settings,
} from '../wasm-lib/pkg/wasm_lib'
import {
configurationToSettingsPayload,
projectConfigurationToSettingsPayload,
SaveSettingsPayload,
} from 'lib/settings/settingsUtils'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { EngineCommandManager } from './std/engineConnection'
@ -559,22 +564,26 @@ export function tomlStringify(toml: any): string | Error {
return toml_stringify(JSON.stringify(toml))
}
export function defaultAppSettings(): Configuration {
return default_app_settings()
export function defaultAppSettings(): Partial<SaveSettingsPayload> {
// Immediately go from Configuration -> Partial<SaveSettingsPayload>
// The returned Rust type is Configuration but it's a lie. Every
// property in that returned object is optional. The Partial<T> essentially
// brings that type in-line with that definition.
return configurationToSettingsPayload(default_app_settings())
}
export function parseAppSettings(toml: string): Configuration | Error {
return parse_app_settings(toml)
export function parseAppSettings(toml: string): Partial<SaveSettingsPayload> {
return configurationToSettingsPayload(parse_app_settings(toml))
}
export function defaultProjectSettings(): ProjectConfiguration | Error {
return default_project_settings()
export function defaultProjectSettings(): Partial<SaveSettingsPayload> {
return projectConfigurationToSettingsPayload(default_project_settings())
}
export function parseProjectSettings(
toml: string
): ProjectConfiguration | Error {
return parse_project_settings(toml)
): Partial<SaveSettingsPayload> {
return projectConfigurationToSettingsPayload(parse_project_settings(toml))
}
export function parseProjectRoute(
@ -583,79 +592,3 @@ export function parseProjectRoute(
): ProjectRoute | Error {
return parse_project_route(JSON.stringify(configuration), route_str)
}
const DEFAULT_HOST = 'https://api.zoo.dev'
const SETTINGS_FILE_NAME = 'settings.toml'
const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
const PROJECT_FOLDER = 'zoo-modeling-app-projects'
const getAppSettingsFilePath = async () => {
const appConfig = await window.electron.getPath('appData')
const fullPath = window.electron.path.join(
appConfig,
window.electron.packageJson.name
)
try {
await window.electron.exists(fullPath)
} catch (e) {
// File/path doesn't exist
if (e.code === 'ENOENT') {
window.electron.mkdir(fullPath, { recursive: true })
}
}
return window.electron.path.join(fullPath, SETTINGS_FILE_NAME)
}
const getInitialDefaultDir = async () => {
const dir = await window.electron.getPath('documents')
return window.electron.path.join(dir, PROJECT_FOLDER)
}
export const readAppSettingsFile = async () => {
let settingsPath = await getAppSettingsFilePath()
try {
await window.electron.exists(settingsPath)
} catch (e) {
if (e === 'ENOENT') {
const config = defaultAppSettings()
config.settings.project.directory = await getInitialDefaultDir()
console.log(config)
return config
}
}
const configToml = await window.electron.readFile(settingsPath)
const configObj = parseProjectSettings(configStr)
return configObj
}
export const writeAppSettingsFile = async () => {
debugger
console.log("STUB")
}
let appStateStore = undefined
export const getState = async (): Promise<ProjectState | undefined> => {
return Promise.resolve(appStateStore)
}
export const setState = async (
state: ProjectState | undefined
): Promise<void> => {
appStateStore = state
}
const initializeProjectDirectory = () => {
debugger
console.log('STUB')
}
export const login = () => {
debugger
console.log('STUB')
}
export const getUser = (token: string, host: string) => {
debugger
console.log("STUB")
}

View File

@ -1,4 +1,4 @@
import { Models } from '@kittycad/lib/dist/types/src'
import { Models } from '@kittycad/lib'
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
import { Project } from 'wasm-lib/kcl/bindings/Project'
@ -8,14 +8,23 @@ import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
import { components } from './machine-api'
import { isDesktop } from './isDesktop'
export {
readAppSettingsFile,
writeAppSettingsFile,
getState,
setState,
getUser,
login,
import {
defaultAppSettings,
tomlStringify,
parseAppSettings,
parseProjectSettings,
} from 'lang/wasm'
export {
parseProjectRoute,
} from 'lang/wasm'
import { SaveSettingsPayload } from 'lib/settings/settingsUtils'
const DEFAULT_HOST = 'https://api.zoo.dev'
const SETTINGS_FILE_NAME = 'settings.toml'
const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
const PROJECT_FOLDER = 'zoo-modeling-app-projects'
const DEFAULT_PROJECT_KCL_FILE = "main.kcl"
// List machines on the local network.
export async function listMachines(): Promise<{
@ -34,15 +43,7 @@ export async function renameProjectDirectory(
projectPath: string,
newName: string
): Promise<string> {
return window.electron.ipcRenderer.invoke<string>(
'rename_project_directory',
{ projectPath, newName }
)
}
// Get the initial default dir for holding all projects.
export async function getInitialDefaultDir(): Promise<string> {
return window.electron.getInitialDefaultDir()
debugger
}
export async function showInFolder(path: string | undefined): Promise<void> {
@ -50,15 +51,15 @@ export async function showInFolder(path: string | undefined): Promise<void> {
console.error('path is undefined cannot call desktop showInFolder')
return
}
return window.electron.ipcRenderer.invoke('show_in_folder', { path })
debugger
}
export async function initializeProjectDirectory(
config: Configuration
export async function ensureProjectDirectoryExists(
config: Partial<SaveSettingsPayload>
): Promise<string | undefined> {
const projectDir = config.settings.project.directory
const projectDir = config.app.projectDirectory
try {
await window.electron.exists(projectDir)
await window.electron.stat(projectDir)
} catch (e) {
if (e === 'ENOENT') {
window.electron.mkdir(projectDir, { recursive: true }, (e) => {
@ -73,73 +74,360 @@ export async function initializeProjectDirectory(
export async function createNewProjectDirectory(
projectName: string,
initialCode?: string,
configuration?: Configuration
configuration?: Partial<SaveSettingsPayload>
): Promise<Project> {
if (!configuration) {
configuration = await readAppSettingsFile()
}
return window.electron.ipcRenderer.invoke('create_new_project_directory', {
configuration,
projectName,
initialCode,
})
const mainDir = await ensureProjectDirectoryExists(configuration)
if (!projectName) {
return Promise.reject('Project name cannot be empty.')
}
const projectDir = window.electron.path.join(mainDir, projectName)
try {
await window.electron.stat(projectDir)
} catch (e) {
if (e === 'ENOENT') {
window.electron.mkdir(projectDir, { recursive: true })
}
}
const projectFile = window.electron.path.join(
projectDir,
DEFAULT_PROJECT_KCL_FILE
)
await window.electron.writeFile(projectFile, initialCode ?? '')
return {
file: {
path: projectDir,
name: projectName,
// We don't need to recursively get all files in the project directory.
// Because we just created it and it's empty.
children: undefined,
},
default_file: projectFile,
metadata: undefined /* TODO */,
kcl_file_count: 1,
directory_count: 0,
}
}
export async function listProjects(
configuration?: Configuration
configuration?: Partial<SaveSettingsPayload>
): Promise<Project[]> {
const projectDir = await initializeProjectDirectory(configuration)
const projectDir = await ensureProjectDirectoryExists(configuration)
const projects = []
const entries = await window.electron.readdir(projectDir)
for (let entry of entries) {
// Ignore directories
console.log(entry)
const projectPath = window.electron.path.join(projectDir, entry)
// if it's not a directory ignore.
const isDirectory = await window.electron.statIsDirectory(projectPath)
if (!isDirectory) { continue }
const project = await getProjectInfo(projectPath)
// Needs at least one file to be added to the projects list
if (project.kcl_file_count === 0) { continue }
projects.push(project)
}
return projects
}
const IMPORT_FILE_EXTENSIONS = [
// TODO Use ImportFormat enum
"stp", "glb", "fbxb", "kcl"
];
const isRelevantFile = (filename: string): boolean => IMPORT_FILE_EXTENSIONS.some(
(ext) => filename.endsWith('.' + ext))
const collectAllFilesRecursiveFrom = async (path: string) => {
// Make sure the filesystem object exists.
try { await window.electron.stat(path) }
catch (e) {
if (e === 'ENOENT') {
return Promise.reject(new Error(`Directory ${path} does not exist`))
}
}
// Make sure the path is a directory.
const isPathDir = await window.electron.statIsDirectory(path)
if (!isPathDir) {
return Promise.reject(new Error(`Path ${path} is not a directory`));
}
const pathParts = path.split('/')
let entry = /* FileEntry */ {
name: pathParts.slice(-1)[0],
path: pathParts.slice(0, -1).join('/'),
children: [],
}
const children = []
const entries = await window.electron.readdir(path)
for (let e of entries) {
// ignore hidden files and directories (starting with a dot)
if (e.indexOf('.') === 0) { continue }
const ePath = window.electron.path.join(path, e)
const isEDir = await window.electron.statIsDirectory(ePath)
if (isEDir) {
const subChildren = await collectAllFilesRecursiveFrom(ePath)
children.push(subChildren)
} else {
if (!isRelevantFile(ePath)) { continue }
children.push(/* FileEntry */ {
name: e,
path: ePath.split('/').slice(0, -1).join('/'),
children: undefined,
});
}
}
// We don't set this to none if there are no children, because it's a directory.
entry.children = children
return entry
}
const getDefaultKclFileForDir = async (projectDir, file) => {
// Make sure the dir is a directory.
const isFileEntryDir = await window.electron.statIsDirectory(file.path)
if (!isFileEntryDir) {
return Promise.reject(new Error(`Path ${file.path} is not a directory`))
}
let defaultFilePath = window.electron.path.join(file.path, DEFAULT_PROJECT_KCL_FILE)
try { await window.eletron.stat(defaultFilePath) }
catch (e) {
if (e === 'ENOENT') {
// Find a kcl file in the directory.
if (file.children) {
for (let entry of file.children) {
if (entry.name.endsWith(".kcl")) {
return window.electron.path.join(projectDir, entry.name)
} else if (entry.children.is_some()) {
// Recursively find a kcl file in the directory.
return getDefaultKclFileForDir(entry.path, entry)
}
}
}
}
}
// If we didn't find a kcl file, create one.
await window.electron.writeFile(defaultFilePath, '')
return defaultFilePath
}
const kclFileCount = (file /* fileEntry */) => {
let count = 0;
if (file.children) {
for (let entry of file.children) {
if (entry.name.endsWith(".kcl")) {
count += 1
} else {
count += kclFileCount(entry)
}
}
}
return count
}
/// Populate the number of directories in the project.
const directoryCount = (file /* FileEntry */) => {
let count = 0
if (file.children) {
for (let entry of file.children) {
count += 1
directoryCount(entry)
}
}
return count
}
export async function getProjectInfo(
projectPath: string,
configuration?: Configuration
): Promise<Project> {
if (!configuration) {
configuration = await readAppSettingsFile()
}
return window.electron.ipcRenderer.invoke('get_project_info', {
configuration,
projectPath,
})
}
// Check the directory.
try { await window.electron.stat(projectPath) }
catch (e) {
if (e === 'ENOENT') {
return Promise.reject(new Error(`Project directory does not exist: ${project_path}`));
}
}
export async function parseProjectRoute(
configuration: Configuration,
route: string
): Promise<ProjectRoute> {
return window.electron.ipcRenderer.invoke('parse_project_route', {
configuration,
route,
})
// Make sure it is a directory.
const projectPathIsDir = await window.electron.statIsDirectory(projectPath)
if (!projectPathIsDir) {
return Promise.reject(new Error(`Project path is not a directory: ${project_path}`));
}
let walked = await collectAllFilesRecursiveFrom(projectPath)
let default_file = await getDefaultKclFileForDir(projectPath, walked)
let project = /* FileEntry */ {
file: walked,
metadata: undefined,
kcl_file_count: 0,
directory_count: 0,
default_file,
};
// Populate the number of KCL files in the project.
project.kcl_file_count = kclFileCount(project.file)
//Populate the number of directories in the project.
project.directory_count = directoryCount(project.file)
return project
}
export async function readDirRecursive(path: string): Promise<FileEntry[]> {
return window.electron.ipcRenderer.invoke('read_dir_recursive', { path })
}
// Read project settings file.
export async function readProjectSettingsFile(
projectPath: string
): Promise<ProjectConfiguration> {
return window.electron.ipcRenderer.invoke('read_project_settings_file', {
projectPath,
})
debugger
}
// Write project settings file.
export async function writeProjectSettingsFile(
projectPath: string,
settings: ProjectConfiguration
): Promise<void> {
return window.electron.ipcRenderer.invoke('write_project_settings_file', {
projectPath,
configuration: settings,
})
export async function writeProjectSettingsFile({
projectPath,
configuration,
}: {
projectPath: string
configuration: Partial<SaveSettingsPayload>
}): Promise<void> {
const projectSettingsFilePath = await getProjectSettingsFilePath(projectPath)
const tomlStr = tomlStringify(configuration)
return window.electron.writeFile(projectSettingsFilePath, tomlStr)
}
const getAppSettingsFilePath = async () => {
const appConfig = await window.electron.getPath('appData')
const fullPath = window.electron.path.join(
appConfig,
window.electron.packageJson.name
)
try {
await window.electron.stat(fullPath)
} catch (e) {
// File/path doesn't exist
if (e.code === 'ENOENT') {
window.electron.mkdir(fullPath, { recursive: true })
}
}
return window.electron.path.join(fullPath, SETTINGS_FILE_NAME)
}
const getProjectSettingsFilePath = async (projectPath: string) => {
try {
await window.electron.stat(projectPath)
} catch (e) {
if (e.code === 'ENOENT') {
window.electron.mkdir(projectPath, { recursive: true })
}
}
return window.electron.path.join(projectPath, PROJECT_SETTINGS_FILE_NAME)
}
export const getInitialDefaultDir = async () => {
const dir = await window.electron.getPath('documents')
return window.electron.path.join(dir, PROJECT_FOLDER)
}
export const readProjectSettingsFile = async (projectPath: string): ProjectConfiguration => {
let settingsPath = await getProjectSettingsFilePath(projectPath)
// Check if this file exists.
try { await window.electron.stat(settingsPath) }
catch (e) {
if (e === 'ENOENT') {
// Return the default configuration.
return {}
}
}
const configToml = await window.electron.readFile(settingsPath)
const configObj = parseProjectSettings(configToml)
return configObj
}
export const readAppSettingsFile = async () => {
let settingsPath = await getAppSettingsFilePath()
try {
await window.electron.stat(settingsPath)
} catch (e) {
if (e === 'ENOENT') {
const config = defaultAppSettings()
config.app.projectDirectory = await getInitialDefaultDir()
return config
}
}
const configToml = await window.electron.readFile(settingsPath)
const configObj = parseAppSettings(configToml)
return configObj
}
export const writeAppSettingsFile = async (
config: Partial<SaveSettingsPayload>
) => {
const appSettingsFilePath = await getAppSettingsFilePath()
const tomlStr = tomlStringify(config)
return window.electron.writeFile(appSettingsFilePath, tomlStr)
}
let appStateStore = undefined
export const getState = async (): Promise<ProjectState | undefined> => {
return Promise.resolve(appStateStore)
}
export const setState = async (
state: ProjectState | undefined
): Promise<void> => {
appStateStore = state
}
export const login = () => {
console.log('STUB')
}
export const getUser = async (
token: string,
hostname: string
): Models['User_type'] => {
// Use the host passed in if it's set.
// Otherwise, use the default host.
const host = !hostname ? DEFAULT_HOST : hostname
// Change the baseURL to the one we want.
let baseurl = host
if (!(host.indexOf('http://') === 0) && !(host.indexOf('https://') === 0)) {
baseurl = `https://${host}`
if (host.indexOf('localhost') === 0) {
baseurl = `http://${host}`
}
}
// Use kittycad library to fetch the user info from /user/me
if (baseurl != DEFAULT_HOST) {
// The TypeScript generated library uses environment variables for this
// because it was intended for NodeJS.
window.electron.process.env.BASE_URL(baseurl)
}
const user = await window.electron.kittycad.users.get_user_self({
client: { token },
})
return user
}

View File

@ -4,22 +4,46 @@ import fs from 'node:fs/promises'
import packageJson from '../../package.json'
const readFile = (path: string) => fs.readFile(path, 'utf-8')
const writeFile = (path: string, data: string) =>
fs.writeFile(path, data, 'utf-8')
const readdir = (path: string) => fs.readdir(path, 'utf-8')
const exists = (path: string) =>
new Promise((resolve, reject) =>
fs.stat(path, (err, data) => {
if (err) return reject(err.code)
return resolve(data)
})
)
const stat = (path: string) => fs.stat(path).catch((e) => Promise.reject(e.code))
// Electron has behavior where it doesn't clone the prototype chain over.
// So we need to call stat.isDirectory on this side.
const statIsDirectory = (path: string) => stat(path).then((res) => res.isDirectory())
const getPath = async (name: string) => ipcRenderer.invoke('app.getPath', name)
contextBridge.exposeInMainWorld('electron', {
readFile,
readdir,
path,
exists,
mkdir: fs.mkdir,
getPath,
packageJson,
const exposeProcessEnv = (varName: string) => {
return {
[varName](value?: string) {
if (value !== undefined) {
process.env[varName] = value
} else {
return process.env[varName]
}
},
}
}
import('@kittycad/lib').then((kittycad) => {
contextBridge.exposeInMainWorld('electron', {
readFile,
writeFile,
readdir,
path,
stat,
statIsDirectory,
mkdir: fs.mkdir,
getPath,
packageJson,
platform: process.platform,
process: {
// Setter/getter has to be created because
// these are read-only over the boundary.
env: Object.assign({}, exposeProcessEnv('BASE_URL')),
},
kittycad: {
users: kittycad.users,
},
})
})

View File

@ -11,12 +11,11 @@ import {
import { loadAndValidateSettings } from './settings/settingsUtils'
import makeUrlPathRelative from './makeUrlPathRelative'
import { sep } from '@tauri-apps/api/path'
import { readTextFile } from '@tauri-apps/plugin-fs'
import { codeManager, kclManager } from 'lib/singletons'
import { fileSystemManager } from 'lang/std/fileSystemManager'
import {
getProjectInfo,
initializeProjectDirectory,
ensureProjectDirectoryExists,
listProjects,
} from './desktop'
import { createSettings } from './settings/initialSettings'
@ -90,14 +89,14 @@ export const fileLoader: LoaderFunction = async ({
if (!current_file_name || !current_file_path || !project_name) {
return redirect(
`${paths.FILE}/${encodeURIComponent(
`${params.id}${isDesktop() ? sep() : '/'}${PROJECT_ENTRYPOINT}`
`${params.id}${isDesktop() ? window.electron.path.sep : '/'}${PROJECT_ENTRYPOINT}`
)}`
)
}
// TODO: PROJECT_ENTRYPOINT is hardcoded
// until we support setting a project's entrypoint file
const code = await readTextFile(current_file_path)
const code = await window.electron.readFile(current_file_path)
// Update both the state and the editor's code.
// We explicitly do not write to the file here since we are loading from
@ -162,7 +161,7 @@ export const homeLoader: LoaderFunction = async (): Promise<
}
const { configuration } = await loadAndValidateSettings()
const projectDir = await initializeProjectDirectory(configuration)
const projectDir = await ensureProjectDirectoryExists(configuration)
if (projectDir) {
const projects = await listProjects(configuration)

View File

@ -27,7 +27,7 @@ import { BROWSER_PROJECT_NAME } from 'lib/constants'
* We do this because the JS settings type has all the fancy shit
* for hiding and showing settings.
**/
function configurationToSettingsPayload(
export function configurationToSettingsPayload(
configuration: Configuration
): Partial<SaveSettingsPayload> {
return {
@ -65,7 +65,7 @@ function configurationToSettingsPayload(
}
}
function projectConfigurationToSettingsPayload(
export function projectConfigurationToSettingsPayload(
configuration: ProjectConfiguration
): Partial<SaveSettingsPayload> {
return {
@ -165,14 +165,12 @@ export async function loadAndValidateSettings(
await initPromise
// Load the app settings from the file system or localStorage.
const appSettings = onDesktop
const appSettingsPayload = onDesktop
? await readAppSettingsFile()
: readLocalStorageAppSettingsFile()
if (err(appSettings)) return Promise.reject(appSettings)
if (err(appSettingsPayload)) return Promise.reject(appSettingsPayload)
// Convert the app settings to the JS settings format.
const appSettingsPayload = configurationToSettingsPayload(appSettings)
setSettingsAtLevel(settings, 'user', appSettingsPayload)
// Load the project settings if they exist
@ -190,7 +188,7 @@ export async function loadAndValidateSettings(
}
// Return the settings object
return { settings, configuration: appSettings }
return { settings, configuration: appSettingsPayload }
}
export async function saveSettings(
@ -207,7 +205,7 @@ export async function saveSettings(
if (err(tomlString)) return
// Parse this as a Configuration.
const appSettings = parseAppSettings(tomlString)
const appSettings = { settings: parseAppSettings(tomlString) }
if (err(appSettings)) return
const tomlString2 = tomlStringify(appSettings)

View File

@ -1,12 +1,4 @@
let toast = undefined
try {
global
} catch (e) {
import('react-hot-toast').then((_toast) => {
toast = _toast
})
}
import toast from 'react-hot-toast'
type ExcludeErr<T> = Exclude<T, Error>
@ -16,6 +8,7 @@ export function err<T>(value: ExcludeErr<T> | Error): value is Error {
return false
}
console.error(value)
return true
}

View File

@ -146,7 +146,10 @@ async function getUser(context: UserContext) {
})
.then((res) => res.json())
.catch((err) => console.error('error from Browser getUser', err))
: getUserDesktop(context.token, VITE_KC_API_BASE_URL)
: getUserDesktop(
'f8864550-84a6-4a06-8d3f-68d29bbe5608' /* context.token */,
VITE_KC_API_BASE_URL
)
const user = await userPromise

View File

@ -11,7 +11,7 @@ if (require('electron-squirrel-startup')) {
}
const createWindow = () => {
const mainWindow = new BrowserWindow({
let mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {

View File

@ -82,7 +82,7 @@ const Home = () => {
event: EventFrom<typeof homeMachine>
) => {
if (event.data && 'name' in event.data) {
let projectPath = context.defaultDirectory + sep() + event.data.name
let projectPath = context.defaultDirectory + window.electron.path.sep + event.data.name
onProjectOpen(
{
name: event.data.name,
@ -192,15 +192,15 @@ const Home = () => {
new FormData(e.target as HTMLFormElement)
)
if (newProjectName !== project.name) {
if (newProjectName !== project.file.name) {
send('Rename project', {
data: { oldName: project.name, newName: newProjectName },
data: { oldName: project.file.name, newName: newProjectName },
})
}
}
async function handleDeleteProject(project: Project) {
send('Delete project', { data: { name: project.name || '' } })
send('Delete project', { data: { name: project.file.name || '' } })
}
return (
@ -296,7 +296,7 @@ const Home = () => {
<ul className="grid w-full grid-cols-4 gap-4">
{searchResults.sort(getSortFunction(sort)).map((project) => (
<ProjectCard
key={project.name}
key={project.file.name}
project={project}
handleRenameProject={handleRenameProject}
handleDeleteProject={handleDeleteProject}

View File

@ -495,7 +495,6 @@ pub fn default_app_settings() -> Result<JsValue, String> {
console_error_panic_hook::set_once();
let settings = kcl_lib::settings::types::Configuration::default();
web_sys::console::log(&js_sys::Array::of1(&JsValue::from_str(&format!("{:?}", settings))));
serde_wasm_bindgen::to_value(&settings).map_err(|e| e.to_string())
}