diff --git a/src/App.tsx b/src/App.tsx index 7496f32eb..d50e54ba0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,11 +23,24 @@ import { CoreDumpManager } from 'lib/coredump' import { UnitsMenu } from 'components/UnitsMenu' import { CameraProjectionToggle } from 'components/CameraProjectionToggle' import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher' +import { useCommandsContext } from 'hooks/useCommandsContext' export function App() { const { project, file } = useLoaderData() as IndexLoaderData + const { commandBarSend } = useCommandsContext() + // Keep a lookout for a URL query string that invokes the 'import file from URL' command - useCreateFileLinkQuery() + useCreateFileLinkQuery((argDefaultValues) => { + commandBarSend({ + type: 'Find and select command', + data: { + groupId: 'projects', + name: 'Import file from URL', + argDefaultValues, + }, + }) + }) + useRefreshSettings(PATHS.FILE + 'SETTINGS') const navigate = useNavigate() const filePath = useAbsoluteFilePath() diff --git a/src/Router.tsx b/src/Router.tsx index 647d607f3..5c2dd9f8d 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -43,6 +43,7 @@ import { useMemo } from 'react' import { AppStateProvider } from 'AppState' import { reportRejection } from 'lib/trap' import { ProjectsContextProvider } from 'components/ProjectsContextProvider' +import { ProtocolHandler } from 'components/ProtocolHandler' const createRouter = isDesktop() ? createHashRouter : createBrowserRouter @@ -53,19 +54,21 @@ const router = createRouter([ /* Make sure auth is the outermost provider or else we will have * inefficient re-renders, use the react profiler to see. */ element: ( - - - - - - - - - - - - - + + + + + + + + + + + + + + + ), errorElement: , children: [ diff --git a/src/components/ProtocolHandler.tsx b/src/components/ProtocolHandler.tsx new file mode 100644 index 000000000..b5842e2f2 --- /dev/null +++ b/src/components/ProtocolHandler.tsx @@ -0,0 +1,41 @@ +import { getSystemTheme } from 'lib/theme' +import { ZOO_STUDIO_PROTOCOL } from 'lib/link' +import { useState } from 'react' +import { useCreateFileLinkQuery, CreateFileSchemaMethodOptional } from 'hooks/useCreateFileLinkQueryWatcher' +import { isDesktop } from 'lib/isDesktop' + +export const ProtocolHandler = (props: { children: ReactNode } ) => { + const [hasCustomProtocolScheme, setHasCustomProtocolScheme] = useState(false) + const [hasAsked, setHasAsked] = useState(false) + useCreateFileLinkQuery((args) => { + if (hasAsked) return + window.location.href = `zoo-studio:${JSON.stringify(args)}` + setHasAsked(true) + setHasCustomProtocolScheme(true) + }) + + const continueToWebApp = () => { + setHasCustomProtocolScheme(false) + } + + const pathLogomarkSvg = `${isDesktop() ? '.' : ''}/zma-logomark.svg` + + return hasCustomProtocolScheme ?
+
+
+
+ Loading model into Zoo Design Studio, or continue to the web app +
+
+
: props.children +} diff --git a/src/hooks/useCreateFileLinkQueryWatcher.ts b/src/hooks/useCreateFileLinkQueryWatcher.ts index b365ef962..505d3fdb2 100644 --- a/src/hooks/useCreateFileLinkQueryWatcher.ts +++ b/src/hooks/useCreateFileLinkQueryWatcher.ts @@ -1,24 +1,30 @@ import { base64ToString } from 'lib/base64' import { CREATE_FILE_URL_PARAM, DEFAULT_FILE_NAME } from 'lib/constants' import { useEffect } from 'react' -import { useLocation, useSearchParams } from 'react-router-dom' -import { useCommandsContext } from './useCommandsContext' +import { useSearchParams } from 'react-router-dom' import { useSettingsAuthContext } from './useSettingsAuthContext' import { isDesktop } from 'lib/isDesktop' import { FileLinkParams } from 'lib/createFileLink' import { ProjectsCommandSchema } from 'lib/commandBarConfigs/projectsCommandConfig' import { baseUnitsUnion } from 'lib/settings/settingsTypes' +// For initializing the command arguments, we actually want `method` to be undefined +// so that we don't skip it in the command palette. +export type CreateFileSchemaMethodOptional = Omit< + ProjectsCommandSchema['Import file from URL'], + 'method' +> & { + method?: 'newProject' | 'existingProject' +} + /** * companion to createFileLink. This hook runs an effect on mount that * checks the URL for the CREATE_FILE_URL_PARAM and triggers the "Create file" * command if it is present, loading the command's default values from the other * URL parameters. */ -export function useCreateFileLinkQuery() { - const location = useLocation() +export function useCreateFileLinkQuery(callback: (args: CreateFileSchemaMethodOptional) => void) { const [searchParams] = useSearchParams() - const { commandBarSend } = useCommandsContext() const { settings } = useSettingsAuthContext() useEffect(() => { @@ -44,15 +50,6 @@ export function useCreateFileLinkQuery() { } console.log('createFileParam', { ...params, location }) - // For initializing the command arguments, we actually want `method` to be undefined - // so that we don't skip it in the command palette. - type CreateFileSchemaMethodOptional = Omit< - ProjectsCommandSchema['Import file from URL'], - 'method' - > & { - method?: 'newProject' | 'existingProject' - } - const argDefaultValues: CreateFileSchemaMethodOptional = { name: params.name ? isDesktop() @@ -66,14 +63,7 @@ export function useCreateFileLinkQuery() { method: isDesktop() ? undefined : 'existingProject', } - commandBarSend({ - type: 'Find and select command', - data: { - groupId: 'projects', - name: 'Import file from URL', - argDefaultValues, - }, - }) + callback(argDefaultValues) } }, [searchParams]) } diff --git a/src/lib/createFileLink.ts b/src/lib/createFileLink.ts index 2dc5b545e..ccde85eba 100644 --- a/src/lib/createFileLink.ts +++ b/src/lib/createFileLink.ts @@ -10,14 +10,32 @@ export interface FileLinkParams { /** * 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 async function createFileLink({ code, name, units }: FileLinkParams) { + const token = await getAndSyncStoredToken(input) + const urlUserShortlinks = withBaseURL('/user/shortlinks') + const origin = globalThis.window.location.origin - return new URL( + + if (!token && isDesktop()) return Promise.reject(new Error('No token found')) + + let headers = { + 'Content-Type': 'application/json', + } + if (token) headers['Authorization'] = `Bearer ${token}` + + const urlFileToShare = new URL( `/?${CREATE_FILE_URL_PARAM}&name=${encodeURIComponent( name )}&units=${units}&code=${encodeURIComponent(stringToBase64(code))}`, origin - ).href + ).toString() + + const resp = await fetch(urlUserShortlinks, { + headers, + body: JSON.stringify({ url: urlFileToShare }), + }) + const shortlink = await resp.json() + + return shortlink.url } diff --git a/src/lib/link.ts b/src/lib/link.ts new file mode 100644 index 000000000..8c61e060d --- /dev/null +++ b/src/lib/link.ts @@ -0,0 +1,3 @@ +export const ZOO_STUDIO_PROTOCOL = 'zoo-studio' + + diff --git a/src/main.ts b/src/main.ts index eb5c6691a..65760af0c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,6 +21,7 @@ import minimist from 'minimist' import getCurrentProjectFile from 'lib/getCurrentProjectFile' import os from 'node:os' import { reportRejection } from 'lib/trap' +import { ZOO_STUDIO_PROTOCOL } from 'lib/link' let mainWindow: BrowserWindow | null = null @@ -52,9 +53,8 @@ if (require('electron-squirrel-startup')) { app.quit() } -const ZOO_STUDIO_PROTOCOL = 'zoo-studio' -/// Register our application to handle all "zoo-studio://" 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, [ diff --git a/src/routes/Home.tsx b/src/routes/Home.tsx index 8f51a3875..6883c00f9 100644 --- a/src/routes/Home.tsx +++ b/src/routes/Home.tsx @@ -28,8 +28,6 @@ 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() const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)