This commit is contained in:
49lf
2024-07-31 15:31:55 -04:00
parent 53467ee954
commit 638217311b
17 changed files with 298 additions and 218 deletions

View File

@ -49,7 +49,9 @@ export const FileMachineProvider = ({
commandBarSend({ type: 'Close' }) commandBarSend({ type: 'Close' })
navigate( navigate(
`${paths.FILE}/${encodeURIComponent( `${paths.FILE}/${encodeURIComponent(
context.selectedDirectory + window.electron.path.sep + event.data.name context.selectedDirectory +
window.electron.path.sep +
event.data.name
)}` )}`
) )
} else if ( } else if (
@ -97,7 +99,10 @@ export const FileMachineProvider = ({
let createdPath: string let createdPath: string
if (event.data.makeDir) { if (event.data.makeDir) {
createdPath = window.electron.path.join(context.selectedDirectory.path, createdName) createdPath = window.electron.path.join(
context.selectedDirectory.path,
createdName
)
await window.electron.mkdir(createdPath) await window.electron.mkdir(createdPath)
} else { } else {
createdPath = createdPath =
@ -119,8 +124,14 @@ export const FileMachineProvider = ({
) => { ) => {
const { oldName, newName, isDir } = event.data const { oldName, newName, isDir } = event.data
const name = newName ? newName : DEFAULT_FILE_NAME const name = newName ? newName : DEFAULT_FILE_NAME
const oldPath = window.electron.path.join(context.selectedDirectory.path, oldName) const oldPath = window.electron.path.join(
const newDirPath = window.electron.path.join(context.selectedDirectory.path, name) context.selectedDirectory.path,
oldName
)
const newDirPath = window.electron.path.join(
context.selectedDirectory.path,
name
)
const newPath = const newPath =
newDirPath + (name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT) newDirPath + (name.endsWith(FILE_EXT) || isDir ? '' : FILE_EXT)
@ -152,13 +163,15 @@ export const FileMachineProvider = ({
const isDir = !!event.data.children const isDir = !!event.data.children
if (isDir) { if (isDir) {
await window.electron.rm(event.data.path, { await window.electron
recursive: true, .rm(event.data.path, {
}).catch((e) => console.error('Error deleting directory', e)) recursive: true,
})
.catch((e) => console.error('Error deleting directory', e))
} else { } else {
await window.electron.rm(event.data.path).catch((e) => await window.electron
console.error('Error deleting file', e) .rm(event.data.path)
) .catch((e) => console.error('Error deleting file', e))
} }
// If we just deleted the current file or one of its parent directories, // If we just deleted the current file or one of its parent directories,

View File

@ -207,7 +207,9 @@ function ProjectMenuPopover({
<div className="flex flex-col items-start py-0.5"> <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"> <span className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block">
{isDesktop() && file?.name {isDesktop() && file?.name
? file.name.slice(file.name.lastIndexOf(window.electron.path.sep) + 1) ? file.name.slice(
file.name.lastIndexOf(window.electron.path.sep) + 1
)
: APP_NAME} : APP_NAME}
</span> </span>
{isDesktop() && project?.name && ( {isDesktop() && project?.name && (

View File

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

View File

@ -119,11 +119,13 @@ export default class CodeManager {
// Wait one event loop to give a chance for params to be set // Wait one event loop to give a chance for params to be set
// Save the file to disk // Save the file to disk
this._currentFilePath && this._currentFilePath &&
window.electron.writeFile(this._currentFilePath, this.code ?? '').catch((err) => { window.electron
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254) .writeFile(this._currentFilePath, this.code ?? '')
console.error('error saving file', err) .catch((err) => {
toast.error('Error saving file, please check file permissions') // 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')
})
}) })
} else { } else {
safeLSSetItem(PERSIST_CODE_KEY, this.code) safeLSSetItem(PERSIST_CODE_KEY, this.code)

View File

@ -52,8 +52,9 @@ class FileSystemManager {
return Promise.reject(new Error(`Error checking file exists: ${error}`)) return Promise.reject(new Error(`Error checking file exists: ${error}`))
}) })
.then(async (file) => { .then(async (file) => {
try { await window.electron.stat(file) } try {
catch (e) { await window.electron.stat(file)
} catch (e) {
if (e === 'ENOENT') { if (e === 'ENOENT') {
return false return false
} }
@ -77,7 +78,8 @@ class FileSystemManager {
return Promise.reject(new Error(`Error joining dir: ${error}`)) return Promise.reject(new Error(`Error joining dir: ${error}`))
}) })
.then((filepath) => { .then((filepath) => {
return window.electron.readdir(filepath) return window.electron
.readdir(filepath)
.catch((error) => { .catch((error) => {
return Promise.reject(new Error(`Error reading dir: ${error}`)) return Promise.reject(new Error(`Error reading dir: ${error}`))
}) })

View File

@ -13,15 +13,13 @@ import {
parseAppSettings, parseAppSettings,
parseProjectSettings, parseProjectSettings,
} from 'lang/wasm' } from 'lang/wasm'
export { export { parseProjectRoute } from 'lang/wasm'
parseProjectRoute,
} from 'lang/wasm'
const DEFAULT_HOST = 'https://api.zoo.dev' const DEFAULT_HOST = 'https://api.zoo.dev'
const SETTINGS_FILE_NAME = 'settings.toml' const SETTINGS_FILE_NAME = 'settings.toml'
const PROJECT_SETTINGS_FILE_NAME = 'project.toml' const PROJECT_SETTINGS_FILE_NAME = 'project.toml'
const PROJECT_FOLDER = 'zoo-modeling-app-projects' const PROJECT_FOLDER = 'zoo-modeling-app-projects'
const DEFAULT_PROJECT_KCL_FILE = "main.kcl" const DEFAULT_PROJECT_KCL_FILE = 'main.kcl'
// List machines on the local network. // List machines on the local network.
export async function listMachines(): Promise<{ export async function listMachines(): Promise<{
@ -44,20 +42,28 @@ export async function renameProjectDirectory(
return Promise.reject(new Error(`New name for project cannot be empty`)) return Promise.reject(new Error(`New name for project cannot be empty`))
} }
try { await window.electron.stat(projectPath) } try {
catch (e) { await window.electron.stat(projectPath)
} catch (e) {
if (e === 'ENOENT') { if (e === 'ENOENT') {
return Promise.reject(new Error(`Path ${projectPath} is not a directory`)) return Promise.reject(new Error(`Path ${projectPath} is not a directory`))
} }
} }
// Make sure the new name does not exist. // Make sure the new name does not exist.
const newPath = window.electron.path.join(projectPath.split('/').slice(0, -1).join('/'), newName) const newPath = window.electron.path.join(
projectPath.split('/').slice(0, -1).join('/'),
newName
)
try { try {
await window.electron.stat(newPath) await window.electron.stat(newPath)
// If we get here it means the stat succeeded and there's a file already // If we get here it means the stat succeeded and there's a file already
// with the same name... // with the same name...
return Promise.reject(new Error(`Path ${newPath} already exists, cannot rename to an existing path`)) return Promise.reject(
new Error(
`Path ${newPath} already exists, cannot rename to an existing path`
)
)
} catch (e) { } catch (e) {
// Otherwise if it failed and the failure is "it doesnt exist" then rename it! // Otherwise if it failed and the failure is "it doesnt exist" then rename it!
if (e === 'ENOENT') { if (e === 'ENOENT') {
@ -143,11 +149,15 @@ export async function listProjects(
const projectPath = window.electron.path.join(projectDir, entry) const projectPath = window.electron.path.join(projectDir, entry)
// if it's not a directory ignore. // if it's not a directory ignore.
const isDirectory = await window.electron.statIsDirectory(projectPath) const isDirectory = await window.electron.statIsDirectory(projectPath)
if (!isDirectory) { continue } if (!isDirectory) {
continue
}
const project = await getProjectInfo(projectPath) const project = await getProjectInfo(projectPath)
// Needs at least one file to be added to the projects list // Needs at least one file to be added to the projects list
if (project.kcl_file_count === 0) { continue } if (project.kcl_file_count === 0) {
continue
}
projects.push(project) projects.push(project)
} }
return projects return projects
@ -155,90 +165,104 @@ export async function listProjects(
const IMPORT_FILE_EXTENSIONS = [ const IMPORT_FILE_EXTENSIONS = [
// TODO Use ImportFormat enum // TODO Use ImportFormat enum
"stp", "glb", "fbxb", "kcl" 'stp',
'glb',
'fbxb',
'kcl',
] ]
const isRelevantFile = (filename: string): boolean => IMPORT_FILE_EXTENSIONS.some( const isRelevantFile = (filename: string): boolean =>
(ext) => filename.endsWith('.' + ext)) IMPORT_FILE_EXTENSIONS.some((ext) => filename.endsWith('.' + ext))
const collectAllFilesRecursiveFrom = async (path: string) => { const collectAllFilesRecursiveFrom = async (path: string) => {
// Make sure the filesystem object exists. // Make sure the filesystem object exists.
try { await window.electron.stat(path) } try {
catch (e) { await window.electron.stat(path)
if (e === 'ENOENT') { } catch (e) {
return Promise.reject(new Error(`Directory ${path} does not exist`)) 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,
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 */ {
// Make sure the path is a directory. name: e,
const isPathDir = await window.electron.statIsDirectory(path) path: ePath,
if (!isPathDir) { children: undefined,
return Promise.reject(new Error(`Path ${path} is not a directory`))
}
const pathParts = path.split('/')
let entry = /* FileEntry */ {
name: pathParts.slice(-1)[0],
path,
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,
children: undefined,
})
} }
)
} }
}
// We don't set this to none if there are no children, because it's a directory. // We don't set this to none if there are no children, because it's a directory.
entry.children = children entry.children = children
return entry return entry
} }
const getDefaultKclFileForDir = async (projectDir, file) => { const getDefaultKclFileForDir = async (projectDir, file) => {
// Make sure the dir is a directory. // Make sure the dir is a directory.
const isFileEntryDir = await window.electron.statIsDirectory(projectDir) const isFileEntryDir = await window.electron.statIsDirectory(projectDir)
if (!isFileEntryDir) { if (!isFileEntryDir) {
return Promise.reject(new Error(`Path ${projectDir} is not a directory`)) return Promise.reject(new Error(`Path ${projectDir} is not a directory`))
} }
let defaultFilePath = window.electron.path.join(projectDir, DEFAULT_PROJECT_KCL_FILE) let defaultFilePath = window.electron.path.join(
try { await window.electron.stat(defaultFilePath) } projectDir,
catch (e) { DEFAULT_PROJECT_KCL_FILE
if (e === 'ENOENT') { )
// Find a kcl file in the directory. try {
if (file.children) { await window.electron.stat(defaultFilePath)
for (let entry of file.children) { } catch (e) {
if (entry.name.endsWith(".kcl")) { if (e === 'ENOENT') {
return window.electron.path.join(projectDir, entry.name) // Find a kcl file in the directory.
} else if (entry.children.length > 0) { if (file.children) {
// Recursively find a kcl file in the directory. for (let entry of file.children) {
return getDefaultKclFileForDir(entry.path, entry) if (entry.name.endsWith('.kcl')) {
} return window.electron.path.join(projectDir, entry.name)
} } else if (entry.children.length > 0) {
// If we didn't find a kcl file, create one. // Recursively find a kcl file in the directory.
await window.electron.writeFile(defaultFilePath, '') return getDefaultKclFileForDir(entry.path, entry)
return defaultFilePath
} }
}
// If we didn't find a kcl file, create one.
await window.electron.writeFile(defaultFilePath, '')
return defaultFilePath
} }
}
} }
if (!file.children) { if (!file.children) {
@ -249,70 +273,72 @@ const getDefaultKclFileForDir = async (projectDir, file) => {
} }
const kclFileCount = (file /* fileEntry */) => { const kclFileCount = (file /* fileEntry */) => {
let count = 0 let count = 0
if (file.children) { if (file.children) {
for (let entry of file.children) { for (let entry of file.children) {
if (entry.name.endsWith(".kcl")) { if (entry.name.endsWith('.kcl')) {
count += 1 count += 1
} else { } else {
count += kclFileCount(entry) count += kclFileCount(entry)
} }
}
} }
}
return count return count
} }
/// Populate the number of directories in the project. /// Populate the number of directories in the project.
const directoryCount = (file /* FileEntry */) => { const directoryCount = (file /* FileEntry */) => {
let count = 0 let count = 0
if (file.children) { if (file.children) {
for (let entry of file.children) { for (let entry of file.children) {
count += 1 count += 1
directoryCount(entry) directoryCount(entry)
}
} }
}
return count return count
} }
export async function getProjectInfo(projectPath: string): Promise<Project> {
export async function getProjectInfo( // Check the directory.
projectPath: string, try {
): Promise<Project> { await window.electron.stat(projectPath)
// Check the directory. } catch (e) {
try { await window.electron.stat(projectPath) } if (e === 'ENOENT') {
catch (e) { return Promise.reject(
if (e === 'ENOENT') { new Error(`Project directory does not exist: ${project_path}`)
return Promise.reject(new Error(`Project directory does not exist: ${project_path}`)) )
}
} }
}
// Make sure it is a directory. // Make sure it is a directory.
const projectPathIsDir = await window.electron.statIsDirectory(projectPath) const projectPathIsDir = await window.electron.statIsDirectory(projectPath)
if (!projectPathIsDir) { if (!projectPathIsDir) {
return Promise.reject(new Error(`Project path is not a directory: ${project_path}`)) return Promise.reject(
} new Error(`Project path is not a directory: ${project_path}`)
)
}
let walked = await collectAllFilesRecursiveFrom(projectPath) let walked = await collectAllFilesRecursiveFrom(projectPath)
let default_file = await getDefaultKclFileForDir(projectPath, walked) let default_file = await getDefaultKclFileForDir(projectPath, walked)
const metadata = await window.electron.stat(projectPath) const metadata = await window.electron.stat(projectPath)
let project = /* FileEntry */ { let project = /* FileEntry */ {
...walked, ...walked,
metadata, metadata,
kcl_file_count: 0, kcl_file_count: 0,
directory_count: 0, directory_count: 0,
default_file, default_file,
} }
// Populate the number of KCL files in the project. // Populate the number of KCL files in the project.
project.kcl_file_count = kclFileCount(project) project.kcl_file_count = kclFileCount(project)
//Populate the number of directories in the project. //Populate the number of directories in the project.
project.directory_count = directoryCount(project) project.directory_count = directoryCount(project)
return project return project
} }
// Write project settings file. // Write project settings file.
@ -361,18 +387,20 @@ export const getInitialDefaultDir = async () => {
return window.electron.path.join(dir, PROJECT_FOLDER) return window.electron.path.join(dir, PROJECT_FOLDER)
} }
export const readProjectSettingsFile = async (projectPath: string): ProjectConfiguration => { export const readProjectSettingsFile = async (
let settingsPath = await getProjectSettingsFilePath(projectPath) projectPath: string
): ProjectConfiguration => {
let settingsPath = await getProjectSettingsFilePath(projectPath)
// Check if this file exists. // Check if this file exists.
try { await window.electron.stat(settingsPath) } try {
catch (e) { await window.electron.stat(settingsPath)
if (e === 'ENOENT') { } catch (e) {
// Return the default configuration. if (e === 'ENOENT') {
return {} // Return the default configuration.
} return {}
} }
}
const configToml = await window.electron.readFile(settingsPath) const configToml = await window.electron.readFile(settingsPath)
const configObj = parseProjectSettings(configToml) const configObj = parseProjectSettings(configToml)
@ -444,4 +472,3 @@ export const getUser = async (
}) })
return user return user
} }

View File

@ -63,7 +63,10 @@ function interpolateProjectName(projectName: string) {
} }
// Returns the next available index for a project name // Returns the next available index for a project name
export function getNextProjectIndex(projectName: string, projects: FileEntry[]) { export function getNextProjectIndex(
projectName: string,
projects: FileEntry[]
) {
const regex = interpolateProjectName(projectName) const regex = interpolateProjectName(projectName)
const matches = projects.map((project) => project.name?.match(regex)) const matches = projects.map((project) => project.name?.match(regex))
const indices = matches const indices = matches

View File

@ -6,7 +6,8 @@ import packageJson from '../../package.json'
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args) const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args) const save = (args: any) => ipcRenderer.invoke('dialog.showSaveDialog', args)
const openExternal = (url: any) => ipcRenderer.invoke('shell.openExternal', url) const openExternal = (url: any) => ipcRenderer.invoke('shell.openExternal', url)
const showInFolder = (path: string) => ipcRenderer.invoke('shell.showItemInFolder', path) const showInFolder = (path: string) =>
ipcRenderer.invoke('shell.showItemInFolder', path)
const login = (host: string) => ipcRenderer.invoke('login', host) const login = (host: string) => ipcRenderer.invoke('login', host)
const readFile = (path: string) => fs.readFile(path, 'utf-8') const readFile = (path: string) => fs.readFile(path, 'utf-8')
@ -14,10 +15,12 @@ const rename = (prev: string, next: string) => fs.rename(prev, next)
const writeFile = (path: string, data: string) => const writeFile = (path: string, data: string) =>
fs.writeFile(path, data, 'utf-8') fs.writeFile(path, data, 'utf-8')
const readdir = (path: string) => fs.readdir(path, 'utf-8') const readdir = (path: string) => fs.readdir(path, 'utf-8')
const stat = (path: string) => fs.stat(path).catch((e) => Promise.reject(e.code)) 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. // Electron has behavior where it doesn't clone the prototype chain over.
// So we need to call stat.isDirectory on this side. // So we need to call stat.isDirectory on this side.
const statIsDirectory = (path: string) => stat(path).then((res) => res.isDirectory()) const statIsDirectory = (path: string) =>
stat(path).then((res) => res.isDirectory())
const getPath = async (name: string) => ipcRenderer.invoke('app.getPath', name) const getPath = async (name: string) => ipcRenderer.invoke('app.getPath', name)
const exposeProcessEnv = (varName: string) => { const exposeProcessEnv = (varName: string) => {

View File

@ -30,7 +30,10 @@ const save_ = async (file: ModelingAppFile) => {
if (filePathMeta.canceled) return if (filePathMeta.canceled) return
// Write the file. // Write the file.
await window.electron.writeFile(filePathMeta.filePath, new Uint8Array(file.contents)) await window.electron.writeFile(
filePathMeta.filePath,
new Uint8Array(file.contents)
)
} else { } else {
// Download the file to the user's computer. // Download the file to the user's computer.
// Now we need to download the files to the user's downloads folder. // Now we need to download the files to the user's downloads folder.

View File

@ -1,13 +1,14 @@
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
export const openExternalBrowserIfDesktop = (to) => function(e) { export const openExternalBrowserIfDesktop = (to) =>
if (isDesktop()) { function (e) {
window.electron.openExternal(to || e.currentTarget.href) if (isDesktop()) {
e.preventDefault() window.electron.openExternal(to || e.currentTarget.href)
e.stopPropagation() e.preventDefault()
return false e.stopPropagation()
return false
}
} }
}
// Open a new browser window desktop style or browser style. // Open a new browser window desktop style or browser style.
export default async function openWindow(url: string) { export default async function openWindow(url: string) {

View File

@ -70,7 +70,9 @@ export const onboardingRedirectLoader: ActionFunction = async (args) => {
return settingsLoader(args) return settingsLoader(args)
} }
export const fileLoader: LoaderFunction = async (routerData): Promise<FileLoaderData | Response> => { export const fileLoader: LoaderFunction = async (
routerData
): Promise<FileLoaderData | Response> => {
const { params } = routerData const { params } = routerData
let { configuration } = await loadAndValidateSettings() let { configuration } = await loadAndValidateSettings()
@ -93,7 +95,9 @@ export const fileLoader: LoaderFunction = async (routerData): Promise<FileLoader
if (!current_file_name || !current_file_path || !project_name) { if (!current_file_name || !current_file_path || !project_name) {
return redirect( return redirect(
`${paths.FILE}/${encodeURIComponent( `${paths.FILE}/${encodeURIComponent(
`${params.id}${isDesktop() ? window.electron.path.sep : '/'}${PROJECT_ENTRYPOINT}` `${params.id}${
isDesktop() ? window.electron.path.sep : '/'
}${PROJECT_ENTRYPOINT}`
)}` )}`
) )
} }

View File

@ -209,14 +209,15 @@ export function createSettings() {
// In desktop end-to-end tests we can't control the file picker, // In desktop end-to-end tests we can't control the file picker,
// so we seed the new directory value in the element's dataset // so we seed the new directory value in the element's dataset
const inputRefVal = inputRef.current?.dataset.testValue const inputRefVal = inputRef.current?.dataset.testValue
if (inputRef.current && inputRefVal && !Array.isArray(inputRefVal)) { if (
inputRef.current &&
inputRefVal &&
!Array.isArray(inputRefVal)
) {
updateValue(inputRefVal) updateValue(inputRefVal)
} else { } else {
const newPath = await window.electron.open({ const newPath = await window.electron.open({
properties: [ properties: ['openDirectory', 'createDirectory'],
'openDirectory',
'createDirectory',
],
defaultPath: value, defaultPath: value,
title: 'Choose a new project directory', title: 'Choose a new project directory',
}) })

View File

@ -237,7 +237,10 @@ export async function saveSettings(
// Write the project settings. // Write the project settings.
if (onDesktop) { if (onDesktop) {
await writeProjectSettingsFile({ projectPath, configuration: { settings: projectSettings }}) await writeProjectSettingsFile({
projectPath,
configuration: { settings: projectSettings },
})
} else { } else {
localStorage.setItem(localStorageProjectSettingsPath(), tomlStr) localStorage.setItem(localStorageProjectSettingsPath(), tomlStr)
} }

View File

@ -1,4 +1,7 @@
import { getNextProjectIndex, interpolateProjectNameWithIndex } from './desktopFS' import {
getNextProjectIndex,
interpolateProjectNameWithIndex,
} from './desktopFS'
import { MAX_PADDING } from './constants' import { MAX_PADDING } from './constants'
describe('Test project name utility functions', () => { describe('Test project name utility functions', () => {

View File

@ -146,7 +146,7 @@ async function getUser(context: UserContext) {
}) })
.then((res) => res.json()) .then((res) => res.json())
.catch((err) => console.error('error from Browser getUser', err)) .catch((err) => console.error('error from Browser getUser', err))
: getUserDesktop(context.token, VITE_KC_API_BASE_URL ) : getUserDesktop(context.token, VITE_KC_API_BASE_URL)
const user = await userPromise const user = await userPromise

View File

@ -71,45 +71,47 @@ ipcMain.handle('shell.openExternal', (event, data) => {
}) })
ipcMain.handle('login', async (event, host) => { ipcMain.handle('login', async (event, host) => {
console.log('Logging in...') console.log('Logging in...')
// Do an OAuth 2.0 Device Authorization Grant dance to get a token. // Do an OAuth 2.0 Device Authorization Grant dance to get a token.
const issuer = new Issuer({ const issuer = new Issuer({
device_authorization_endpoint: `${host}/oauth2/device/auth`, device_authorization_endpoint: `${host}/oauth2/device/auth`,
token_endpoint: `${host}/oauth2/device/token`, token_endpoint: `${host}/oauth2/device/token`,
}) })
const client = new issuer.Client({ const client = new issuer.Client({
// We can hardcode the client ID. // We can hardcode the client ID.
// This value is safe to be embedded in version control. // This value is safe to be embedded in version control.
// This is the client ID of the KittyCAD app. // This is the client ID of the KittyCAD app.
client_id: "2af127fb-e14e-400a-9c57-a9ed08d1a5b7", client_id: '2af127fb-e14e-400a-9c57-a9ed08d1a5b7',
token_endpoint_auth_method: 'none', token_endpoint_auth_method: 'none',
}) })
const handle = await client.deviceAuthorization() const handle = await client.deviceAuthorization()
// Open the system browser with the auth_uri. // Open the system browser with the auth_uri.
// We do this in the browser and not a separate window because we want 1password and // We do this in the browser and not a separate window because we want 1password and
// other crap to work well. // other crap to work well.
// TODO: find a better way to share this value with tauri e2e tests // TODO: find a better way to share this value with tauri e2e tests
// Here we're using an env var to enable the /tmp file (windows not supported for now) // Here we're using an env var to enable the /tmp file (windows not supported for now)
// and bypass the shell::open call as it fails on GitHub Actions. // and bypass the shell::open call as it fails on GitHub Actions.
const e2e_tauri_enabled = process.env.E2E_TAURI_ENABLED const e2e_tauri_enabled = process.env.E2E_TAURI_ENABLED
if (e2e_tauri_enabled) { if (e2e_tauri_enabled) {
console.warn(`E2E_TAURI_ENABLED is set, won't open ${handle.verification_uri_complete} externally`) console.warn(
let temp = '/tmp' `E2E_TAURI_ENABLED is set, won't open ${handle.verification_uri_complete} externally`
// Overwrite with Windows variable )
if (process.env.TEMP) { let temp = '/tmp'
temp = process.env.TEMP // Overwrite with Windows variable
} if (process.env.TEMP) {
let path = path.join(temp, "kittycad_user_code") temp = process.env.TEMP
console.log(`Writing to ${path}`)
await fs.writeFile(path, handle.user_code)
} else {
shell.openExternal(handle.verification_uri_complete)
} }
let path = path.join(temp, 'kittycad_user_code')
console.log(`Writing to ${path}`)
await fs.writeFile(path, handle.user_code)
} else {
shell.openExternal(handle.verification_uri_complete)
}
// Wait for the user to login. // Wait for the user to login.
const tokenSet = await handle.poll() const tokenSet = await handle.poll()
return tokenSet.access_token return tokenSet.access_token
}) })

View File

@ -80,7 +80,10 @@ const Home = () => {
event: EventFrom<typeof homeMachine> event: EventFrom<typeof homeMachine>
) => { ) => {
if (event.data && 'name' in event.data) { if (event.data && 'name' in event.data) {
let projectPath = context.defaultDirectory + window.electron.path.sep + event.data.name let projectPath =
context.defaultDirectory +
window.electron.path.sep +
event.data.name
onProjectOpen( onProjectOpen(
{ {
name: event.data.name, name: event.data.name,
@ -138,9 +141,12 @@ const Home = () => {
context: ContextFrom<typeof homeMachine>, context: ContextFrom<typeof homeMachine>,
event: EventFrom<typeof homeMachine, 'Delete project'> event: EventFrom<typeof homeMachine, 'Delete project'>
) => { ) => {
await window.electron.rm(window.electron.path.join(context.defaultDirectory, event.data.name), { await window.electron.rm(
recursive: true, window.electron.path.join(context.defaultDirectory, event.data.name),
}) {
recursive: true,
}
)
return `Successfully deleted "${event.data.name}"` return `Successfully deleted "${event.data.name}"`
}, },
}, },