Stream comes up!
This commit is contained in:
@ -51,7 +51,7 @@ export const FileMachineProvider = ({
|
|||||||
commandBarSend({ type: 'Close' })
|
commandBarSend({ type: 'Close' })
|
||||||
navigate(
|
navigate(
|
||||||
`${paths.FILE}/${encodeURIComponent(
|
`${paths.FILE}/${encodeURIComponent(
|
||||||
context.selectedDirectory + sep() + event.data.name
|
context.selectedDirectory + window.electron.path.sep + event.data.name
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
} else if (
|
} else if (
|
||||||
@ -87,7 +87,7 @@ export const FileMachineProvider = ({
|
|||||||
services: {
|
services: {
|
||||||
readFiles: async (context: ContextFrom<typeof fileMachine>) => {
|
readFiles: async (context: ContextFrom<typeof fileMachine>) => {
|
||||||
const newFiles = isDesktop()
|
const newFiles = isDesktop()
|
||||||
? (await getProjectInfo(context.project.path)).children
|
? (await getProjectInfo(context.project.file.path)).children
|
||||||
: []
|
: []
|
||||||
return {
|
return {
|
||||||
...context.project,
|
...context.project,
|
||||||
@ -104,7 +104,7 @@ export const FileMachineProvider = ({
|
|||||||
} else {
|
} else {
|
||||||
createdPath =
|
createdPath =
|
||||||
context.selectedDirectory.path +
|
context.selectedDirectory.path +
|
||||||
sep() +
|
window.electron.path.sep +
|
||||||
createdName +
|
createdName +
|
||||||
(createdName.endsWith(FILE_EXT) ? '' : FILE_EXT)
|
(createdName.endsWith(FILE_EXT) ? '' : FILE_EXT)
|
||||||
await create(createdPath)
|
await create(createdPath)
|
||||||
@ -169,7 +169,7 @@ export const FileMachineProvider = ({
|
|||||||
file?.path.includes(event.data.path)) &&
|
file?.path.includes(event.data.path)) &&
|
||||||
project?.path
|
project?.path
|
||||||
) {
|
) {
|
||||||
navigate(paths.FILE + '/' + encodeURIComponent(project.path))
|
navigate(paths.FILE + '/' + encodeURIComponent(project.file.path))
|
||||||
}
|
}
|
||||||
|
|
||||||
return `Successfully deleted ${isDir ? 'folder' : 'file'} "${
|
return `Successfully deleted ${isDir ? 'folder' : 'file'} "${
|
||||||
|
@ -171,7 +171,7 @@ const FileTreeItem = ({
|
|||||||
// Import non-kcl files
|
// Import non-kcl files
|
||||||
// We want to update both the state and editor here.
|
// We want to update both the state and editor here.
|
||||||
codeManager.updateCodeStateEditor(
|
codeManager.updateCodeStateEditor(
|
||||||
`import("${fileOrDir.path.replace(project.path, '.')}")\n` +
|
`import("${fileOrDir.path.replace(project.file.path, '.')}")\n` +
|
||||||
codeManager.code
|
codeManager.code
|
||||||
)
|
)
|
||||||
codeManager.writeToFile()
|
codeManager.writeToFile()
|
||||||
|
@ -52,7 +52,7 @@ function ProjectCard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// async function setupImageUrl() {
|
// 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)) {
|
// if (await exists(projectImagePath)) {
|
||||||
// const imageData = await readFile(projectImagePath)
|
// const imageData = await readFile(projectImagePath)
|
||||||
// const blob = new Blob([imageData], { type: 'image/jpg' })
|
// const blob = new Blob([imageData], { type: 'image/jpg' })
|
||||||
@ -103,7 +103,7 @@ function ProjectCard({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<h3 className="font-sans relative z-0 p-2">
|
<h3 className="font-sans relative z-0 p-2">
|
||||||
{project.name?.replace(FILE_EXT, '')}
|
{project.file.name?.replace(FILE_EXT, '')}
|
||||||
</h3>
|
</h3>
|
||||||
)}
|
)}
|
||||||
<span className="px-2 text-chalkboard-60 text-xs">
|
<span className="px-2 text-chalkboard-60 text-xs">
|
||||||
@ -169,11 +169,11 @@ function ProjectCard({
|
|||||||
onDismiss={() => setIsConfirmingDelete(false)}
|
onDismiss={() => setIsConfirmingDelete(false)}
|
||||||
>
|
>
|
||||||
<p className="my-4">
|
<p className="my-4">
|
||||||
This will permanently delete "{project.name || 'this file'}
|
This will permanently delete "{project.file.name || 'this file'}
|
||||||
".
|
".
|
||||||
</p>
|
</p>
|
||||||
<p className="my-4">
|
<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.
|
"? This action cannot be undone.
|
||||||
</p>
|
</p>
|
||||||
</DeleteConfirmationDialog>
|
</DeleteConfirmationDialog>
|
||||||
|
@ -24,7 +24,7 @@ export const ProjectCardRenameForm = forwardRef(
|
|||||||
required
|
required
|
||||||
autoCorrect="off"
|
autoCorrect="off"
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
defaultValue={project.name}
|
defaultValue={project.file.name}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
|
@ -5,7 +5,6 @@ import { paths } from 'lib/paths'
|
|||||||
import { isDesktop } from '../lib/isDesktop'
|
import { isDesktop } from '../lib/isDesktop'
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { Fragment, useMemo } from 'react'
|
import { Fragment, useMemo } from 'react'
|
||||||
import { sep } from '@tauri-apps/api/path'
|
|
||||||
import { Logo } from './Logo'
|
import { Logo } from './Logo'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
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"
|
className="hidden select-none cursor-default text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
|
||||||
data-testid="project-name"
|
data-testid="project-name"
|
||||||
>
|
>
|
||||||
{project?.name ? project.name : APP_NAME}
|
{project?.name ? project.file.name : APP_NAME}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -208,12 +207,12 @@ 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(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 && (
|
||||||
<span className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block">
|
<span className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block">
|
||||||
{project.name}
|
{project.file.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,7 +46,7 @@ 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(sep()))
|
.slice(0, decodeURI(location.pathname).lastIndexOf(window.electron.path.sep))
|
||||||
)
|
)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { Platform, platform } from '@tauri-apps/plugin-os'
|
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export type Platform = 'macos' | 'windows' | 'linux'
|
||||||
|
|
||||||
export default function usePlatform() {
|
export default function usePlatform() {
|
||||||
const [platformName, setPlatformName] = useState<Platform | ''>('')
|
const [platformName, setPlatformName] = useState<Platform | ''>('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getPlatform() {
|
async function getPlatform() {
|
||||||
setPlatformName(await platform())
|
setPlatformName(window.electron.platform)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDesktop()) {
|
if (isDesktop()) {
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
// This prevents re-renders of the codemirror editor, when typing.
|
// This prevents re-renders of the codemirror editor, when typing.
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { writeTextFile } from '@tauri-apps/plugin-fs'
|
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { editorManager } from 'lib/singletons'
|
import { editorManager } from 'lib/singletons'
|
||||||
import { Annotation, Transaction } from '@codemirror/state'
|
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
|
// 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 &&
|
||||||
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)
|
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
|
||||||
console.error('error saving file', err)
|
console.error('error saving file', err)
|
||||||
toast.error('Error saving file, please check file permissions')
|
toast.error('Error saving file, please check file permissions')
|
||||||
|
@ -15,7 +15,7 @@ class FileSystemManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async join(dir: string, path: string): Promise<string> {
|
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> {
|
async readFile(path: string): Promise<Uint8Array | void> {
|
||||||
@ -33,7 +33,7 @@ class FileSystemManager {
|
|||||||
return Promise.reject(new Error(`Error reading file: ${error}`))
|
return Promise.reject(new Error(`Error reading file: ${error}`))
|
||||||
})
|
})
|
||||||
.then((file) => {
|
.then((file) => {
|
||||||
return window.electron.ipcRenderer.invoke('readFile', [filepath])
|
return window.electron.readFile(filepath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,8 +51,14 @@ class FileSystemManager {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
return Promise.reject(new Error(`Error checking file exists: ${error}`))
|
return Promise.reject(new Error(`Error checking file exists: ${error}`))
|
||||||
})
|
})
|
||||||
.then((file) => {
|
.then(async (file) => {
|
||||||
return window.electron.ipcRenderer.invoke('exists', [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}`))
|
return Promise.reject(new Error(`Error joining dir: ${error}`))
|
||||||
})
|
})
|
||||||
.then((filepath) => {
|
.then((filepath) => {
|
||||||
return window.electron.ipcRenderer
|
return window.electron.readdir(filepath)
|
||||||
.invoke('readdir', [filepath])
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
return Promise.reject(new Error(`Error reading dir: ${error}`))
|
return Promise.reject(new Error(`Error reading dir: ${error}`))
|
||||||
})
|
})
|
||||||
|
101
src/lang/wasm.ts
101
src/lang/wasm.ts
@ -19,6 +19,11 @@ import init, {
|
|||||||
parse_project_route,
|
parse_project_route,
|
||||||
serialize_project_settings,
|
serialize_project_settings,
|
||||||
} from '../wasm-lib/pkg/wasm_lib'
|
} from '../wasm-lib/pkg/wasm_lib'
|
||||||
|
import {
|
||||||
|
configurationToSettingsPayload,
|
||||||
|
projectConfigurationToSettingsPayload,
|
||||||
|
SaveSettingsPayload,
|
||||||
|
} from 'lib/settings/settingsUtils'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
import { EngineCommandManager } from './std/engineConnection'
|
import { EngineCommandManager } from './std/engineConnection'
|
||||||
@ -559,22 +564,26 @@ export function tomlStringify(toml: any): string | Error {
|
|||||||
return toml_stringify(JSON.stringify(toml))
|
return toml_stringify(JSON.stringify(toml))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function defaultAppSettings(): Configuration {
|
export function defaultAppSettings(): Partial<SaveSettingsPayload> {
|
||||||
return default_app_settings()
|
// 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 {
|
export function parseAppSettings(toml: string): Partial<SaveSettingsPayload> {
|
||||||
return parse_app_settings(toml)
|
return configurationToSettingsPayload(parse_app_settings(toml))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function defaultProjectSettings(): ProjectConfiguration | Error {
|
export function defaultProjectSettings(): Partial<SaveSettingsPayload> {
|
||||||
return default_project_settings()
|
return projectConfigurationToSettingsPayload(default_project_settings())
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseProjectSettings(
|
export function parseProjectSettings(
|
||||||
toml: string
|
toml: string
|
||||||
): ProjectConfiguration | Error {
|
): Partial<SaveSettingsPayload> {
|
||||||
return parse_project_settings(toml)
|
return projectConfigurationToSettingsPayload(parse_project_settings(toml))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseProjectRoute(
|
export function parseProjectRoute(
|
||||||
@ -583,79 +592,3 @@ export function parseProjectRoute(
|
|||||||
): ProjectRoute | Error {
|
): ProjectRoute | Error {
|
||||||
return parse_project_route(JSON.stringify(configuration), route_str)
|
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")
|
|
||||||
}
|
|
||||||
|
@ -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 { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||||
import { Project } from 'wasm-lib/kcl/bindings/Project'
|
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 { components } from './machine-api'
|
||||||
import { isDesktop } from './isDesktop'
|
import { isDesktop } from './isDesktop'
|
||||||
|
|
||||||
export {
|
import {
|
||||||
readAppSettingsFile,
|
defaultAppSettings,
|
||||||
writeAppSettingsFile,
|
tomlStringify,
|
||||||
getState,
|
parseAppSettings,
|
||||||
setState,
|
parseProjectSettings,
|
||||||
getUser,
|
|
||||||
login,
|
|
||||||
} from 'lang/wasm'
|
} 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.
|
// List machines on the local network.
|
||||||
export async function listMachines(): Promise<{
|
export async function listMachines(): Promise<{
|
||||||
@ -34,15 +43,7 @@ export async function renameProjectDirectory(
|
|||||||
projectPath: string,
|
projectPath: string,
|
||||||
newName: string
|
newName: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return window.electron.ipcRenderer.invoke<string>(
|
debugger
|
||||||
'rename_project_directory',
|
|
||||||
{ projectPath, newName }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the initial default dir for holding all projects.
|
|
||||||
export async function getInitialDefaultDir(): Promise<string> {
|
|
||||||
return window.electron.getInitialDefaultDir()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function showInFolder(path: string | undefined): Promise<void> {
|
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')
|
console.error('path is undefined cannot call desktop showInFolder')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return window.electron.ipcRenderer.invoke('show_in_folder', { path })
|
debugger
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initializeProjectDirectory(
|
export async function ensureProjectDirectoryExists(
|
||||||
config: Configuration
|
config: Partial<SaveSettingsPayload>
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
const projectDir = config.settings.project.directory
|
const projectDir = config.app.projectDirectory
|
||||||
try {
|
try {
|
||||||
await window.electron.exists(projectDir)
|
await window.electron.stat(projectDir)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e === 'ENOENT') {
|
if (e === 'ENOENT') {
|
||||||
window.electron.mkdir(projectDir, { recursive: true }, (e) => {
|
window.electron.mkdir(projectDir, { recursive: true }, (e) => {
|
||||||
@ -73,73 +74,360 @@ export async function initializeProjectDirectory(
|
|||||||
export async function createNewProjectDirectory(
|
export async function createNewProjectDirectory(
|
||||||
projectName: string,
|
projectName: string,
|
||||||
initialCode?: string,
|
initialCode?: string,
|
||||||
configuration?: Configuration
|
configuration?: Partial<SaveSettingsPayload>
|
||||||
): Promise<Project> {
|
): Promise<Project> {
|
||||||
if (!configuration) {
|
if (!configuration) {
|
||||||
configuration = await readAppSettingsFile()
|
configuration = await readAppSettingsFile()
|
||||||
}
|
}
|
||||||
return window.electron.ipcRenderer.invoke('create_new_project_directory', {
|
|
||||||
configuration,
|
const mainDir = await ensureProjectDirectoryExists(configuration)
|
||||||
projectName,
|
|
||||||
initialCode,
|
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(
|
export async function listProjects(
|
||||||
configuration?: Configuration
|
configuration?: Partial<SaveSettingsPayload>
|
||||||
): Promise<Project[]> {
|
): Promise<Project[]> {
|
||||||
const projectDir = await initializeProjectDirectory(configuration)
|
const projectDir = await ensureProjectDirectoryExists(configuration)
|
||||||
const projects = []
|
const projects = []
|
||||||
const entries = await window.electron.readdir(projectDir)
|
const entries = await window.electron.readdir(projectDir)
|
||||||
for (let entry of entries) {
|
for (let entry of entries) {
|
||||||
// Ignore directories
|
const projectPath = window.electron.path.join(projectDir, entry)
|
||||||
console.log(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(
|
export async function getProjectInfo(
|
||||||
projectPath: string,
|
projectPath: string,
|
||||||
configuration?: Configuration
|
|
||||||
): Promise<Project> {
|
): Promise<Project> {
|
||||||
if (!configuration) {
|
// Check the directory.
|
||||||
configuration = await readAppSettingsFile()
|
try { await window.electron.stat(projectPath) }
|
||||||
|
catch (e) {
|
||||||
|
if (e === 'ENOENT') {
|
||||||
|
return Promise.reject(new Error(`Project directory does not exist: ${project_path}`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return window.electron.ipcRenderer.invoke('get_project_info', {
|
|
||||||
configuration,
|
|
||||||
projectPath,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function parseProjectRoute(
|
// Make sure it is a directory.
|
||||||
configuration: Configuration,
|
const projectPathIsDir = await window.electron.statIsDirectory(projectPath)
|
||||||
route: string
|
if (!projectPathIsDir) {
|
||||||
): Promise<ProjectRoute> {
|
return Promise.reject(new Error(`Project path is not a directory: ${project_path}`));
|
||||||
return window.electron.ipcRenderer.invoke('parse_project_route', {
|
}
|
||||||
configuration,
|
|
||||||
route,
|
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[]> {
|
export async function readDirRecursive(path: string): Promise<FileEntry[]> {
|
||||||
return window.electron.ipcRenderer.invoke('read_dir_recursive', { path })
|
debugger
|
||||||
}
|
|
||||||
|
|
||||||
// Read project settings file.
|
|
||||||
export async function readProjectSettingsFile(
|
|
||||||
projectPath: string
|
|
||||||
): Promise<ProjectConfiguration> {
|
|
||||||
return window.electron.ipcRenderer.invoke('read_project_settings_file', {
|
|
||||||
projectPath,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write project settings file.
|
// Write project settings file.
|
||||||
export async function writeProjectSettingsFile(
|
export async function writeProjectSettingsFile({
|
||||||
projectPath: string,
|
|
||||||
settings: ProjectConfiguration
|
|
||||||
): Promise<void> {
|
|
||||||
return window.electron.ipcRenderer.invoke('write_project_settings_file', {
|
|
||||||
projectPath,
|
projectPath,
|
||||||
configuration: settings,
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -4,22 +4,46 @@ import fs from 'node:fs/promises'
|
|||||||
import packageJson from '../../package.json'
|
import packageJson from '../../package.json'
|
||||||
|
|
||||||
const readFile = (path: string) => fs.readFile(path, 'utf-8')
|
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 readdir = (path: string) => fs.readdir(path, 'utf-8')
|
||||||
const exists = (path: string) =>
|
const stat = (path: string) => fs.stat(path).catch((e) => Promise.reject(e.code))
|
||||||
new Promise((resolve, reject) =>
|
// Electron has behavior where it doesn't clone the prototype chain over.
|
||||||
fs.stat(path, (err, data) => {
|
// So we need to call stat.isDirectory on this side.
|
||||||
if (err) return reject(err.code)
|
const statIsDirectory = (path: string) => stat(path).then((res) => res.isDirectory())
|
||||||
return resolve(data)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
const getPath = async (name: string) => ipcRenderer.invoke('app.getPath', name)
|
const getPath = async (name: string) => ipcRenderer.invoke('app.getPath', name)
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electron', {
|
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,
|
readFile,
|
||||||
|
writeFile,
|
||||||
readdir,
|
readdir,
|
||||||
path,
|
path,
|
||||||
exists,
|
stat,
|
||||||
|
statIsDirectory,
|
||||||
mkdir: fs.mkdir,
|
mkdir: fs.mkdir,
|
||||||
getPath,
|
getPath,
|
||||||
packageJson,
|
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,
|
||||||
|
},
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -11,12 +11,11 @@ import {
|
|||||||
import { loadAndValidateSettings } from './settings/settingsUtils'
|
import { loadAndValidateSettings } from './settings/settingsUtils'
|
||||||
import makeUrlPathRelative from './makeUrlPathRelative'
|
import makeUrlPathRelative from './makeUrlPathRelative'
|
||||||
import { sep } from '@tauri-apps/api/path'
|
import { sep } from '@tauri-apps/api/path'
|
||||||
import { readTextFile } from '@tauri-apps/plugin-fs'
|
|
||||||
import { codeManager, kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||||
import {
|
import {
|
||||||
getProjectInfo,
|
getProjectInfo,
|
||||||
initializeProjectDirectory,
|
ensureProjectDirectoryExists,
|
||||||
listProjects,
|
listProjects,
|
||||||
} from './desktop'
|
} from './desktop'
|
||||||
import { createSettings } from './settings/initialSettings'
|
import { createSettings } from './settings/initialSettings'
|
||||||
@ -90,14 +89,14 @@ export const fileLoader: LoaderFunction = async ({
|
|||||||
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() ? sep() : '/'}${PROJECT_ENTRYPOINT}`
|
`${params.id}${isDesktop() ? window.electron.path.sep : '/'}${PROJECT_ENTRYPOINT}`
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: PROJECT_ENTRYPOINT is hardcoded
|
// TODO: PROJECT_ENTRYPOINT is hardcoded
|
||||||
// until we support setting a project's entrypoint file
|
// 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.
|
// Update both the state and the editor's code.
|
||||||
// We explicitly do not write to the file here since we are loading from
|
// 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 { configuration } = await loadAndValidateSettings()
|
||||||
|
|
||||||
const projectDir = await initializeProjectDirectory(configuration)
|
const projectDir = await ensureProjectDirectoryExists(configuration)
|
||||||
|
|
||||||
if (projectDir) {
|
if (projectDir) {
|
||||||
const projects = await listProjects(configuration)
|
const projects = await listProjects(configuration)
|
||||||
|
@ -27,7 +27,7 @@ import { BROWSER_PROJECT_NAME } from 'lib/constants'
|
|||||||
* We do this because the JS settings type has all the fancy shit
|
* We do this because the JS settings type has all the fancy shit
|
||||||
* for hiding and showing settings.
|
* for hiding and showing settings.
|
||||||
**/
|
**/
|
||||||
function configurationToSettingsPayload(
|
export function configurationToSettingsPayload(
|
||||||
configuration: Configuration
|
configuration: Configuration
|
||||||
): Partial<SaveSettingsPayload> {
|
): Partial<SaveSettingsPayload> {
|
||||||
return {
|
return {
|
||||||
@ -65,7 +65,7 @@ function configurationToSettingsPayload(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectConfigurationToSettingsPayload(
|
export function projectConfigurationToSettingsPayload(
|
||||||
configuration: ProjectConfiguration
|
configuration: ProjectConfiguration
|
||||||
): Partial<SaveSettingsPayload> {
|
): Partial<SaveSettingsPayload> {
|
||||||
return {
|
return {
|
||||||
@ -165,14 +165,12 @@ export async function loadAndValidateSettings(
|
|||||||
await initPromise
|
await initPromise
|
||||||
|
|
||||||
// Load the app settings from the file system or localStorage.
|
// Load the app settings from the file system or localStorage.
|
||||||
const appSettings = onDesktop
|
const appSettingsPayload = onDesktop
|
||||||
? await readAppSettingsFile()
|
? await readAppSettingsFile()
|
||||||
: readLocalStorageAppSettingsFile()
|
: 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)
|
setSettingsAtLevel(settings, 'user', appSettingsPayload)
|
||||||
|
|
||||||
// Load the project settings if they exist
|
// Load the project settings if they exist
|
||||||
@ -190,7 +188,7 @@ export async function loadAndValidateSettings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return the settings object
|
// Return the settings object
|
||||||
return { settings, configuration: appSettings }
|
return { settings, configuration: appSettingsPayload }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveSettings(
|
export async function saveSettings(
|
||||||
@ -207,7 +205,7 @@ export async function saveSettings(
|
|||||||
if (err(tomlString)) return
|
if (err(tomlString)) return
|
||||||
|
|
||||||
// Parse this as a Configuration.
|
// Parse this as a Configuration.
|
||||||
const appSettings = parseAppSettings(tomlString)
|
const appSettings = { settings: parseAppSettings(tomlString) }
|
||||||
if (err(appSettings)) return
|
if (err(appSettings)) return
|
||||||
|
|
||||||
const tomlString2 = tomlStringify(appSettings)
|
const tomlString2 = tomlStringify(appSettings)
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
let toast = undefined
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
try {
|
|
||||||
global
|
|
||||||
} catch (e) {
|
|
||||||
import('react-hot-toast').then((_toast) => {
|
|
||||||
toast = _toast
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExcludeErr<T> = Exclude<T, Error>
|
type ExcludeErr<T> = Exclude<T, Error>
|
||||||
|
|
||||||
@ -16,6 +8,7 @@ export function err<T>(value: ExcludeErr<T> | Error): value is Error {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error(value)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,7 +146,10 @@ 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(
|
||||||
|
'f8864550-84a6-4a06-8d3f-68d29bbe5608' /* context.token */,
|
||||||
|
VITE_KC_API_BASE_URL
|
||||||
|
)
|
||||||
|
|
||||||
const user = await userPromise
|
const user = await userPromise
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ if (require('electron-squirrel-startup')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createWindow = () => {
|
const createWindow = () => {
|
||||||
const mainWindow = new BrowserWindow({
|
let mainWindow = new BrowserWindow({
|
||||||
width: 800,
|
width: 800,
|
||||||
height: 600,
|
height: 600,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
@ -82,7 +82,7 @@ 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 + 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,
|
||||||
@ -192,15 +192,15 @@ const Home = () => {
|
|||||||
new FormData(e.target as HTMLFormElement)
|
new FormData(e.target as HTMLFormElement)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (newProjectName !== project.name) {
|
if (newProjectName !== project.file.name) {
|
||||||
send('Rename project', {
|
send('Rename project', {
|
||||||
data: { oldName: project.name, newName: newProjectName },
|
data: { oldName: project.file.name, newName: newProjectName },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDeleteProject(project: Project) {
|
async function handleDeleteProject(project: Project) {
|
||||||
send('Delete project', { data: { name: project.name || '' } })
|
send('Delete project', { data: { name: project.file.name || '' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -296,7 +296,7 @@ const Home = () => {
|
|||||||
<ul className="grid w-full grid-cols-4 gap-4">
|
<ul className="grid w-full grid-cols-4 gap-4">
|
||||||
{searchResults.sort(getSortFunction(sort)).map((project) => (
|
{searchResults.sort(getSortFunction(sort)).map((project) => (
|
||||||
<ProjectCard
|
<ProjectCard
|
||||||
key={project.name}
|
key={project.file.name}
|
||||||
project={project}
|
project={project}
|
||||||
handleRenameProject={handleRenameProject}
|
handleRenameProject={handleRenameProject}
|
||||||
handleDeleteProject={handleDeleteProject}
|
handleDeleteProject={handleDeleteProject}
|
||||||
|
@ -495,7 +495,6 @@ pub fn default_app_settings() -> Result<JsValue, String> {
|
|||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
let settings = kcl_lib::settings::types::Configuration::default();
|
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())
|
serde_wasm_bindgen::to_value(&settings).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user