At implementing login and getUser

This commit is contained in:
49lf
2024-07-24 21:37:29 -04:00
parent 0b454939b3
commit 118ac33ebe
16 changed files with 264 additions and 176 deletions

View File

@ -60,6 +60,9 @@ root.render(
reportWebVitals() reportWebVitals()
const runTauriUpdater = async () => { const runTauriUpdater = async () => {
console.log('STUB: @pierre')
return
try { try {
const update = await check() const update = await check()
if (update && update.available) { if (update && update.available) {

View File

@ -15,14 +15,16 @@ 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 window.electron.ipcRenderer.invoke('join', [dir, path])
} }
async readFile(path: string): Promise<Uint8Array | void> { async readFile(path: string): Promise<Uint8Array | void> {
// Using local file system only works from desktop. // Using local file system only works from desktop.
if (!isDesktop()) { if (!isDesktop()) {
return Promise.reject( return Promise.reject(
new Error('This function can only be called from the desktop application') new Error(
'This function can only be called from the desktop application'
)
) )
} }
@ -39,7 +41,9 @@ class FileSystemManager {
// Using local file system only works from desktop. // Using local file system only works from desktop.
if (!isDesktop()) { if (!isDesktop()) {
return Promise.reject( return Promise.reject(
new Error('This function can only be called from the desktop application') new Error(
'This function can only be called from the desktop application'
)
) )
} }
@ -56,7 +60,9 @@ class FileSystemManager {
// Using local file system only works from desktop. // Using local file system only works from desktop.
if (!isDesktop()) { if (!isDesktop()) {
return Promise.reject( return Promise.reject(
new Error('This function can only be called from the desktop application') new Error(
'This function can only be called from the desktop application'
)
) )
} }
@ -65,7 +71,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.ipcRenderer.invoke('readdir', [filepath]) return window.electron.ipcRenderer
.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}`))
}) })

View File

@ -17,6 +17,7 @@ import init, {
parse_project_settings, parse_project_settings,
default_project_settings, default_project_settings,
parse_project_route, parse_project_route,
serialize_project_settings,
} from '../wasm-lib/pkg/wasm_lib' } from '../wasm-lib/pkg/wasm_lib'
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'
@ -558,7 +559,7 @@ export function tomlStringify(toml: any): string | Error {
return toml_stringify(JSON.stringify(toml)) return toml_stringify(JSON.stringify(toml))
} }
export function defaultAppSettings(): Configuration | Error { export function defaultAppSettings(): Configuration {
return default_app_settings() return default_app_settings()
} }
@ -582,3 +583,79 @@ 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")
}

View File

@ -8,21 +8,14 @@ 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'
// Get the app state from desktop. export {
export async function getState(): Promise<ProjectState | undefined> { readAppSettingsFile,
if (!isDesktop()) { writeAppSettingsFile,
return undefined getState,
} setState,
return await window.electron.ipcRenderer.invoke('get_state') getUser,
} login,
} from 'lang/wasm'
// Set the app state in desktop.
export async function setState(state: ProjectState | undefined): Promise<void> {
if (!isDesktop()) {
return
}
return window.electron.ipcRenderer.invoke('set_state', { state })
}
// List machines on the local network. // List machines on the local network.
export async function listMachines(): Promise<{ export async function listMachines(): Promise<{
@ -41,21 +34,18 @@ export async function renameProjectDirectory(
projectPath: string, projectPath: string,
newName: string newName: string
): Promise<string> { ): Promise<string> {
return window.electron.ipcRenderer.invoke<string>('rename_project_directory', { projectPath, newName }) return window.electron.ipcRenderer.invoke<string>(
'rename_project_directory',
{ projectPath, newName }
)
} }
// Get the initial default dir for holding all projects. // Get the initial default dir for holding all projects.
export async function getInitialDefaultDir(): Promise<string> { export async function getInitialDefaultDir(): Promise<string> {
if (!isDesktop()) { return window.electron.getInitialDefaultDir()
return ''
}
return invoke<string>('get_initial_default_dir')
} }
export async function showInFolder(path: string | undefined): Promise<void> { export async function showInFolder(path: string | undefined): Promise<void> {
if (!isDesktop()) {
return
}
if (!path) { if (!path) {
console.error('path is undefined cannot call desktop showInFolder') console.error('path is undefined cannot call desktop showInFolder')
return return
@ -64,14 +54,20 @@ export async function showInFolder(path: string | undefined): Promise<void> {
} }
export async function initializeProjectDirectory( export async function initializeProjectDirectory(
settings: Configuration config: Configuration
): Promise<string | undefined> { ): Promise<string | undefined> {
if (!isDesktop()) { const projectDir = config.settings.project.directory
return undefined try {
await window.electron.exists(projectDir)
} catch (e) {
if (e === 'ENOENT') {
window.electron.mkdir(projectDir, { recursive: true }, (e) => {
console.log(e)
})
}
} }
return window.electron.ipcRenderer.invoke('initialize_project_directory', {
configuration: settings, return projectDir
})
} }
export async function createNewProjectDirectory( export async function createNewProjectDirectory(
@ -92,10 +88,13 @@ export async function createNewProjectDirectory(
export async function listProjects( export async function listProjects(
configuration?: Configuration configuration?: Configuration
): Promise<Project[]> { ): Promise<Project[]> {
if (!configuration) { const projectDir = await initializeProjectDirectory(configuration)
configuration = await readAppSettingsFile() const projects = []
const entries = await window.electron.readdir(projectDir)
for (let entry of entries) {
// Ignore directories
console.log(entry)
} }
return window.electron.ipcRenderer.invoke('list_projects', { configuration })
} }
export async function getProjectInfo( export async function getProjectInfo(
@ -111,10 +110,6 @@ export async function getProjectInfo(
}) })
} }
export async function login(host: string): Promise<string> {
return window.electron.ipcRenderer.invoke('login', { host })
}
export async function parseProjectRoute( export async function parseProjectRoute(
configuration: Configuration, configuration: Configuration,
route: string route: string
@ -125,40 +120,10 @@ export async function parseProjectRoute(
}) })
} }
export async function getUser(
token: string | undefined,
host: string
): Promise<Models['User_type'] | Record<'error_code', unknown> | void> {
if (!token) {
console.error('token is undefined cannot call desktop getUser')
return
}
return window.electron.ipcRenderer.invoke>(
'get_user',
{
token: token,
hostname: host,
}
).catch((err) => console.error('error from Tauri getUser', err))
}
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 }) return window.electron.ipcRenderer.invoke('read_dir_recursive', { path })
} }
// Read the contents of the app settings.
export async function readAppSettingsFile(): Promise<Configuration> {
return window.electron.ipcRenderer.invoke('read_app_settings_file')
}
// Write the contents of the app settings.
export async function writeAppSettingsFile(
settings: Configuration
): Promise<void> {
return window.electron.ipcRenderer.invoke('write_app_settings_file', { configuration: settings })
}
// Read project settings file. // Read project settings file.
export async function readProjectSettingsFile( export async function readProjectSettingsFile(
projectPath: string projectPath: string

View File

@ -1,19 +1,25 @@
import { contextBridge } from 'electron' import { ipcRenderer, contextBridge } from 'electron'
import path from 'path' import path from 'path'
import fs from 'node:fs' import fs from 'node:fs/promises'
import packageJson from '../../package.json'
// All these functions call into lib/electron since many require filesystem const readFile = (path: string) => fs.readFile(path, 'utf-8')
// access, and the second half is the original tauri code also stored app const readdir = (path: string) => fs.readdir(path, 'utf-8')
// state on the "desktop" side. const exists = (path: string) =>
new Promise((resolve, reject) =>
fs.stat(path, (err, data) => {
if (err) return reject(err.code)
return resolve(data)
})
)
const getPath = async (name: string) => ipcRenderer.invoke('app.getPath', name)
const DEFAULT_HOST = "https://api.zoo.dev" contextBridge.exposeInMainWorld('electron', {
const SETTINGS_FILE_NAME = "settings.toml" readFile,
const PROJECT_SETTINGS_FILE_NAME = "project.toml" readdir,
const PROJECT_FOLDER = "zoo-modeling-app-projects" path,
exists,
contextBridge.exposeInMainWorld("fs", { mkdir: fs.mkdir,
readFile(p: string) { return fs.readFile(p, 'utf-8') }, getPath,
readdir(p: string) { return fs.readdir(p, 'utf-8') }, packageJson,
join() { return path.join(...arguments) },
exists(p: string) { fs.exists(p) },
}) })

View File

@ -1,5 +1,5 @@
// https://github.com/electron/electron/issues/2288#issuecomment-337858978 // https://github.com/electron/electron/issues/2288#issuecomment-337858978
// Thank you // Thank you
export function isDesktop(): boolean { export function isDesktop(): boolean {
return navigator.userAgent.toLowerCase().indexOf("electron") > -1; return navigator.userAgent.toLowerCase().indexOf('electron') > -1
} }

View File

@ -192,7 +192,8 @@ export function createSettings() {
description: 'The directory to save and load projects from', description: 'The directory to save and load projects from',
hideOnLevel: 'project', hideOnLevel: 'project',
hideOnPlatform: 'web', hideOnPlatform: 'web',
validate: (v) => typeof v === 'string' && (v.length > 0 || !isDesktop()), validate: (v) =>
typeof v === 'string' && (v.length > 0 || !isDesktop()),
Component: ({ value, updateValue }) => { Component: ({ value, updateValue }) => {
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
return ( return (

View File

@ -161,10 +161,8 @@ export async function loadAndValidateSettings(
const settings = createSettings() const settings = createSettings()
const onDesktop = isDesktop() const onDesktop = isDesktop()
if (!onDesktop) { // Make sure we have wasm initialized.
// Make sure we have wasm initialized. 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 appSettings = onDesktop

View File

@ -1,4 +1,12 @@
import toast from 'react-hot-toast' let toast = undefined
try {
global
} catch (e) {
import('react-hot-toast').then((_toast) => {
toast = _toast
})
}
type ExcludeErr<T> = Exclude<T, Error> type ExcludeErr<T> = Exclude<T, Error>

View File

@ -3,7 +3,7 @@ import { Models } from '@kittycad/lib'
import withBaseURL from '../lib/withBaseURL' import withBaseURL from '../lib/withBaseURL'
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
import { VITE_KC_API_BASE_URL, VITE_KC_DEV_TOKEN } from 'env' import { VITE_KC_API_BASE_URL, VITE_KC_DEV_TOKEN } from 'env'
import { getUser as getUserTauri } from 'lib/desktop' import { getUser as getUserDesktop } from 'lib/desktop'
const SKIP_AUTH = const SKIP_AUTH =
import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV
@ -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))
: getUserTauri(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

@ -1,7 +1,8 @@
// Some of the following was taken from bits and pieces of the vite-typescript // Some of the following was taken from bits and pieces of the vite-typescript
// template that ElectronJS provides. // template that ElectronJS provides.
import { app, BrowserWindow } from 'electron' import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { app, BrowserWindow, ipcMain } from 'electron'
import path from 'path' import path from 'path'
// Handle creating/removing shortcuts on Windows when installing/uninstalling. // Handle creating/removing shortcuts on Windows when installing/uninstalling.
@ -17,8 +18,8 @@ const createWindow = () => {
nodeIntegration: false, // do not give the application implicit system access nodeIntegration: false, // do not give the application implicit system access
contextIsolation: true, // expose system functions in preload contextIsolation: true, // expose system functions in preload
sandbox: false, // expose nodejs in preload sandbox: false, // expose nodejs in preload
preload: path.join(__dirname, "./preload.js") preload: path.join(__dirname, './preload.js'),
} },
}) })
// and load the index.html of the app. // and load the index.html of the app.
@ -48,3 +49,6 @@ app.on('window-all-closed', () => {
// Some APIs can only be used after this event occurs. // Some APIs can only be used after this event occurs.
app.on('ready', createWindow) app.on('ready', createWindow)
ipcMain.handle('app.getPath', (event, data) => {
return app.getPath(data)
})

View File

@ -2589,6 +2589,17 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]] [[package]]
name = "serde_bytes" name = "serde_bytes"
version = "0.11.14" version = "0.11.14"
@ -3625,6 +3636,8 @@ dependencies = [
"kittycad", "kittycad",
"pretty_assertions", "pretty_assertions",
"reqwest", "reqwest",
"serde",
"serde-wasm-bindgen",
"serde_json", "serde_json",
"tokio", "tokio",
"toml", "toml",

View File

@ -16,6 +16,8 @@ gloo-utils = "0.2.0"
kcl-lib = { path = "kcl" } kcl-lib = { path = "kcl" }
kittycad.workspace = true kittycad.workspace = true
serde_json = "1.0.122" serde_json = "1.0.122"
serde_json = "1.0.122"
serde-wasm-bindgen = "0.6.5"
tokio = { version = "1.39.2", features = ["sync"] } tokio = { version = "1.39.2", features = ["sync"] }
toml = "0.8.19" toml = "0.8.19"
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] } uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }

View File

@ -19,7 +19,7 @@ const DEFAULT_PROJECT_NAME_TEMPLATE: &str = "project-$nnn";
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct Configuration { pub struct Configuration {
/// The settings for the modeling app. /// The settings for the modeling app.
#[serde(default, skip_serializing_if = "is_default")] #[serde(default)]
#[validate(nested)] #[validate(nested)]
pub settings: Settings, pub settings: Settings,
} }
@ -183,23 +183,23 @@ impl Configuration {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct Settings { pub struct Settings {
/// The settings for the modeling app. /// The settings for the modeling app.
#[serde(default, skip_serializing_if = "is_default")] #[serde(default)]
#[validate(nested)] #[validate(nested)]
pub app: AppSettings, pub app: AppSettings,
/// Settings that affect the behavior while modeling. /// Settings that affect the behavior while modeling.
#[serde(default, skip_serializing_if = "is_default")] #[serde(default)]
#[validate(nested)] #[validate(nested)]
pub modeling: ModelingSettings, pub modeling: ModelingSettings,
/// Settings that affect the behavior of the KCL text editor. /// Settings that affect the behavior of the KCL text editor.
#[serde(default, alias = "textEditor", skip_serializing_if = "is_default")] #[serde(default, alias = "textEditor")]
#[validate(nested)] #[validate(nested)]
pub text_editor: TextEditorSettings, pub text_editor: TextEditorSettings,
/// Settings that affect the behavior of project management. /// Settings that affect the behavior of project management.
#[serde(default, alias = "projects", skip_serializing_if = "is_default")] #[serde(default, alias = "projects")]
#[validate(nested)] #[validate(nested)]
pub project: ProjectSettings, pub project: ProjectSettings,
/// Settings that affect the behavior of the command bar. /// Settings that affect the behavior of the command bar.
#[serde(default, alias = "commandBar", skip_serializing_if = "is_default")] #[serde(default, alias = "commandBar")]
#[validate(nested)] #[validate(nested)]
pub command_bar: CommandBarSettings, pub command_bar: CommandBarSettings,
} }
@ -212,11 +212,11 @@ pub struct Settings {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct AppSettings { pub struct AppSettings {
/// The settings for the appearance of the app. /// The settings for the appearance of the app.
#[serde(default, skip_serializing_if = "is_default")] #[serde(default)]
#[validate(nested)] #[validate(nested)]
pub appearance: AppearanceSettings, pub appearance: AppearanceSettings,
/// The onboarding status of the app. /// The onboarding status of the app.
#[serde(default, alias = "onboardingStatus", skip_serializing_if = "is_default")] #[serde(default, alias = "onboardingStatus")]
pub onboarding_status: OnboardingStatus, pub onboarding_status: OnboardingStatus,
/// Backwards compatible project directory setting. /// Backwards compatible project directory setting.
#[serde(default, alias = "projectDirectory", skip_serializing_if = "Option::is_none")] #[serde(default, alias = "projectDirectory", skip_serializing_if = "Option::is_none")]
@ -232,10 +232,10 @@ pub struct AppSettings {
pub enable_ssao: Option<bool>, pub enable_ssao: Option<bool>,
/// Permanently dismiss the banner warning to download the desktop app. /// Permanently dismiss the banner warning to download the desktop app.
/// This setting only applies to the web app. And is temporary until we have Linux support. /// This setting only applies to the web app. And is temporary until we have Linux support.
#[serde(default, alias = "dismissWebBanner", skip_serializing_if = "is_default")] #[serde(default, alias = "dismissWebBanner")]
pub dismiss_web_banner: bool, pub dismiss_web_banner: bool,
/// When the user is idle, and this is true, the stream will be torn down. /// When the user is idle, and this is true, the stream will be torn down.
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")] #[serde(default, alias = "streamIdleMode")]
stream_idle_mode: bool, stream_idle_mode: bool,
} }
@ -275,10 +275,10 @@ impl From<FloatOrInt> for AppColor {
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct AppearanceSettings { pub struct AppearanceSettings {
/// The overall theme of the app. /// The overall theme of the app.
#[serde(default, skip_serializing_if = "is_default")] #[serde(default)]
pub theme: AppTheme, pub theme: AppTheme,
/// The hue of the primary theme color for the app. /// The hue of the primary theme color for the app.
#[serde(default, skip_serializing_if = "is_default")] #[serde(default)]
#[validate(nested)] #[validate(nested)]
pub color: AppColor, pub color: AppColor,
} }
@ -367,23 +367,23 @@ impl From<AppTheme> for kittycad::types::Color {
#[ts(export)] #[ts(export)]
pub struct ModelingSettings { pub struct ModelingSettings {
/// The default unit to use in modeling dimensions. /// The default unit to use in modeling dimensions.
#[serde(default, alias = "defaultUnit", skip_serializing_if = "is_default")] #[serde(default, alias = "defaultUnit")]
pub base_unit: UnitLength, pub base_unit: UnitLength,
/// The controls for how to navigate the 3D view. /// The controls for how to navigate the 3D view.
#[serde(default, alias = "mouseControls", skip_serializing_if = "is_default")] #[serde(default, alias = "mouseControls")]
pub mouse_controls: MouseControlType, pub mouse_controls: MouseControlType,
/// Highlight edges of 3D objects? /// Highlight edges of 3D objects?
#[serde(default, alias = "highlightEdges", skip_serializing_if = "is_default")] #[serde(default, alias = "highlightEdges")]
pub highlight_edges: DefaultTrue, pub highlight_edges: DefaultTrue,
/// Whether to show the debug panel, which lets you see various states /// Whether to show the debug panel, which lets you see various states
/// of the app to aid in development. /// of the app to aid in development.
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")] #[serde(default, alias = "showDebugPanel")]
pub show_debug_panel: bool, pub show_debug_panel: bool,
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled. /// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
#[serde(default, skip_serializing_if = "is_default")] #[serde(default)]
pub enable_ssao: DefaultTrue, pub enable_ssao: DefaultTrue,
/// Whether or not to show a scale grid in the 3D modeling view /// Whether or not to show a scale grid in the 3D modeling view
#[serde(default, alias = "showScaleGrid", skip_serializing_if = "is_default")] #[serde(default, alias = "showScaleGrid")]
pub show_scale_grid: bool, pub show_scale_grid: bool,
} }
@ -492,10 +492,10 @@ pub enum MouseControlType {
#[ts(export)] #[ts(export)]
pub struct TextEditorSettings { pub struct TextEditorSettings {
/// Whether to wrap text in the editor or overflow with scroll. /// Whether to wrap text in the editor or overflow with scroll.
#[serde(default, alias = "textWrapping", skip_serializing_if = "is_default")] #[serde(default, alias = "textWrapping")]
pub text_wrapping: DefaultTrue, pub text_wrapping: DefaultTrue,
/// Whether to make the cursor blink in the editor. /// Whether to make the cursor blink in the editor.
#[serde(default, alias = "blinkingCursor", skip_serializing_if = "is_default")] #[serde(default, alias = "blinkingCursor")]
pub blinking_cursor: DefaultTrue, pub blinking_cursor: DefaultTrue,
} }
@ -505,10 +505,10 @@ pub struct TextEditorSettings {
#[ts(export)] #[ts(export)]
pub struct ProjectSettings { pub struct ProjectSettings {
/// The directory to save and load projects from. /// The directory to save and load projects from.
#[serde(default, skip_serializing_if = "is_default")] #[serde(default)]
pub directory: std::path::PathBuf, pub directory: std::path::PathBuf,
/// The default project name to use when creating a new project. /// The default project name to use when creating a new project.
#[serde(default, alias = "defaultProjectName", skip_serializing_if = "is_default")] #[serde(default, alias = "defaultProjectName")]
pub default_project_name: ProjectNameTemplate, pub default_project_name: ProjectNameTemplate,
} }
@ -541,7 +541,7 @@ impl From<String> for ProjectNameTemplate {
#[ts(export)] #[ts(export)]
pub struct CommandBarSettings { pub struct CommandBarSettings {
/// Whether to include settings in the command bar. /// Whether to include settings in the command bar.
#[serde(default, alias = "includeSettings", skip_serializing_if = "is_default")] #[serde(default, alias = "includeSettings")]
pub include_settings: DefaultTrue, pub include_settings: DefaultTrue,
} }

View File

@ -6,8 +6,9 @@ use std::{
}; };
use futures::stream::TryStreamExt; use futures::stream::TryStreamExt;
use gloo_utils::format::JsValueSerdeExt; use kcl_lib::{coredump::CoreDump, engine::EngineManager, executor::ExecutorSettings, lint::checks};
use kcl_lib::{coredump::CoreDump, engine::EngineManager, executor::ExecutorSettings}; use serde::{Deserialize, Serialize};
use serde_wasm_bindgen;
use tower_lsp::{LspService, Server}; use tower_lsp::{LspService, Server};
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
@ -54,14 +55,13 @@ pub async fn execute_wasm(
}; };
let memory = ctx.run(&program, Some(memory)).await.map_err(String::from)?; let memory = ctx.run(&program, Some(memory)).await.map_err(String::from)?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
// gloo-serialize crate instead. serde_wasm_bindgen::to_value(&memory).map_err(|e| e.to_string())
JsValue::from_serde(&memory).map_err(|e| e.to_string())
} }
// wasm_bindgen wrapper for execute // wasm_bindgen wrapper for execute
#[wasm_bindgen] #[wasm_bindgen]
pub async fn kcl_lint(program_str: &str) -> Result<JsValue, String> { pub async fn kcl_lint(program_str: &str) -> Result<JsValue, JsValue> {
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?; let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
@ -70,7 +70,7 @@ pub async fn kcl_lint(program_str: &str) -> Result<JsValue, String> {
findings.push(discovered_finding); findings.push(discovered_finding);
} }
Ok(JsValue::from_serde(&findings).map_err(|e| e.to_string())?) Ok(serde_wasm_bindgen::to_value(&findings)?)
} }
// wasm_bindgen wrapper for creating default planes // wasm_bindgen wrapper for creating default planes
@ -89,9 +89,7 @@ pub async fn make_default_planes(
.await .await
.map_err(String::from)?; .map_err(String::from)?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the serde_wasm_bindgen::to_value(&default_planes).map_err(|e| e.to_string())
// gloo-serialize crate instead.
JsValue::from_serde(&default_planes).map_err(|e| e.to_string())
} }
// wasm_bindgen wrapper for modifying the grid // wasm_bindgen wrapper for modifying the grid
@ -143,9 +141,7 @@ pub async fn modify_ast_for_sketch_wasm(
.await .await
.map_err(String::from)?; .map_err(String::from)?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the serde_wasm_bindgen::to_value(&program).map_err(|e| e.to_string())
// gloo-serialize crate instead.
JsValue::from_serde(&program).map_err(|e| e.to_string())
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -161,7 +157,7 @@ pub fn deserialize_files(data: &[u8]) -> Result<JsValue, JsError> {
} }
if let Some(kittycad::types::OkWebSocketResponseData::Export { files }) = ws_resp.resp { if let Some(kittycad::types::OkWebSocketResponseData::Export { files }) = ws_resp.resp {
return Ok(JsValue::from_serde(&files)?); return Ok(serde_wasm_bindgen::to_value(&files)?);
} }
Err(JsError::new(&format!("Invalid response type, got: {:?}", ws_resp))) Err(JsError::new(&format!("Invalid response type, got: {:?}", ws_resp)))
@ -174,7 +170,7 @@ pub fn lexer_wasm(js: &str) -> Result<JsValue, JsError> {
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
let tokens = kcl_lib::token::lexer(js).map_err(JsError::from)?; let tokens = kcl_lib::token::lexer(js).map_err(JsError::from)?;
Ok(JsValue::from_serde(&tokens)?) Ok(serde_wasm_bindgen::to_value(&tokens)?)
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -184,9 +180,7 @@ pub fn parse_wasm(js: &str) -> Result<JsValue, String> {
let tokens = kcl_lib::token::lexer(js).map_err(String::from)?; let tokens = kcl_lib::token::lexer(js).map_err(String::from)?;
let parser = kcl_lib::parser::Parser::new(tokens); let parser = kcl_lib::parser::Parser::new(tokens);
let program = parser.ast().map_err(String::from)?; let program = parser.ast().map_err(String::from)?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the serde_wasm_bindgen::to_value(&program).map_err(|e| e.to_string())
// gloo-serialize crate instead.
JsValue::from_serde(&program).map_err(|e| e.to_string())
} }
// wasm_bindgen wrapper for recast // wasm_bindgen wrapper for recast
@ -200,7 +194,7 @@ pub fn recast_wasm(json_str: &str) -> Result<JsValue, JsError> {
// Use the default options until we integrate into the UI the ability to change them. // Use the default options until we integrate into the UI the ability to change them.
let result = program.recast(&Default::default(), 0); let result = program.recast(&Default::default(), 0);
Ok(JsValue::from_serde(&result)?) Ok(serde_wasm_bindgen::to_value(&result)?)
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -481,9 +475,7 @@ pub fn program_memory_init() -> Result<JsValue, String> {
let memory = kcl_lib::executor::ProgramMemory::default(); let memory = kcl_lib::executor::ProgramMemory::default();
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the serde_wasm_bindgen::to_value(&memory).map_err(|e| e.to_string())
// gloo-serialize crate instead.
JsValue::from_serde(&memory).map_err(|e| e.to_string())
} }
/// Get a coredump. /// Get a coredump.
@ -494,9 +486,7 @@ pub async fn coredump(core_dump_manager: kcl_lib::coredump::wasm::CoreDumpManage
let core_dumper = kcl_lib::coredump::wasm::CoreDumper::new(core_dump_manager); let core_dumper = kcl_lib::coredump::wasm::CoreDumper::new(core_dump_manager);
let dump = core_dumper.dump().await.map_err(|e| e.to_string())?; let dump = core_dumper.dump().await.map_err(|e| e.to_string())?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the serde_wasm_bindgen::to_value(&dump).map_err(|e| e.to_string())
// gloo-serialize crate instead.
JsValue::from_serde(&dump).map_err(|e| e.to_string())
} }
/// Get the default app settings. /// Get the default app settings.
@ -505,10 +495,9 @@ 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))));
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the serde_wasm_bindgen::to_value(&settings).map_err(|e| e.to_string())
// gloo-serialize crate instead.
JsValue::from_serde(&settings).map_err(|e| e.to_string())
} }
/// Parse the app settings. /// Parse the app settings.
@ -519,9 +508,7 @@ pub fn parse_app_settings(toml_str: &str) -> Result<JsValue, String> {
let settings = kcl_lib::settings::types::Configuration::backwards_compatible_toml_parse(&toml_str) let settings = kcl_lib::settings::types::Configuration::backwards_compatible_toml_parse(&toml_str)
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the serde_wasm_bindgen::to_value(&settings).map_err(|e| e.to_string())
// gloo-serialize crate instead.
JsValue::from_serde(&settings).map_err(|e| e.to_string())
} }
/// Get the default project settings. /// Get the default project settings.
@ -531,12 +518,10 @@ pub fn default_project_settings() -> Result<JsValue, String> {
let settings = kcl_lib::settings::types::project::ProjectConfiguration::default(); let settings = kcl_lib::settings::types::project::ProjectConfiguration::default();
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the serde_wasm_bindgen::to_value(&settings).map_err(|e| e.to_string())
// gloo-serialize crate instead.
JsValue::from_serde(&settings).map_err(|e| e.to_string())
} }
/// Parse the project settings. /// Parse (deserialize) the project settings.
#[wasm_bindgen] #[wasm_bindgen]
pub fn parse_project_settings(toml_str: &str) -> Result<JsValue, String> { pub fn parse_project_settings(toml_str: &str) -> Result<JsValue, String> {
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
@ -544,9 +529,17 @@ pub fn parse_project_settings(toml_str: &str) -> Result<JsValue, String> {
let settings = kcl_lib::settings::types::project::ProjectConfiguration::backwards_compatible_toml_parse(&toml_str) let settings = kcl_lib::settings::types::project::ProjectConfiguration::backwards_compatible_toml_parse(&toml_str)
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the serde_wasm_bindgen::to_value(&settings).map_err(|e| e.to_string())
// gloo-serialize crate instead. }
JsValue::from_serde(&settings).map_err(|e| e.to_string())
/// Serialize the project settings.
#[wasm_bindgen]
pub fn serialize_project_settings(val: JsValue) -> Result<JsValue, String> {
console_error_panic_hook::set_once();
let config: kcl_lib::settings::types::Configuration = serde_wasm_bindgen::from_value(val).map_err(|e| e.to_string())?;
let toml_str = toml::to_string_pretty(&config).map_err(|e| e.to_string())?;
serde_wasm_bindgen::to_value(&toml_str).map_err(|e| e.to_string())
} }
/// Parse the project route. /// Parse the project route.
@ -560,7 +553,5 @@ pub fn parse_project_route(configuration: &str, route: &str) -> Result<JsValue,
let route = let route =
kcl_lib::settings::types::file::ProjectRoute::from_route(&configuration, route).map_err(|e| e.to_string())?; kcl_lib::settings::types::file::ProjectRoute::from_route(&configuration, route).map_err(|e| e.to_string())?;
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the serde_wasm_bindgen::to_value(&route).map_err(|e| e.to_string())
// gloo-serialize crate instead.
JsValue::from_serde(&route).map_err(|e| e.to_string())
} }

View File

@ -1,29 +1,42 @@
import type { ConfigEnv, UserConfig } from 'vite' import type { ConfigEnv, UserConfig } from 'vite'
import { defineConfig, mergeConfig } from 'vite' import { defineConfig, mergeConfig } from 'vite'
import { getBuildConfig, external, pluginHotRestart } from './vite.base.config' import { configDefaults } from 'vitest/config'
import viteTsconfigPaths from 'vite-tsconfig-paths' import viteTsconfigPaths from 'vite-tsconfig-paths'
import {
getBuildConfig,
getBuildDefine,
external,
pluginHotRestart,
} from './vite.base.config'
// https://vitejs.dev/config // https://vitejs.dev/config
export default defineConfig((env) => { export default defineConfig((env) => {
const forgeEnv = env as ConfigEnv<'build'> const forgeEnv = env as ConfigEnv<'build'>
const { forgeConfigSelf } = forgeEnv const define = getBuildDefine(forgeEnv)
const { root, mode, forgeConfigSelf } = forgeEnv
const config: UserConfig = { const config: UserConfig = {
root,
mode,
base: './',
build: { build: {
lib: {
entry: forgeConfigSelf.entry!,
fileName: () => '[name].js',
formats: ['cjs'],
},
rollupOptions: { rollupOptions: {
external, external,
// Preload scripts may contain Web assets, so use the `build.rollupOptions.input` instead `build.lib.entry`.
input: forgeConfigSelf.entry!,
output: {
format: 'cjs',
// It should not be split chunks.
inlineDynamicImports: true,
entryFileNames: '[name].js',
chunkFileNames: '[name].js',
assetFileNames: '[name].[ext]',
},
}, },
}, },
plugins: [pluginHotRestart('reload'), viteTsconfigPaths()], resolve: {
// Load the Node.js entry.
mainFields: ['module', 'jsnext:main', 'jsnext'],
},
plugins: [pluginHotRestart('restart'), viteTsconfigPaths()],
worker: {
plugins: () => [viteTsconfigPaths()],
},
define,
} }
return mergeConfig(getBuildConfig(forgeEnv), config) return mergeConfig(getBuildConfig(forgeEnv), config)