diff --git a/src/App.tsx b/src/App.tsx index 73ffe4f26..7496f32eb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -26,6 +26,7 @@ import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher' export function App() { const { project, file } = useLoaderData() as IndexLoaderData + // Keep a lookout for a URL query string that invokes the 'import file from URL' command useCreateFileLinkQuery() useRefreshSettings(PATHS.FILE + 'SETTINGS') const navigate = useNavigate() diff --git a/src/components/CommandComboBox.tsx b/src/components/CommandComboBox.tsx index d647c9af5..a4f1566d2 100644 --- a/src/components/CommandComboBox.tsx +++ b/src/components/CommandComboBox.tsx @@ -4,6 +4,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext' import { Command } from 'lib/commandTypes' import { useEffect, useState } from 'react' import { CustomIcon } from './CustomIcon' +import { getActorNextEvents } from 'lib/utils' function CommandComboBox({ options, @@ -73,7 +74,8 @@ function CommandComboBox({ {'icon' in option && option.icon && ( @@ -96,3 +98,11 @@ function CommandComboBox({ } export default CommandComboBox + +function optionIsDisabled(option: Command): boolean { + return ( + 'machineActor' in option && + option.machineActor !== undefined && + !getActorNextEvents(option.machineActor.getSnapshot()).includes(option.name) + ) +} diff --git a/src/components/ProjectSidebarMenu.tsx b/src/components/ProjectSidebarMenu.tsx index 5735743a5..74ee48577 100644 --- a/src/components/ProjectSidebarMenu.tsx +++ b/src/components/ProjectSidebarMenu.tsx @@ -186,7 +186,6 @@ function ProjectMenuPopover({ { id: 'share-link', Element: 'button', - className: !isDesktop() ? 'hidden' : '', children: 'Share link to file', onClick: async () => { const shareUrl = createFileLink({ diff --git a/src/components/ProjectsContextProvider.tsx b/src/components/ProjectsContextProvider.tsx index 4615d222b..b6df1776f 100644 --- a/src/components/ProjectsContextProvider.tsx +++ b/src/components/ProjectsContextProvider.tsx @@ -3,11 +3,11 @@ import { useCommandsContext } from 'hooks/useCommandsContext' import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher' import { useProjectsLoader } from 'hooks/useProjectsLoader' import { projectsMachine } from 'machines/projectsMachine' -import { createContext, useEffect, useState } from 'react' +import { createContext, useCallback, useEffect, useState } from 'react' import { Actor, AnyStateMachine, fromPromise, Prop, StateFrom } from 'xstate' import { useLspContext } from './LspProvider' import toast from 'react-hot-toast' -import { useLocation, useNavigate } from 'react-router-dom' +import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' import { PATHS } from 'lib/paths' import { createNewProjectDirectory, @@ -18,11 +18,27 @@ import { getNextProjectIndex, interpolateProjectNameWithIndex, doesProjectNameNeedInterpolated, + getNextFileName, } from 'lib/desktopFS' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import useStateMachineCommands from 'hooks/useStateMachineCommands' import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig' import { isDesktop } from 'lib/isDesktop' +import { + CREATE_FILE_URL_PARAM, + FILE_EXT, + PROJECT_ENTRYPOINT, +} from 'lib/constants' +import { DeepPartial } from 'lib/types' +import { Configuration } from 'wasm-lib/kcl/bindings/Configuration' +import { codeManager } from 'lib/singletons' +import { + loadAndValidateSettings, + projectConfigurationToSettingsPayload, + saveSettings, + setSettingsAtLevel, +} from 'lib/settings/settingsUtils' +import { Project } from 'lib/project' type MachineContext = { state?: StateFrom @@ -44,47 +60,25 @@ export const ProjectsContextProvider = ({ children, }: { children: React.ReactNode -}) => { - return isDesktop() ? ( - {children} - ) : ( - {children} - ) -} - -const ProjectsContextWeb = ({ children }: { children: React.ReactNode }) => { - return ( - {}, - }} - > - {children} - - ) -} - -const ProjectsContextDesktop = ({ - children, -}: { - children: React.ReactNode }) => { const navigate = useNavigate() const location = useLocation() + const [searchParams, setSearchParams] = useSearchParams() + const clearImportSearchParams = useCallback(() => { + // Clear the search parameters related to the "Import file from URL" command + // or we'll never be able cancel or submit it. + searchParams.delete(CREATE_FILE_URL_PARAM) + searchParams.delete('code') + searchParams.delete('name') + searchParams.delete('units') + setSearchParams(searchParams) + }, [searchParams, setSearchParams]) const { commandBarSend } = useCommandsContext() const { onProjectOpen } = useLspContext() const { - settings: { context: settings }, + settings: { context: settings, send: settingsSend }, } = useSettingsAuthContext() - useEffect(() => { - console.log( - 'project directory changed', - settings.app.projectDirectory.current - ) - }, [settings.app.projectDirectory.current]) - const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0) const { projectPaths, projectsDir } = useProjectsLoader([ projectsLoaderTrigger, @@ -163,6 +157,31 @@ const ProjectsContextDesktop = ({ } } }, + navigateToFile: ({ context, event }) => { + if (event.type !== 'xstate.done.actor.create-file') return + // For now, the browser version of create-file doesn't need to navigate + // since it just overwrites the current file. + if (!isDesktop()) return + let projectPath = window.electron.join( + context.defaultDirectory, + event.output.projectName + ) + let filePath = window.electron.join( + projectPath, + event.output.fileName + ) + onProjectOpen( + { + name: event.output.projectName, + path: projectPath, + }, + null + ) + const pathToNavigateTo = `${PATHS.FILE}/${encodeURIComponent( + filePath + )}` + navigate(pathToNavigateTo) + }, toastSuccess: ({ event }) => toast.success( ('data' in event && typeof event.data === 'string' && event.data) || @@ -182,7 +201,10 @@ const ProjectsContextDesktop = ({ ), }, actors: { - readProjects: fromPromise(() => listProjects()), + readProjects: fromPromise(async () => { + if (!isDesktop()) return [] as Project[] + return listProjects() + }), createProject: fromPromise(async ({ input }) => { let name = ( input && 'name' in input && input.name @@ -238,6 +260,101 @@ const ProjectsContextDesktop = ({ name: input.name, } }), + createFile: fromPromise(async ({ input }) => { + let projectName = + (input.method === 'newProject' ? input.name : input.projectName) || + settings.projects.defaultProjectName.current + let fileName = + input.method === 'newProject' + ? PROJECT_ENTRYPOINT + : input.name.endsWith(FILE_EXT) + ? input.name + : input.name + FILE_EXT + let message = 'File created successfully' + const unitsConfiguration: DeepPartial = { + settings: { + project: { + directory: settings.app.projectDirectory.current, + }, + modeling: { + base_unit: input.units, + }, + }, + } + + if (isDesktop()) { + const needsInterpolated = + doesProjectNameNeedInterpolated(projectName) + console.log( + `The project name "${projectName}" needs interpolated: ${needsInterpolated}` + ) + if (needsInterpolated) { + const nextIndex = getNextProjectIndex(projectName, input.projects) + projectName = interpolateProjectNameWithIndex( + projectName, + nextIndex + ) + } + + // Create the project around the file if newProject + if (input.method === 'newProject') { + await createNewProjectDirectory( + projectName, + input.code, + unitsConfiguration + ) + message = `Project "${projectName}" created successfully with link contents` + } else { + let projectPath = window.electron.join( + settings.app.projectDirectory.current, + projectName + ) + + message = `File "${fileName}" created successfully` + const existingConfiguration = await loadAndValidateSettings( + projectPath + ) + const settingsToSave = setSettingsAtLevel( + existingConfiguration.settings, + 'project', + projectConfigurationToSettingsPayload(unitsConfiguration) + ) + await saveSettings(settingsToSave, projectPath) + } + + // Create the file + let baseDir = window.electron.join( + settings.app.projectDirectory.current, + projectName + ) + const { name, path } = getNextFileName({ + entryName: fileName, + baseDir, + }) + fileName = name + + await window.electron.writeFile(path, input.code || '') + } else { + // Browser version doesn't navigate, just overwrites the current file + clearImportSearchParams() + codeManager.updateCodeStateEditor(input.code || '') + await codeManager.writeToFile() + message = 'File successfully overwritten with link contents' + settingsSend({ + type: 'set.modeling.defaultUnit', + data: { + level: 'project', + value: input.units, + }, + }) + } + + return { + message, + fileName, + projectName, + } + }), }, guards: { 'Has at least 1 project': ({ event }) => { @@ -267,6 +384,7 @@ const ProjectsContextDesktop = ({ state, commandBarConfig: projectsCommandBarConfig, actor, + onCancel: clearImportSearchParams, }) return ( diff --git a/src/hooks/useCreateFileLinkQueryWatcher.ts b/src/hooks/useCreateFileLinkQueryWatcher.ts index 3e1ceb94a..24915a411 100644 --- a/src/hooks/useCreateFileLinkQueryWatcher.ts +++ b/src/hooks/useCreateFileLinkQueryWatcher.ts @@ -1,7 +1,7 @@ import { base64ToString } from 'lib/base64' import { CREATE_FILE_URL_PARAM, DEFAULT_FILE_NAME } from 'lib/constants' import { useEffect } from 'react' -import { useLocation } from 'react-router-dom' +import { useSearchParams } from 'react-router-dom' import { useCommandsContext } from './useCommandsContext' import { useSettingsAuthContext } from './useSettingsAuthContext' import { isDesktop } from 'lib/isDesktop' @@ -16,28 +16,28 @@ import { baseUnitsUnion } from 'lib/settings/settingsTypes' * URL parameters. */ export function useCreateFileLinkQuery() { - const location = useLocation() + const [searchParams] = useSearchParams() const { commandBarSend } = useCommandsContext() const { settings } = useSettingsAuthContext() useEffect(() => { - const urlParams = new URLSearchParams(location.search) - const createFileParam = urlParams.has(CREATE_FILE_URL_PARAM) + const createFileParam = searchParams.has(CREATE_FILE_URL_PARAM) console.log('checking for createFileParam', { createFileParam, - urlParams: [...urlParams.entries()], - location, + searchParams: [...searchParams.entries()], }) if (createFileParam) { const params: FileLinkParams = { - code: base64ToString(decodeURIComponent(urlParams.get('code') ?? '')), + code: base64ToString( + decodeURIComponent(searchParams.get('code') ?? '') + ), - name: urlParams.get('name') ?? DEFAULT_FILE_NAME, + name: searchParams.get('name') ?? DEFAULT_FILE_NAME, units: - (baseUnitsUnion.find((unit) => urlParams.get('units') === unit) || + (baseUnitsUnion.find((unit) => searchParams.get('units') === unit) || settings.context.modeling.defaultUnit.default) ?? settings.context.modeling.defaultUnit.current, } @@ -74,5 +74,5 @@ export function useCreateFileLinkQuery() { }, }) } - }, [location.search]) + }, [searchParams]) } diff --git a/src/hooks/useProjectsLoader.tsx b/src/hooks/useProjectsLoader.tsx index 04e55c917..aff8edaa8 100644 --- a/src/hooks/useProjectsLoader.tsx +++ b/src/hooks/useProjectsLoader.tsx @@ -14,7 +14,7 @@ export const useProjectsLoader = (deps?: [number]) => { useEffect(() => { // Useless on web, until we get fake filesystems over there. - if (!isDesktop) return + if (!isDesktop()) return if (deps && deps[0] === lastTs) return diff --git a/src/lib/commandBarConfigs/projectsCommandConfig.ts b/src/lib/commandBarConfigs/projectsCommandConfig.ts index cc639e003..2af32111b 100644 --- a/src/lib/commandBarConfigs/projectsCommandConfig.ts +++ b/src/lib/commandBarConfigs/projectsCommandConfig.ts @@ -1,6 +1,7 @@ import { UnitLength_type } from '@kittycad/lib/dist/types/src/models' import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning' import { StateMachineCommandSetConfig } from 'lib/commandTypes' +import { isDesktop } from 'lib/isDesktop' import { baseUnitLabels, baseUnitsUnion } from 'lib/settings/settingsTypes' import { projectsMachine } from 'machines/projectsMachine' @@ -112,16 +113,27 @@ export const projectsCommandBarConfig: StateMachineCommandSetConfig< inputType: 'options', required: true, skip: true, - options: [ - { name: 'New Project', value: 'newProject' }, - { name: 'Existing Project', value: 'existingProject' }, - ], + options: isDesktop() + ? [ + { name: 'New project', value: 'newProject' }, + { name: 'Existing project', value: 'existingProject' }, + ] + : [{ name: 'Overwrite', value: 'existingProject' }], + valueSummary(value) { + return isDesktop() + ? value === 'newProject' + ? 'New project' + : 'Existing project' + : 'Overwrite' + }, }, // TODO: We can't get the currently-opened project to auto-populate here because // it's not available on projectMachine, but lower in fileMachine. Unify these. projectName: { inputType: 'options', - required: (commandsContext) => commandsContext.argumentsToSubmit.method === 'existingProject', + required: (commandsContext) => + isDesktop() && + commandsContext.argumentsToSubmit.method === 'existingProject', skip: true, options: [], optionsFromContext: (context) => @@ -132,13 +144,16 @@ export const projectsCommandBarConfig: StateMachineCommandSetConfig< }, name: { inputType: 'string', - required: true, + required: isDesktop(), skip: true, }, code: { inputType: 'text', - required: false, + required: true, skip: true, + valueSummary(value) { + return value?.trim().split('\n').length + ' lines' + }, }, units: { inputType: 'options', @@ -151,7 +166,17 @@ export const projectsCommandBarConfig: StateMachineCommandSetConfig< }, }, reviewMessage(commandBarContext) { - return `Will add the contents from URL to a new ${commandBarContext.argumentsToSubmit.method === 'newProject' ? 'project with file main.kcl' : `file within the project "${commandBarContext.argumentsToSubmit.projectName}"`} named "${commandBarContext.argumentsToSubmit.name}", and set default units to "${commandBarContext.argumentsToSubmit.units}".` + return isDesktop() + ? `Will add the contents from URL to a new ${ + commandBarContext.argumentsToSubmit.method === 'newProject' + ? 'project with file main.kcl' + : `file within the project "${commandBarContext.argumentsToSubmit.projectName}"` + } named "${ + commandBarContext.argumentsToSubmit.name + }", and set default units to "${ + commandBarContext.argumentsToSubmit.units + }".` + : `Will overwrite the contents of the current file with the contents from the URL.` }, - } + }, } diff --git a/src/lib/createFileLink.ts b/src/lib/createFileLink.ts index 85d2adb75..2dc5b545e 100644 --- a/src/lib/createFileLink.ts +++ b/src/lib/createFileLink.ts @@ -1,26 +1,23 @@ import { UnitLength_type } from '@kittycad/lib/dist/types/src/models' -import { CREATE_FILE_URL_PARAM, PROD_APP_URL } from './constants' +import { CREATE_FILE_URL_PARAM } from './constants' import { stringToBase64 } from './base64' export interface FileLinkParams { - code: string - name: string - units: UnitLength_type + code: string + name: string + units: UnitLength_type } /** * Given a file's code, name, and units, creates shareable link * TODO: make the app respect this link */ -export function createFileLink({ - code, - name, - units, -}: FileLinkParams) { +export function createFileLink({ code, name, units }: FileLinkParams) { + const origin = globalThis.window.location.origin return new URL( `/?${CREATE_FILE_URL_PARAM}&name=${encodeURIComponent( name )}&units=${units}&code=${encodeURIComponent(stringToBase64(code))}`, - PROD_APP_URL + origin ).href } diff --git a/src/lib/routeLoaders.ts b/src/lib/routeLoaders.ts index 894cd6d71..7bc4a0fde 100644 --- a/src/lib/routeLoaders.ts +++ b/src/lib/routeLoaders.ts @@ -104,7 +104,7 @@ export const fileLoader: LoaderFunction = async ( return redirect( `${PATHS.FILE}/${encodeURIComponent( isDesktop() ? fallbackFile : params.id + '/' + PROJECT_ENTRYPOINT - )}` + )}${new URL(routerData.request.url).search || ''}` ) } diff --git a/src/machines/projectsMachine.ts b/src/machines/projectsMachine.ts index e2d6827df..126f5244d 100644 --- a/src/machines/projectsMachine.ts +++ b/src/machines/projectsMachine.ts @@ -25,7 +25,10 @@ export const projectsMachine = setup({ type: 'Delete project' data: ProjectsCommandSchema['Delete project'] } - | { type: 'Import file from URL'; data: ProjectsCommandSchema['Import file from URL'] } + | { + type: 'Import file from URL' + data: ProjectsCommandSchema['Import file from URL'] + } | { type: 'navigate'; data: { name: string } } | { type: 'xstate.done.actor.read-projects' @@ -43,6 +46,10 @@ export const projectsMachine = setup({ type: 'xstate.done.actor.rename-project' output: { message: string; oldName: string; newName: string } } + | { + type: 'xstate.done.actor.create-file' + output: { message: string; projectName: string; fileName: string } + } | { type: 'assign'; data: { [key: string]: any } }, input: {} as { projects: Project[] @@ -61,6 +68,7 @@ export const projectsMachine = setup({ toastError: () => {}, navigateToProject: () => {}, navigateToProjectIfNeeded: () => {}, + navigateToFile: () => {}, }, actors: { readProjects: fromPromise(() => Promise.resolve([] as Project[])), @@ -91,13 +99,20 @@ export const projectsMachine = setup({ name: '', }) ), + createFile: fromPromise( + (_: { + input: ProjectsCommandSchema['Import file from URL'] & { + projects: Project[] + } + }) => Promise.resolve({ message: '', projectName: '', fileName: '' }) + ), }, guards: { 'Has at least 1 project': () => false, 'New project method is used': () => false, }, }).createMachine({ - /** @xstate-layout N4IgpgJg5mDOIC5QAkD2BbMACdBDAxgBYCWAdmAMS6yzFSkDaADALqKgAOqtALsaqXYgAHogAsAJgA0IAJ6IAjBIkA2AHQAOCUw0qNkjQE4xYjQF8zMtJhwES5NcmpZSqLBwBOqAFZh8PWAoAJTBcCHcvX39YZjYkEC5efkF40QQFFQBmAHY1Qwz9QwBWU0NMrRl5BGyxBTUVJlVM01rtBXNLEGtsPCIyMEdnVwifPwCKAGEPUJ5sT1H-WKFE4j4BITSMzMzNIsyJDSZMhSYmFWzKxAkTNSYxIuU9zOKT7IsrDB67fsHYEajxiEwv8xjFWMtuKtkhsrpJboZsioincFMdTIjLggVGJ1GImMVMg0JISjEV3l1PrY+g4nH95gDAiFSLgbPSxkt4is1ilQGlrhJ4YjkbU0RoMXJEIYkWoikV8hJinjdOdyd0qfYBrSQdFJtNcLNtTwOZxIdyYQh+YKkSjReKqqYBQoEQpsgiSi9MqrKb0Nb9DYEACJgAA2YANbMW4M5puhqVhAvxQptCnRKkxCleuw0Gm2+2aRVRXpsPp+Woj4wA8hwwKRDcaEjH1nGLXDE9aRSmxWmJVipWocmdsbL8kxC501SWHFMZmQoIaKBABAMyAA3VAAawG+D1swAtOX61zY7zENlEWoMkoatcdPoipjCWIZRIirozsPiYYi19qQNp-rZ3nMAPC8Dw1A4YN9QAM1QDx0DUbcZjAfdInZKMTSSJsT2qc9Lwka8lTvTFTB2WUMiyQwc3yPRv3VH4mRZQDywXJc1FXDcBmmZlMBQhYjXQhtMJ5ERT1wlQr0kQj7kxKiZTxM5s2zbQEVoycBgY9AmNQ-wKGA0DwMgngYLgtQuJZZCDwEo8sJEnD1Dwgjb2kntig0GUkWVfZDEMfFVO+Bwg1DPhSDnZjFwcdjNzUCAQzDCztP4uIMKhGy0jPezxPwySnPvHsTjlPIhyOHRsnwlM-N-NRArDLS+N0kDYIM6DYPgmKgvivjD0bYS+VbBF21RTs7UUUcimfRpXyYRF8LFCrfSBCBaoZFiItINcor1CBeIZLqhPNdKL0yxzs2cqoNDqdpSo0EoSoLOb6NCRaQv9FblzWjjTMe7bQQYBQksElKesUMRjFubRMllCHsm2a5MVldRDBfFpmj0IpxPuhwFqW0F6v0iDmpMzbvuiXbAfNFNQcaI5IaKaH9jETFsUMNRVDxOU5TxBUMcof8DSg4hQ1Js1mwyfCL2JYlakkA4GZcp16jEaGzylfMsm53UkKwfnBb+iE9pFlQDjUM8HiYV99At2WqhOO5+zPLQqbRyiyXJVwYvgeIJ38sA9bJ5td27KpdzG7zQ70VFslOBVim5v1hnLD3kuF7D2hOW5smRCQM2uhQHkZ6VtkRBFnmu0qFFjssEsTgHk9syR03xAUlWOVQ9gzyjY957H-F92u0hMJ8ryc86iUyYiCvxFNzldVvjFjjTu54XvjzrnIWZUfI0eRM2VCyK3JThe5mhdWnS5dj5i29qrYuC0KEuX1LxDxepnlOfYDghp0G+yOpngzN+Yajnno9Re1drJA3SC6HY4lvL6AVMiJEuUqgbzckfQ4+gcTnUJBYCwQA */ + /** @xstate-layout N4IgpgJg5mDOIC5QAkD2BbMACdBDAxgBYCWAdmAMS6yzFSkDaADALqKgAOqtALsaqXYgAHogAsAJgA0IAJ6IAjAHYAbADoArBJVMFTCQA4mTAMwmxAXwsy0mHARLkKASXRcATjywAzYgBtsb3cMLABVACUAGWY2JBAuXn5BONEESRl5BAUFFQM1HQUxJSYATmyFAyUTKxsMbDwiMjA1ZGosUlQsDmCAKzB8HlgKcLBcCC7e-sGYoQTiPgEhVIUy9SKSiQ0NEwkclRyMxGKJNRKlMRVDU23zpRqQW3qHJpa2jonUPoGhgGF3UZ42G6nymMzicwWyVAyzKJzECg0OiYGjERhK+kOWUMSlOGiUlTEJlMxRKBnuj3sjXIr1gHy+g2Go3GwPpsDBnG48ySS0QGj0+Q0JTOGkqBg2JhKmNRJy2OyUEn0Kml5LqlMczVatJZUyGI1IuDs2oG7PinMhPIQfKYAqFShF+PFkrkRwkYjU8OUShKKhMKg0pUs1geqoa6ppdJ1FD+AKBk2NrFmZu5KV5-L9tvtYokEsxelRagUCoMiMumyUdpVdlDL01Ee+FAAImAAoC6zwTRDk9DU9b08LRY7MWd1AZcoS-aoLiYFJWnlSNW0jQyAPIcMCkNsdpOLFOWtOC-sO7NOzLbGWFbY6acmFESWdql7R3B8UhQNsUCACZpkABuqAA1s0+D-M+YAALRLluiQ7t2WRMGIbryjeBgGBIEglNsCi5sY6hMOchQqEoCLFCh97VtST4vm+S4UGA7jBO4agcH4z7eKg7joGowExhBcbtgm4LblCIiKPBiHZiKqHoZhuaFhoBbGMYBhiPBCgStUQYUuRzR6gaZDUXxH5fmov4Ac0-z6pgvEgvGsQctBwnLGJahIZJaEYdOmKEfJuQSoRRKSRcZHPNSunoPp750QxTEsTwbEcWoFkGuBkECfZXIwSJcEIS5Ekoe5MnOggXqIaUqFiiUhIInemkhiFzRNi2EU0Z+1KmYBagQM2YCAtZ9JQRljmiTlrn5dJnlFShOLwcpZjKBs+KBrUVb1WojU9c1hlRexMWsexnFdS2KV8QN5q7laNqHlmOaTcpmjoWc8L6HoYrBfOagjGMm02QyrXfqQf4dSBEB9Tqp1dllebichUkeVhRXTiU+QecUKhCps4pvWGn0QN9rJGW1ANmYlTKg98DAKHZpoORanoyqh8oImU3pKJifJ5Ohdr7CYqjIvKWMvDjeORttjHMXtCXA2T0xpdTg20+W9MSIzgorIRXlpr6ORbPs+jwQLFEgVRPj+JQf0mUTHXcaBYG+AE4OZcsxbuts5hbCK+zFmImJgSc5zPV6BQVD6RQG80lERXblCi7tcX7VxRvgVHDtDQgaE4vK2gXKiTA6ItubmJo8IoqOylERUVhBh0XXwHEWn1YmNO7mBKg+2KpzK2IpIBUwBhqUtwYre9tbvEutfpWdsFFnkPpVJnQqz15Ozur36FoypREbGH4Zj438u7tO1qIu5qK5PBGyYjebpEkS44e9oVTbxHr5tnvk9ZeXajTuW+zaHNuzYRUmoeCEp-RKlUHJbeYVhYDDfhDVIxR5IGGnISQk6E+6EkxMUBQX9c66EMMg3Q5Zt7rWNkuOBjsjilDUAqQsZQzzgOkJNUk7o7SmF-tiXOUCmQwMGBQ1OF4TgVGzFUG8yIxQGDZiYPI4oqh+mPsrDSy05xhmfm+KO-CLT+iRucEUuwFQqWzMWXM2YTAuTtL6ZBylkRcMrkAA */ id: 'Home machine', initial: 'Reading projects', @@ -114,13 +129,7 @@ export const projectsMachine = setup({ target: '.Reading projects', }, - "Import file from URL": [{ - target: ".Creating project", - guard: "New project method is used" - }, { - target: ".Reading projects", - actions: "navigateToProject" - }] + 'Import file from URL': '.Creating file', }, states: { 'Has no projects': { @@ -165,7 +174,10 @@ export const projectsMachine = setup({ id: 'create-project', src: 'createProject', input: ({ event, context }) => { - if (event.type !== 'Create project' && event.type !== 'Import file from URL') { + if ( + event.type !== 'Create project' && + event.type !== 'Import file from URL' + ) { return { name: '', projects: context.projects, @@ -280,7 +292,41 @@ export const projectsMachine = setup({ actions: ['toastError'], }, ], - } + }, + }, + + 'Creating file': { + invoke: { + id: 'create-file', + src: 'createFile', + input: ({ event, context }) => { + if (event.type !== 'Import file from URL') { + return { + code: '', + name: '', + units: 'mm', + method: 'existingProject', + projects: context.projects, + } + } + return { + code: event.data.code || '', + name: event.data.name, + units: event.data.units, + method: event.data.method, + projectName: event.data.projectName, + projects: context.projects, + } + }, + onDone: { + target: 'Reading projects', + actions: ['navigateToFile', 'toastSuccess'], + }, + onError: { + target: 'Reading projects', + actions: 'toastError', + }, + }, }, }, }) diff --git a/src/main.ts b/src/main.ts index 7f8c9d614..8a3325cd8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -53,7 +53,7 @@ if (require('electron-squirrel-startup')) { const ZOO_STUDIO_PROTOCOL = 'zoo-studio' -/// Register our application to handle all "electron-fiddle://" protocols. +/// Register our application to handle all "zoo-studio://" protocols. if (process.defaultApp) { if (process.argv.length >= 2) { app.setAsDefaultProtocolClient(ZOO_STUDIO_PROTOCOL, process.execPath, [ @@ -90,6 +90,7 @@ const createWindow = (filePath?: string): BrowserWindow => { if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { newWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL).catch(reportRejection) } else { + console.log('Loading from file', filePath) getProjectPathAtStartup(filePath) .then(async (projectPath) => { const startIndex = path.join( @@ -320,6 +321,7 @@ const getProjectPathAtStartup = async ( // macOS: open-url events that were received before the app is ready const getOpenUrls: string[] = (global as any).getOpenUrls if (getOpenUrls && getOpenUrls.length > 0) { + console.log('getOpenUrls', getOpenUrls) projectPath = getOpenUrls[0] // We only do one project at a } // Reset this so we don't accidentally use it again. @@ -389,6 +391,8 @@ function registerStartupListeners() { ) { event.preventDefault() + console.log('open-url', url) + // If we have a mainWindow, lets open another window. if (mainWindow) { createWindow(url) diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index d68dba866..8f51a3875 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -28,6 +28,7 @@ import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher' // This route only opens in the desktop context for now, // as defined in Router.tsx, so we can use the desktop APIs and types. const Home = () => { + // Keep a lookout for a URL query string that invokes the 'import file from URL' command useCreateFileLinkQuery() const { state, send } = useProjectsContext() const { commandBarSend } = useCommandsContext() @@ -207,7 +208,7 @@ const Home = () => {

Go to a test create-file link